mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
868
package-lock.json
generated
868
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -13,41 +13,41 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/iron": "^7.0.1",
|
"@hapi/iron": "^7.0.1",
|
||||||
"@headlessui/react": "^1.7.14",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@headlessui/tailwindcss": "^0.1.3",
|
"@headlessui/tailwindcss": "^0.1.3",
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.0",
|
||||||
"@mapbox/mapbox-sdk": "^0.15.1",
|
"@mapbox/mapbox-sdk": "^0.15.1",
|
||||||
"@next/bundle-analyzer": "^13.4.3",
|
"@next/bundle-analyzer": "^13.4.4",
|
||||||
"@prisma/client": "^4.13.0",
|
"@prisma/client": "^4.15.0",
|
||||||
"@react-email/components": "^0.0.6",
|
"@react-email/components": "^0.0.6",
|
||||||
"@react-email/render": "^0.0.7",
|
"@react-email/render": "^0.0.7",
|
||||||
"@react-email/tailwind": "^0.0.8",
|
"@react-email/tailwind": "^0.0.8",
|
||||||
"@vercel/analytics": "^1.0.0",
|
"@vercel/analytics": "^1.0.1",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
"cloudinary": "^1.36.4",
|
"cloudinary": "^1.37.0",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.1.3",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mapbox-gl": "^2.14.1",
|
"mapbox-gl": "^2.15.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"multer-storage-cloudinary": "^4.0.0",
|
"multer-storage-cloudinary": "^4.0.0",
|
||||||
"next": "^13.3.4",
|
"next": "^13.4.4",
|
||||||
"next-connect": "^1.0.0-next.3",
|
"next-connect": "^1.0.0-next.3",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pino": "^8.12.0",
|
"pino": "^8.14.1",
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-daisyui": "^3.1.2",
|
"react-daisyui": "^3.1.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-email": "^1.9.3",
|
"react-email": "^1.9.3",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.44.3",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-icons": "^4.8.0",
|
"react-icons": "^4.9.0",
|
||||||
"react-intersection-observer": "^9.4.3",
|
"react-intersection-observer": "^9.4.4",
|
||||||
"react-map-gl": "^7.0.23",
|
"react-map-gl": "^7.0.25",
|
||||||
"react-responsive-carousel": "^3.2.23",
|
"react-responsive-carousel": "^3.2.23",
|
||||||
"sparkpost": "^2.1.4",
|
"sparkpost": "^2.1.4",
|
||||||
"swr": "^2.1.5",
|
"swr": "^2.1.5",
|
||||||
@@ -55,37 +55,37 @@
|
|||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^7.6.0",
|
"@faker-js/faker": "^8.0.2",
|
||||||
"@types/cookie": "^0.5.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"@types/jsonwebtoken": "^9.0.2",
|
"@types/jsonwebtoken": "^9.0.2",
|
||||||
"@types/lodash": "^4.14.194",
|
"@types/lodash": "^4.14.195",
|
||||||
"@types/mapbox__mapbox-sdk": "^0.13.4",
|
"@types/mapbox__mapbox-sdk": "^0.13.4",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^18.16.3",
|
"@types/node": "^20.2.5",
|
||||||
"@types/passport-local": "^1.0.35",
|
"@types/passport-local": "^1.0.35",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^18.2.8",
|
||||||
"@types/react-dom": "^18.2.1",
|
"@types/react-dom": "^18.2.4",
|
||||||
"@types/sparkpost": "^2.1.5",
|
"@types/sparkpost": "^2.1.5",
|
||||||
"@vercel/fetch": "^6.2.0",
|
"@vercel/fetch": "^6.2.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"daisyui": "^2.51.6",
|
"daisyui": "^3.0.0",
|
||||||
"dotenv-cli": "^7.2.1",
|
"dotenv-cli": "^7.2.1",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "17.0.0",
|
"eslint-config-airbnb-typescript": "17.0.0",
|
||||||
"eslint-config-next": "^13.3.4",
|
"eslint-config-next": "^13.4.4",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.24",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-jsdoc": "^0.4.2",
|
"prettier-plugin-jsdoc": "^0.4.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.8",
|
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||||
"onchange": "^7.1.0",
|
"onchange": "^7.1.0",
|
||||||
"prisma": "^4.13.0",
|
"prisma": "^4.15.0",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.2",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"schema": "./src/prisma/schema.prisma",
|
"schema": "./src/prisma/schema.prisma",
|
||||||
|
|||||||
@@ -3,19 +3,25 @@ import validateUsernameRequest from '@/requests/validateUsernameRequest';
|
|||||||
import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas';
|
import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas';
|
||||||
import { Switch } from '@headlessui/react';
|
import { Switch } from '@headlessui/react';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { FC, useContext, useState } from 'react';
|
import { Dispatch, FC, useContext } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import UserContext from '@/contexts/UserContext';
|
import UserContext from '@/contexts/UserContext';
|
||||||
import sendEditUserRequest from '@/requests/User/sendEditUserRequest';
|
import sendEditUserRequest from '@/requests/User/sendEditUserRequest';
|
||||||
import createErrorToast from '@/util/createErrorToast';
|
import createErrorToast from '@/util/createErrorToast';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer';
|
||||||
import FormError from '../ui/forms/FormError';
|
import FormError from '../ui/forms/FormError';
|
||||||
import FormInfo from '../ui/forms/FormInfo';
|
import FormInfo from '../ui/forms/FormInfo';
|
||||||
import FormLabel from '../ui/forms/FormLabel';
|
import FormLabel from '../ui/forms/FormLabel';
|
||||||
import FormTextInput from '../ui/forms/FormTextInput';
|
import FormTextInput from '../ui/forms/FormTextInput';
|
||||||
|
|
||||||
const AccountInfo: FC = () => {
|
interface AccountInfoProps {
|
||||||
|
pageState: AccountPageState;
|
||||||
|
dispatch: Dispatch<AccountPageAction>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountInfo: FC<AccountInfoProps> = ({ pageState, dispatch }) => {
|
||||||
const { user, mutate } = useContext(UserContext);
|
const { user, mutate } = useContext(UserContext);
|
||||||
|
|
||||||
const EditUserSchema = BaseCreateUserSchema.pick({
|
const EditUserSchema = BaseCreateUserSchema.pick({
|
||||||
@@ -47,18 +53,16 @@ const AccountInfo: FC = () => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [editToggled, setEditToggled] = useState(false);
|
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof EditUserSchema>) => {
|
const onSubmit = async (data: z.infer<typeof EditUserSchema>) => {
|
||||||
const loadingToast = toast.loading('Submitting edits...');
|
const loadingToast = toast.loading('Submitting edits...');
|
||||||
try {
|
try {
|
||||||
await sendEditUserRequest({ user: user!, data });
|
await sendEditUserRequest({ user: user!, data });
|
||||||
toast.remove(loadingToast);
|
toast.remove(loadingToast);
|
||||||
toast.success('Edits submitted successfully.');
|
toast.success('Edits submitted successfully.');
|
||||||
setEditToggled(false);
|
dispatch({ type: 'CLOSE_ALL' });
|
||||||
await mutate!();
|
await mutate!();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setEditToggled(false);
|
dispatch({ type: 'CLOSE_ALL' });
|
||||||
toast.remove(loadingToast);
|
toast.remove(loadingToast);
|
||||||
createErrorToast(error);
|
createErrorToast(error);
|
||||||
await mutate!();
|
await mutate!();
|
||||||
@@ -82,9 +86,9 @@ const AccountInfo: FC = () => {
|
|||||||
<Switch
|
<Switch
|
||||||
className="toggle"
|
className="toggle"
|
||||||
id="edit-toggle"
|
id="edit-toggle"
|
||||||
checked={editToggled}
|
checked={pageState.accountInfoOpen}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setEditToggled((val) => !val);
|
dispatch({ type: 'TOGGLE_ACCOUNT_INFO_VISIBILITY' });
|
||||||
await mutate!();
|
await mutate!();
|
||||||
reset({
|
reset({
|
||||||
username: user!.username,
|
username: user!.username,
|
||||||
@@ -96,7 +100,7 @@ const AccountInfo: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{editToggled && (
|
{pageState.accountInfoOpen && (
|
||||||
<form
|
<form
|
||||||
className="form-control space-y-5"
|
className="form-control space-y-5"
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
@@ -109,7 +113,7 @@ const AccountInfo: FC = () => {
|
|||||||
</FormInfo>
|
</FormInfo>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
type="text"
|
type="text"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
|
||||||
error={!!formState.errors.username}
|
error={!!formState.errors.username}
|
||||||
id="username"
|
id="username"
|
||||||
formValidationSchema={register('username')}
|
formValidationSchema={register('username')}
|
||||||
@@ -120,7 +124,7 @@ const AccountInfo: FC = () => {
|
|||||||
</FormInfo>
|
</FormInfo>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
type="email"
|
type="email"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
|
||||||
error={!!formState.errors.email}
|
error={!!formState.errors.email}
|
||||||
id="email"
|
id="email"
|
||||||
formValidationSchema={register('email')}
|
formValidationSchema={register('email')}
|
||||||
@@ -134,7 +138,7 @@ const AccountInfo: FC = () => {
|
|||||||
</FormInfo>
|
</FormInfo>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
type="text"
|
type="text"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
|
||||||
error={!!formState.errors.firstName}
|
error={!!formState.errors.firstName}
|
||||||
id="firstName"
|
id="firstName"
|
||||||
formValidationSchema={register('firstName')}
|
formValidationSchema={register('firstName')}
|
||||||
@@ -147,7 +151,7 @@ const AccountInfo: FC = () => {
|
|||||||
</FormInfo>
|
</FormInfo>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
type="text"
|
type="text"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
|
||||||
error={!!formState.errors.lastName}
|
error={!!formState.errors.lastName}
|
||||||
id="lastName"
|
id="lastName"
|
||||||
formValidationSchema={register('lastName')}
|
formValidationSchema={register('lastName')}
|
||||||
@@ -157,7 +161,7 @@ const AccountInfo: FC = () => {
|
|||||||
<button
|
<button
|
||||||
className="btn-primary btn my-5 w-full"
|
className="btn-primary btn my-5 w-full"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
|
||||||
>
|
>
|
||||||
Save Changes
|
Save Changes
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
76
src/components/Account/DeleteAccount.tsx
Normal file
76
src/components/Account/DeleteAccount.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer';
|
||||||
|
import { Switch } from '@headlessui/react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { Dispatch, FunctionComponent, useRef } from 'react';
|
||||||
|
|
||||||
|
interface DeleteAccountProps {
|
||||||
|
pageState: AccountPageState;
|
||||||
|
dispatch: Dispatch<AccountPageAction>;
|
||||||
|
}
|
||||||
|
const DeleteAccount: FunctionComponent<DeleteAccountProps> = ({
|
||||||
|
dispatch,
|
||||||
|
pageState,
|
||||||
|
}) => {
|
||||||
|
const deleteRef = useRef<null | HTMLDialogElement>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card w-full space-y-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="flex w-full items-center justify-between space-x-5">
|
||||||
|
<div className="">
|
||||||
|
<h1 className="text-lg font-bold">Delete Your Account</h1>
|
||||||
|
<p>Want to leave? Delete your account here.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
className="toggle"
|
||||||
|
id="edit-toggle"
|
||||||
|
checked={pageState.deleteAccountOpen}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch({ type: 'TOGGLE_DELETE_ACCOUNT_VISIBILITY' });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{pageState.deleteAccountOpen && (
|
||||||
|
<>
|
||||||
|
<div className="mt-3">
|
||||||
|
<button
|
||||||
|
className="btn-primary btn w-full"
|
||||||
|
onClick={() => deleteRef.current!.showModal()}
|
||||||
|
>
|
||||||
|
Delete my account
|
||||||
|
</button>
|
||||||
|
<dialog id="delete-modal" className="modal" ref={deleteRef}>
|
||||||
|
<div className="modal-box text-center">
|
||||||
|
<h3 className="text-lg font-bold">{`You're about to delete your account.`}</h3>
|
||||||
|
<p className="">This action is permanent and cannot be reversed.</p>
|
||||||
|
<div className="modal-action flex-col space-x-0 space-y-3">
|
||||||
|
<button
|
||||||
|
className="btn-error btn-sm btn w-full"
|
||||||
|
onClick={async () => {
|
||||||
|
deleteRef.current!.close();
|
||||||
|
await router.replace('/api/users/logout');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Okay, delete my account
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-success btn-sm btn w-full"
|
||||||
|
onClick={() => deleteRef.current!.close()}
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteAccount;
|
||||||
@@ -1,17 +1,24 @@
|
|||||||
import { Switch } from '@headlessui/react';
|
import { Switch } from '@headlessui/react';
|
||||||
import { FunctionComponent, useState } from 'react';
|
import { Dispatch, FunctionComponent } from 'react';
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas';
|
import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas';
|
||||||
import sendUpdatePasswordRequest from '@/requests/User/sendUpdatePasswordRequest';
|
import sendUpdatePasswordRequest from '@/requests/User/sendUpdatePasswordRequest';
|
||||||
|
import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import createErrorToast from '@/util/createErrorToast';
|
||||||
import FormError from '../ui/forms/FormError';
|
import FormError from '../ui/forms/FormError';
|
||||||
import FormInfo from '../ui/forms/FormInfo';
|
import FormInfo from '../ui/forms/FormInfo';
|
||||||
import FormLabel from '../ui/forms/FormLabel';
|
import FormLabel from '../ui/forms/FormLabel';
|
||||||
import FormTextInput from '../ui/forms/FormTextInput';
|
import FormTextInput from '../ui/forms/FormTextInput';
|
||||||
|
|
||||||
const Security: FunctionComponent = () => {
|
interface SecurityProps {
|
||||||
const [editToggled, setEditToggled] = useState(false);
|
pageState: AccountPageState;
|
||||||
|
dispatch: Dispatch<AccountPageAction>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Security: FunctionComponent<SecurityProps> = ({ dispatch, pageState }) => {
|
||||||
const { register, handleSubmit, formState, reset } = useForm<
|
const { register, handleSubmit, formState, reset } = useForm<
|
||||||
z.infer<typeof UpdatePasswordSchema>
|
z.infer<typeof UpdatePasswordSchema>
|
||||||
>({
|
>({
|
||||||
@@ -19,9 +26,16 @@ const Security: FunctionComponent = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<z.infer<typeof UpdatePasswordSchema>> = async (data) => {
|
const onSubmit: SubmitHandler<z.infer<typeof UpdatePasswordSchema>> = async (data) => {
|
||||||
|
const loadingToast = toast.loading('Changing password.');
|
||||||
|
try {
|
||||||
await sendUpdatePasswordRequest(data);
|
await sendUpdatePasswordRequest(data);
|
||||||
setEditToggled(value => !value)
|
toast.remove(loadingToast);
|
||||||
reset();
|
toast.success('Password changed successfully.');
|
||||||
|
dispatch({ type: 'CLOSE_ALL' });
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({ type: 'CLOSE_ALL' });
|
||||||
|
createErrorToast(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,15 +50,15 @@ const Security: FunctionComponent = () => {
|
|||||||
<Switch
|
<Switch
|
||||||
className="toggle"
|
className="toggle"
|
||||||
id="edit-toggle"
|
id="edit-toggle"
|
||||||
checked={editToggled}
|
checked={pageState.securityOpen}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditToggled((val) => !val);
|
dispatch({ type: 'TOGGLE_SECURITY_VISIBILITY' });
|
||||||
reset();
|
reset();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{editToggled && (
|
{pageState.securityOpen && (
|
||||||
<form className="form-control" noValidate onSubmit={handleSubmit(onSubmit)}>
|
<form className="form-control" noValidate onSubmit={handleSubmit(onSubmit)}>
|
||||||
<FormInfo>
|
<FormInfo>
|
||||||
<FormLabel htmlFor="password">New Password</FormLabel>
|
<FormLabel htmlFor="password">New Password</FormLabel>
|
||||||
@@ -52,7 +66,7 @@ const Security: FunctionComponent = () => {
|
|||||||
</FormInfo>
|
</FormInfo>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
type="password"
|
type="password"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.securityOpen || formState.isSubmitting}
|
||||||
error={!!formState.errors.password}
|
error={!!formState.errors.password}
|
||||||
id="password"
|
id="password"
|
||||||
formValidationSchema={register('password')}
|
formValidationSchema={register('password')}
|
||||||
@@ -63,7 +77,7 @@ const Security: FunctionComponent = () => {
|
|||||||
</FormInfo>
|
</FormInfo>
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
type="password"
|
type="password"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.securityOpen || formState.isSubmitting}
|
||||||
error={!!formState.errors.confirmPassword}
|
error={!!formState.errors.confirmPassword}
|
||||||
id="password"
|
id="password"
|
||||||
formValidationSchema={register('confirmPassword')}
|
formValidationSchema={register('confirmPassword')}
|
||||||
@@ -71,7 +85,7 @@ const Security: FunctionComponent = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-primary btn mt-5"
|
className="btn-primary btn mt-5"
|
||||||
disabled={!editToggled || formState.isSubmitting}
|
disabled={!pageState.securityOpen || formState.isSubmitting}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
|
|||||||
@@ -121,10 +121,10 @@ const EditCommentBody: FC<EditCommentBodyProps> = ({
|
|||||||
))}
|
))}
|
||||||
</Rating>
|
</Rating>
|
||||||
</div>
|
</div>
|
||||||
<div className="btn-group btn-group-horizontal">
|
<div className="join">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-xs btn lg:btn-sm"
|
className="btn-xs join-item btn lg:btn-sm"
|
||||||
disabled={isSubmitting || isDeleting}
|
disabled={isSubmitting || isDeleting}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setInEditMode(false);
|
setInEditMode(false);
|
||||||
@@ -135,13 +135,13 @@ const EditCommentBody: FC<EditCommentBodyProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting || isDeleting}
|
disabled={isSubmitting || isDeleting}
|
||||||
className="btn-xs btn lg:btn-sm"
|
className="btn-xs join-item btn lg:btn-sm"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-xs btn lg:btn-sm"
|
className="btn-xs join-item btn lg:btn-sm"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
disabled={isDeleting || formState.isSubmitting}
|
disabled={isDeleting || formState.isSubmitting}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa';
|
|
||||||
import { FC } from 'react';
|
|
||||||
|
|
||||||
interface PaginationProps {
|
|
||||||
pageNum: number;
|
|
||||||
pageCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BeerIndexPaginationBar: FC<PaginationProps> = ({ pageCount, pageNum }) => {
|
|
||||||
return (
|
|
||||||
<div className="btn-group">
|
|
||||||
<Link
|
|
||||||
className={`btn ${pageNum === 1 ? 'btn-disabled' : ''}`}
|
|
||||||
href={{ pathname: '/beers', query: { page_num: pageNum - 1 } }}
|
|
||||||
scroll={false}
|
|
||||||
>
|
|
||||||
<FaArrowLeft />
|
|
||||||
</Link>
|
|
||||||
<button className="btn">Page {pageNum}</button>
|
|
||||||
<Link
|
|
||||||
className={`btn ${pageNum === pageCount ? 'btn-disabled' : ''}`}
|
|
||||||
href={{ pathname: '/beers', query: { page_num: pageNum + 1 } }}
|
|
||||||
scroll={false}
|
|
||||||
>
|
|
||||||
<FaArrowRight />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BeerIndexPaginationBar;
|
|
||||||
@@ -27,9 +27,9 @@ const CustomToast: FC<{ children: ReactNode }> = ({ children }) => {
|
|||||||
const alertType = toastToClassName(t.type);
|
const alertType = toastToClassName(t.type);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`alert ${alertType} w-11/12 flex-row items-center shadow-lg animate-in fade-in duration-200 lg:w-2/12`}
|
className={`alert ${alertType} flex w-11/12 items-center justify-between shadow-lg animate-in fade-in duration-200 lg:w-4/12`}
|
||||||
>
|
>
|
||||||
<p className='text-sm'>{resolveValue(t.message, t)}</p>
|
<p className="w-full">{resolveValue(t.message, t)}</p>
|
||||||
{t.type !== 'loading' && (
|
{t.type !== 'loading' && (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ const DesktopLinks: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block flex-none">
|
<div className="block flex-none">
|
||||||
<ul className="menu menu-horizontal p-0">
|
<ul className="menu menu-horizontal menu-sm">
|
||||||
{pages.map((page) => {
|
{pages.map((page) => {
|
||||||
return (
|
return (
|
||||||
<li key={page.slug}>
|
<li key={page.slug}>
|
||||||
<Link tabIndex={0} href={page.slug}>
|
<Link tabIndex={0} href={page.slug} className="hover:bg-primary-focus">
|
||||||
<span
|
<span
|
||||||
className={`text-lg uppercase ${
|
className={`text-lg uppercase ${
|
||||||
currentURL === page.slug ? 'font-black' : 'font-medium'
|
currentURL === page.slug ? 'font-black' : 'font-medium'
|
||||||
@@ -43,7 +43,7 @@ const MobileLinks: FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="dropdown-content menu rounded-box menu-compact mt-3 w-48 bg-base-100 p-2 shadow"
|
className="menu-compact dropdown-content menu rounded-box mt-3 w-48 bg-base-100 p-2 shadow"
|
||||||
>
|
>
|
||||||
{pages.map((page) => (
|
{pages.map((page) => (
|
||||||
<li key={page.slug}>
|
<li key={page.slug}>
|
||||||
@@ -64,7 +64,7 @@ const Navbar = () => {
|
|||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar sticky top-0 z-50 bg-primary text-primary-content">
|
<div className="navbar sticky top-0 z-50 bg-primary text-primary-content">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Link className="btn-ghost btn normal-case" href="/">
|
<Link className="btn-ghost btn normal-case" href="/">
|
||||||
<span className="cursor-pointer text-lg font-bold">The Biergarten App</span>
|
<span className="cursor-pointer text-lg font-bold">The Biergarten App</span>
|
||||||
@@ -98,7 +98,7 @@ const Navbar = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{isDesktopView ? <DesktopLinks /> : <MobileLinks />}</div>
|
<div>{isDesktopView ? <DesktopLinks /> : <MobileLinks />}</div>
|
||||||
</nav>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Navbar;
|
export default Navbar;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ const Button: FunctionComponent<FormButtonProps> = ({
|
|||||||
// eslint-disable-next-line react/button-has-type
|
// eslint-disable-next-line react/button-has-type
|
||||||
<button
|
<button
|
||||||
type={type}
|
type={type}
|
||||||
className={`btn-primary btn w-full rounded-xl ${isSubmitting ? 'loading' : ''}`}
|
className={`btn-primary btn w-full rounded-xl`}
|
||||||
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,12 +4,21 @@ import { NextPage } from 'next';
|
|||||||
import { Tab } from '@headlessui/react';
|
import { Tab } from '@headlessui/react';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import AccountInfo from '@/components/Account/AccountInfo';
|
import AccountInfo from '@/components/Account/AccountInfo';
|
||||||
import { useContext } from 'react';
|
import { useContext, useReducer } from 'react';
|
||||||
import UserContext from '@/contexts/UserContext';
|
import UserContext from '@/contexts/UserContext';
|
||||||
import Security from '@/components/Account/Security';
|
import Security from '@/components/Account/Security';
|
||||||
|
import DeleteAccount from '@/components/Account/DeleteAccount';
|
||||||
|
import accountPageReducer from '@/reducers/accountPageReducer';
|
||||||
|
|
||||||
const AccountPage: NextPage = () => {
|
const AccountPage: NextPage = () => {
|
||||||
const { user } = useContext(UserContext);
|
const { user } = useContext(UserContext);
|
||||||
|
|
||||||
|
const [pageState, dispatch] = useReducer(accountPageReducer, {
|
||||||
|
accountInfoOpen: false,
|
||||||
|
securityOpen: false,
|
||||||
|
deleteAccountOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -46,8 +55,9 @@ const AccountPage: NextPage = () => {
|
|||||||
</Tab.List>
|
</Tab.List>
|
||||||
<Tab.Panels>
|
<Tab.Panels>
|
||||||
<Tab.Panel className="h-full space-y-5">
|
<Tab.Panel className="h-full space-y-5">
|
||||||
<AccountInfo />
|
<AccountInfo pageState={pageState} dispatch={dispatch} />
|
||||||
<Security />
|
<Security pageState={pageState} dispatch={dispatch} />
|
||||||
|
<DeleteAccount pageState={pageState} dispatch={dispatch} />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
<Tab.Panel>Your posts!</Tab.Panel>
|
<Tab.Panel>Your posts!</Tab.Panel>
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
|
|||||||
3
src/prisma/migrations/20230603010553_/migration.sql
Normal file
3
src/prisma/migrations/20230603010553_/migration.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Location" ADD COLUMN "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMPTZ(3);
|
||||||
@@ -106,6 +106,8 @@ model Location {
|
|||||||
postedBy User @relation(fields: [postedById], references: [id], onDelete: Cascade)
|
postedBy User @relation(fields: [postedById], references: [id], onDelete: Cascade)
|
||||||
postedById String
|
postedById String
|
||||||
BreweryPost BreweryPost?
|
BreweryPost BreweryPost?
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||||
|
updatedAt DateTime? @updatedAt @db.Timestamptz(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
model BreweryPost {
|
model BreweryPost {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const createNewBeerImages = async ({
|
|||||||
joinData: { beerPosts, users },
|
joinData: { beerPosts, users },
|
||||||
}: CreateNewBeerImagesArgs) => {
|
}: CreateNewBeerImagesArgs) => {
|
||||||
const prisma = DBClient.instance;
|
const prisma = DBClient.instance;
|
||||||
const createdAt = faker.date.past(1);
|
|
||||||
|
|
||||||
const beerImageData: BeerImageData[] = [];
|
const beerImageData: BeerImageData[] = [];
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ const createNewBeerImages = async ({
|
|||||||
const caption = faker.lorem.sentence();
|
const caption = faker.lorem.sentence();
|
||||||
const alt = faker.lorem.sentence();
|
const alt = faker.lorem.sentence();
|
||||||
const path = imageUrls[Math.floor(Math.random() * imageUrls.length)];
|
const path = imageUrls[Math.floor(Math.random() * imageUrls.length)];
|
||||||
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
|
|
||||||
beerImageData.push({
|
beerImageData.push({
|
||||||
path,
|
path,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const createNewBeerComments = async ({
|
|||||||
const content = faker.lorem.lines(5);
|
const content = faker.lorem.lines(5);
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
const beerPost = beerPosts[Math.floor(Math.random() * beerPosts.length)];
|
const beerPost = beerPosts[Math.floor(Math.random() * beerPosts.length)];
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
const rating = Math.floor(Math.random() * 5) + 1;
|
const rating = Math.floor(Math.random() * 5) + 1;
|
||||||
|
|
||||||
beerCommentData.push({
|
beerCommentData.push({
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { BeerPost, User } from '@prisma/client';
|
import type { BeerPost, User } from '@prisma/client';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
import DBClient from '../../DBClient';
|
import DBClient from '../../DBClient';
|
||||||
|
|
||||||
interface BeerPostLikeData {
|
interface BeerPostLikeData {
|
||||||
beerPostId: string;
|
beerPostId: string;
|
||||||
likedById: string;
|
likedById: string;
|
||||||
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewBeerPostLikes = async ({
|
const createNewBeerPostLikes = async ({
|
||||||
@@ -19,10 +22,11 @@ const createNewBeerPostLikes = async ({
|
|||||||
for (let i = 0; i < numberOfLikes; i++) {
|
for (let i = 0; i < numberOfLikes; i++) {
|
||||||
const beerPost = beerPosts[Math.floor(Math.random() * beerPosts.length)];
|
const beerPost = beerPosts[Math.floor(Math.random() * beerPosts.length)];
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
beerPostLikeData.push({
|
beerPostLikeData.push({
|
||||||
beerPostId: beerPost.id,
|
beerPostId: beerPost.id,
|
||||||
likedById: user.id,
|
likedById: user.id,
|
||||||
|
createdAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const createNewBeerPosts = async ({
|
|||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
const beerType = beerTypes[Math.floor(Math.random() * beerTypes.length)];
|
const beerType = beerTypes[Math.floor(Math.random() * beerTypes.length)];
|
||||||
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
|
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
|
|
||||||
const abv = Math.floor(Math.random() * (12 - 4) + 4);
|
const abv = Math.floor(Math.random() * (12 - 4) + 4);
|
||||||
const ibu = Math.floor(Math.random() * (60 - 10) + 10);
|
const ibu = Math.floor(Math.random() * (60 - 10) + 10);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const createNewBeerTypes = async ({ joinData }: CreateNewBeerTypesArgs) => {
|
|||||||
|
|
||||||
types.forEach((type) => {
|
types.forEach((type) => {
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
|
|
||||||
beerTypeData.push({
|
beerTypeData.push({
|
||||||
name: type,
|
name: type,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const createNewBreweryImages = async ({
|
|||||||
joinData: { breweryPosts, users },
|
joinData: { breweryPosts, users },
|
||||||
}: CreateBreweryImagesArgs) => {
|
}: CreateBreweryImagesArgs) => {
|
||||||
const prisma = DBClient.instance;
|
const prisma = DBClient.instance;
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
const breweryImageData: BreweryImageData[] = [];
|
const breweryImageData: BreweryImageData[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ const createNewBreweryPostComments = async ({
|
|||||||
const { breweryPosts, users } = joinData;
|
const { breweryPosts, users } = joinData;
|
||||||
const prisma = DBClient.instance;
|
const prisma = DBClient.instance;
|
||||||
const breweryPostCommentData: BreweryPostCommentData[] = [];
|
const breweryPostCommentData: BreweryPostCommentData[] = [];
|
||||||
const createdAt = faker.date.past(1);
|
|
||||||
const rating = Math.floor(Math.random() * 5) + 1;
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
for (let i = 0; i < numberOfComments; i++) {
|
for (let i = 0; i < numberOfComments; i++) {
|
||||||
const content = faker.lorem.lines(3).replace(/\n/g, ' ');
|
const content = faker.lorem.lines(3).replace(/\n/g, ' ');
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
|
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
|
||||||
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
|
const rating = Math.floor(Math.random() * 5) + 1;
|
||||||
|
|
||||||
breweryPostCommentData.push({
|
breweryPostCommentData.push({
|
||||||
content,
|
content,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import type { BreweryPost, User } from '@prisma/client';
|
import type { BreweryPost, User } from '@prisma/client';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
import DBClient from '../../DBClient';
|
import DBClient from '../../DBClient';
|
||||||
|
|
||||||
interface BreweryPostLikeData {
|
interface BreweryPostLikeData {
|
||||||
breweryPostId: string;
|
breweryPostId: string;
|
||||||
likedById: string;
|
likedById: string;
|
||||||
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewBreweryPostLikes = async ({
|
const createNewBreweryPostLikes = async ({
|
||||||
@@ -21,10 +24,12 @@ const createNewBreweryPostLikes = async ({
|
|||||||
for (let i = 0; i < numberOfLikes; i++) {
|
for (let i = 0; i < numberOfLikes; i++) {
|
||||||
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
|
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
|
|
||||||
breweryPostLikeData.push({
|
breweryPostLikeData.push({
|
||||||
breweryPostId: breweryPost.id,
|
breweryPostId: breweryPost.id,
|
||||||
likedById: user.id,
|
likedById: user.id,
|
||||||
|
createdAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await DBClient.instance.breweryPostLike.createMany({
|
await DBClient.instance.breweryPostLike.createMany({
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ const createNewBreweryPosts = async ({
|
|||||||
locations.splice(locationIndex, 1); // Remove the location from the array
|
locations.splice(locationIndex, 1); // Remove the location from the array
|
||||||
const description = faker.lorem.lines(20).replace(/(\r\n|\n|\r)/gm, ' ');
|
const description = faker.lorem.lines(20).replace(/(\r\n|\n|\r)/gm, ' ');
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past({ years: 1 });
|
||||||
const dateEstablished = faker.date.past(40);
|
const dateEstablished = faker.date.past({ years: 40 });
|
||||||
|
|
||||||
breweryData.push({
|
breweryData.push({
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { User } from '@prisma/client';
|
import { User } from '@prisma/client';
|
||||||
import { GeocodeFeature } from '@mapbox/mapbox-sdk/services/geocoding';
|
|
||||||
import DBClient from '../../DBClient';
|
import DBClient from '../../DBClient';
|
||||||
import geocode from '../../../config/mapbox/geocoder';
|
import canadianCities from '../util/canadianCities';
|
||||||
|
|
||||||
interface CreateNewLocationsArgs {
|
interface CreateNewLocationsArgs {
|
||||||
numberOfLocations: number;
|
numberOfLocations: number;
|
||||||
@@ -19,6 +18,7 @@ interface LocationData {
|
|||||||
coordinates: number[];
|
coordinates: number[];
|
||||||
address: string;
|
address: string;
|
||||||
postedById: string;
|
postedById: string;
|
||||||
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewLocations = async ({
|
const createNewLocations = async ({
|
||||||
@@ -27,42 +27,25 @@ const createNewLocations = async ({
|
|||||||
}: CreateNewLocationsArgs) => {
|
}: CreateNewLocationsArgs) => {
|
||||||
const prisma = DBClient.instance;
|
const prisma = DBClient.instance;
|
||||||
|
|
||||||
const locationNames: string[] = [];
|
const locationData: LocationData[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
for (let i = 0; i < numberOfLocations; i++) {
|
for (let i = 0; i < numberOfLocations; i++) {
|
||||||
locationNames.push(faker.address.cityName());
|
const randomIndex = Math.floor(Math.random() * canadianCities.length);
|
||||||
}
|
const randomCity = canadianCities[randomIndex];
|
||||||
|
|
||||||
const geocodePromises: Promise<GeocodeFeature>[] = [];
|
|
||||||
|
|
||||||
locationNames.forEach((locationName) => {
|
|
||||||
geocodePromises.push(geocode(locationName));
|
|
||||||
});
|
|
||||||
|
|
||||||
const geocodedLocations = await Promise.all(geocodePromises);
|
|
||||||
|
|
||||||
const locationData: LocationData[] = [];
|
|
||||||
|
|
||||||
geocodedLocations.forEach((geodata) => {
|
|
||||||
const randomUser = joinData.users[Math.floor(Math.random() * joinData.users.length)];
|
const randomUser = joinData.users[Math.floor(Math.random() * joinData.users.length)];
|
||||||
|
canadianCities.splice(randomIndex, 1);
|
||||||
const city = geodata.text;
|
|
||||||
const postedById = randomUser.id;
|
|
||||||
const stateOrProvince = geodata.context?.find((c) => c.id.startsWith('region'))?.text;
|
|
||||||
const country = geodata.context?.find((c) => c.id.startsWith('country'))?.text;
|
|
||||||
const coordinates = geodata.center;
|
|
||||||
const address = geodata.place_name;
|
|
||||||
|
|
||||||
locationData.push({
|
locationData.push({
|
||||||
city,
|
address: randomCity.city,
|
||||||
stateOrProvince,
|
city: randomCity.city,
|
||||||
country,
|
coordinates: [randomCity.longitude, randomCity.latitude],
|
||||||
coordinates,
|
createdAt: faker.date.past({ years: 1 }),
|
||||||
address,
|
postedById: randomUser.id,
|
||||||
postedById,
|
stateOrProvince: randomCity.province,
|
||||||
});
|
country: 'Canada',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await prisma.location.createMany({ data: locationData, skipDuplicates: true });
|
await prisma.location.createMany({ data: locationData, skipDuplicates: true });
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => {
|
|||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
for (let i = 0; i < numberOfUsers; i++) {
|
for (let i = 0; i < numberOfUsers; i++) {
|
||||||
const randomValue = crypto.randomBytes(1).toString('hex');
|
const randomValue = crypto.randomBytes(1).toString('hex');
|
||||||
const firstName = faker.name.firstName();
|
const firstName = faker.person.firstName();
|
||||||
const lastName = faker.name.lastName();
|
const lastName = faker.person.lastName();
|
||||||
const username = `${firstName[0]}.${lastName}.${randomValue}`.toLowerCase();
|
const username = `${firstName[0]}.${lastName}.${randomValue}`.toLowerCase();
|
||||||
const email = faker.internet
|
const email = faker.internet
|
||||||
.email(firstName, randomValue, 'example.com')
|
.email({ firstName, lastName, provider: 'example.com' })
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
const userAvailable =
|
const userAvailable =
|
||||||
@@ -51,7 +51,7 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => {
|
|||||||
takenEmails.push(email);
|
takenEmails.push(email);
|
||||||
|
|
||||||
const dateOfBirth = faker.date.birthdate({ mode: 'age', min: 19 });
|
const dateOfBirth = faker.date.birthdate({ mode: 'age', min: 19 });
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past({ years: 4 });
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
firstName,
|
firstName,
|
||||||
|
|||||||
10425
src/prisma/seed/util/canadianCities.ts
Executable file
10425
src/prisma/seed/util/canadianCities.ts
Executable file
File diff suppressed because it is too large
Load Diff
52
src/reducers/accountPageReducer.ts
Normal file
52
src/reducers/accountPageReducer.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
export interface AccountPageState {
|
||||||
|
accountInfoOpen: boolean;
|
||||||
|
securityOpen: boolean;
|
||||||
|
deleteAccountOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AccountPageAction =
|
||||||
|
| { type: 'TOGGLE_ACCOUNT_INFO_VISIBILITY' }
|
||||||
|
| { type: 'TOGGLE_SECURITY_VISIBILITY' }
|
||||||
|
| { type: 'TOGGLE_DELETE_ACCOUNT_VISIBILITY' }
|
||||||
|
| { type: 'CLOSE_ALL' };
|
||||||
|
|
||||||
|
const accountPageReducer = (
|
||||||
|
state: AccountPageState,
|
||||||
|
action: AccountPageAction,
|
||||||
|
): AccountPageState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'TOGGLE_ACCOUNT_INFO_VISIBILITY': {
|
||||||
|
return {
|
||||||
|
accountInfoOpen: !state.accountInfoOpen,
|
||||||
|
securityOpen: false,
|
||||||
|
deleteAccountOpen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'TOGGLE_DELETE_ACCOUNT_VISIBILITY': {
|
||||||
|
return {
|
||||||
|
accountInfoOpen: false,
|
||||||
|
securityOpen: false,
|
||||||
|
deleteAccountOpen: !state.deleteAccountOpen,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'TOGGLE_SECURITY_VISIBILITY': {
|
||||||
|
return {
|
||||||
|
accountInfoOpen: false,
|
||||||
|
securityOpen: !state.securityOpen,
|
||||||
|
deleteAccountOpen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'CLOSE_ALL': {
|
||||||
|
return {
|
||||||
|
accountInfoOpen: false,
|
||||||
|
securityOpen: false,
|
||||||
|
deleteAccountOpen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('Invalid action type.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default accountPageReducer;
|
||||||
@@ -5,6 +5,7 @@ import Welcome from '@/emails/Welcome';
|
|||||||
import { render } from '@react-email/render';
|
import { render } from '@react-email/render';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { BASE_URL } from '@/config/env';
|
import { BASE_URL } from '@/config/env';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
import GetUserSchema from './schema/GetUserSchema';
|
import GetUserSchema from './schema/GetUserSchema';
|
||||||
|
|
||||||
type UserSchema = z.infer<typeof GetUserSchema>;
|
type UserSchema = z.infer<typeof GetUserSchema>;
|
||||||
@@ -17,8 +18,10 @@ const sendConfirmationEmail = async ({ id, username, email }: UserSchema) => {
|
|||||||
const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`;
|
const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`;
|
||||||
const address = email;
|
const address = email;
|
||||||
|
|
||||||
const html = render(Welcome({ name, url, subject })!);
|
const component = Welcome({ name, url, subject })! as ReactElement<unknown, string>;
|
||||||
const text = render(Welcome({ name, url, subject })!, { plainText: true });
|
|
||||||
|
const html = render(component);
|
||||||
|
const text = render(component, { plainText: true });
|
||||||
|
|
||||||
await sendEmail({ address, subject, text, html });
|
await sendEmail({ address, subject, text, html });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"buildCommand": "npx prisma generate && npx prisma migrate deploy && next build"
|
"buildCommand": "npx prisma generate && npx prisma migrate deploy && next build",
|
||||||
|
"installCommand": "npm install --legacy-peer-deps"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user