Merge pull request #45 from aaronpo97/dev

Dev updates
This commit is contained in:
Aaron Po
2023-06-02 23:15:54 -04:00
committed by GitHub
28 changed files with 11107 additions and 605 deletions

868
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,41 +13,41 @@
},
"dependencies": {
"@hapi/iron": "^7.0.1",
"@headlessui/react": "^1.7.14",
"@headlessui/react": "^1.7.15",
"@headlessui/tailwindcss": "^0.1.3",
"@hookform/resolvers": "^3.1.0",
"@mapbox/mapbox-sdk": "^0.15.1",
"@next/bundle-analyzer": "^13.4.3",
"@prisma/client": "^4.13.0",
"@next/bundle-analyzer": "^13.4.4",
"@prisma/client": "^4.15.0",
"@react-email/components": "^0.0.6",
"@react-email/render": "^0.0.7",
"@react-email/tailwind": "^0.0.8",
"@vercel/analytics": "^1.0.0",
"@vercel/analytics": "^1.0.1",
"argon2": "^0.30.3",
"cloudinary": "^1.36.4",
"cloudinary": "^1.37.0",
"cookie": "^0.5.0",
"date-fns": "^2.30.0",
"dotenv": "^16.0.3",
"dotenv": "^16.1.3",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
"mapbox-gl": "^2.14.1",
"mapbox-gl": "^2.15.0",
"multer": "^1.4.5-lts.1",
"multer-storage-cloudinary": "^4.0.0",
"next": "^13.3.4",
"next": "^13.4.4",
"next-connect": "^1.0.0-next.3",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"pino": "^8.12.0",
"pino": "^8.14.1",
"pino-pretty": "^10.0.0",
"react": "^18.2.0",
"react-daisyui": "^3.1.2",
"react-dom": "^18.2.0",
"react-email": "^1.9.3",
"react-hook-form": "^7.43.9",
"react-hook-form": "^7.44.3",
"react-hot-toast": "^2.4.1",
"react-icons": "^4.8.0",
"react-intersection-observer": "^9.4.3",
"react-map-gl": "^7.0.23",
"react-icons": "^4.9.0",
"react-intersection-observer": "^9.4.4",
"react-map-gl": "^7.0.25",
"react-responsive-carousel": "^3.2.23",
"sparkpost": "^2.1.4",
"swr": "^2.1.5",
@@ -55,37 +55,37 @@
"zod": "^3.21.4"
},
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@faker-js/faker": "^8.0.2",
"@types/cookie": "^0.5.1",
"@types/jsonwebtoken": "^9.0.2",
"@types/lodash": "^4.14.194",
"@types/lodash": "^4.14.195",
"@types/mapbox__mapbox-sdk": "^0.13.4",
"@types/multer": "^1.4.7",
"@types/node": "^18.16.3",
"@types/node": "^20.2.5",
"@types/passport-local": "^1.0.35",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"@types/sparkpost": "^2.1.5",
"@vercel/fetch": "^6.2.0",
"autoprefixer": "^10.4.14",
"daisyui": "^2.51.6",
"daisyui": "^3.0.0",
"dotenv-cli": "^7.2.1",
"eslint": "^8.39.0",
"eslint": "^8.41.0",
"eslint-config-airbnb-base": "15.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-plugin-react": "^7.32.2",
"postcss": "^8.4.23",
"postcss": "^8.4.24",
"prettier": "^2.8.8",
"prettier-plugin-jsdoc": "^0.4.2",
"prettier-plugin-tailwindcss": "^0.2.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"onchange": "^7.1.0",
"prisma": "^4.13.0",
"prisma": "^4.15.0",
"tailwindcss": "^3.3.2",
"tailwindcss-animate": "^1.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
"typescript": "^5.1.3"
},
"prisma": {
"schema": "./src/prisma/schema.prisma",

View File

@@ -3,19 +3,25 @@ import validateUsernameRequest from '@/requests/validateUsernameRequest';
import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas';
import { Switch } from '@headlessui/react';
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 { z } from 'zod';
import UserContext from '@/contexts/UserContext';
import sendEditUserRequest from '@/requests/User/sendEditUserRequest';
import createErrorToast from '@/util/createErrorToast';
import { toast } from 'react-hot-toast';
import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer';
import FormError from '../ui/forms/FormError';
import FormInfo from '../ui/forms/FormInfo';
import FormLabel from '../ui/forms/FormLabel';
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 EditUserSchema = BaseCreateUserSchema.pick({
@@ -47,18 +53,16 @@ const AccountInfo: FC = () => {
),
});
const [editToggled, setEditToggled] = useState(false);
const onSubmit = async (data: z.infer<typeof EditUserSchema>) => {
const loadingToast = toast.loading('Submitting edits...');
try {
await sendEditUserRequest({ user: user!, data });
toast.remove(loadingToast);
toast.success('Edits submitted successfully.');
setEditToggled(false);
dispatch({ type: 'CLOSE_ALL' });
await mutate!();
} catch (error) {
setEditToggled(false);
dispatch({ type: 'CLOSE_ALL' });
toast.remove(loadingToast);
createErrorToast(error);
await mutate!();
@@ -82,9 +86,9 @@ const AccountInfo: FC = () => {
<Switch
className="toggle"
id="edit-toggle"
checked={editToggled}
checked={pageState.accountInfoOpen}
onClick={async () => {
setEditToggled((val) => !val);
dispatch({ type: 'TOGGLE_ACCOUNT_INFO_VISIBILITY' });
await mutate!();
reset({
username: user!.username,
@@ -96,7 +100,7 @@ const AccountInfo: FC = () => {
/>
</div>
</div>
{editToggled && (
{pageState.accountInfoOpen && (
<form
className="form-control space-y-5"
onSubmit={handleSubmit(onSubmit)}
@@ -109,7 +113,7 @@ const AccountInfo: FC = () => {
</FormInfo>
<FormTextInput
type="text"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.username}
id="username"
formValidationSchema={register('username')}
@@ -120,7 +124,7 @@ const AccountInfo: FC = () => {
</FormInfo>
<FormTextInput
type="email"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.email}
id="email"
formValidationSchema={register('email')}
@@ -134,7 +138,7 @@ const AccountInfo: FC = () => {
</FormInfo>
<FormTextInput
type="text"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.firstName}
id="firstName"
formValidationSchema={register('firstName')}
@@ -147,7 +151,7 @@ const AccountInfo: FC = () => {
</FormInfo>
<FormTextInput
type="text"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
error={!!formState.errors.lastName}
id="lastName"
formValidationSchema={register('lastName')}
@@ -157,7 +161,7 @@ const AccountInfo: FC = () => {
<button
className="btn-primary btn my-5 w-full"
type="submit"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.accountInfoOpen || formState.isSubmitting}
>
Save Changes
</button>

View 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;

View File

@@ -1,17 +1,24 @@
import { Switch } from '@headlessui/react';
import { FunctionComponent, useState } from 'react';
import { Dispatch, FunctionComponent } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas';
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 FormInfo from '../ui/forms/FormInfo';
import FormLabel from '../ui/forms/FormLabel';
import FormTextInput from '../ui/forms/FormTextInput';
const Security: FunctionComponent = () => {
const [editToggled, setEditToggled] = useState(false);
interface SecurityProps {
pageState: AccountPageState;
dispatch: Dispatch<AccountPageAction>;
}
const Security: FunctionComponent<SecurityProps> = ({ dispatch, pageState }) => {
const { register, handleSubmit, formState, reset } = useForm<
z.infer<typeof UpdatePasswordSchema>
>({
@@ -19,9 +26,16 @@ const Security: FunctionComponent = () => {
});
const onSubmit: SubmitHandler<z.infer<typeof UpdatePasswordSchema>> = async (data) => {
await sendUpdatePasswordRequest(data);
setEditToggled(value => !value)
reset();
const loadingToast = toast.loading('Changing password.');
try {
await sendUpdatePasswordRequest(data);
toast.remove(loadingToast);
toast.success('Password changed successfully.');
dispatch({ type: 'CLOSE_ALL' });
} catch (error) {
dispatch({ type: 'CLOSE_ALL' });
createErrorToast(error);
}
};
return (
@@ -36,15 +50,15 @@ const Security: FunctionComponent = () => {
<Switch
className="toggle"
id="edit-toggle"
checked={editToggled}
checked={pageState.securityOpen}
onClick={() => {
setEditToggled((val) => !val);
dispatch({ type: 'TOGGLE_SECURITY_VISIBILITY' });
reset();
}}
/>
</div>
</div>
{editToggled && (
{pageState.securityOpen && (
<form className="form-control" noValidate onSubmit={handleSubmit(onSubmit)}>
<FormInfo>
<FormLabel htmlFor="password">New Password</FormLabel>
@@ -52,7 +66,7 @@ const Security: FunctionComponent = () => {
</FormInfo>
<FormTextInput
type="password"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.securityOpen || formState.isSubmitting}
error={!!formState.errors.password}
id="password"
formValidationSchema={register('password')}
@@ -63,7 +77,7 @@ const Security: FunctionComponent = () => {
</FormInfo>
<FormTextInput
type="password"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.securityOpen || formState.isSubmitting}
error={!!formState.errors.confirmPassword}
id="password"
formValidationSchema={register('confirmPassword')}
@@ -71,7 +85,7 @@ const Security: FunctionComponent = () => {
<button
className="btn-primary btn mt-5"
disabled={!editToggled || formState.isSubmitting}
disabled={!pageState.securityOpen || formState.isSubmitting}
type="submit"
>
Update

View File

@@ -121,10 +121,10 @@ const EditCommentBody: FC<EditCommentBodyProps> = ({
))}
</Rating>
</div>
<div className="btn-group btn-group-horizontal">
<div className="join">
<button
type="button"
className="btn-xs btn lg:btn-sm"
className="btn-xs join-item btn lg:btn-sm"
disabled={isSubmitting || isDeleting}
onClick={() => {
setInEditMode(false);
@@ -135,13 +135,13 @@ const EditCommentBody: FC<EditCommentBodyProps> = ({
<button
type="submit"
disabled={isSubmitting || isDeleting}
className="btn-xs btn lg:btn-sm"
className="btn-xs join-item btn lg:btn-sm"
>
Save
</button>
<button
type="button"
className="btn-xs btn lg:btn-sm"
className="btn-xs join-item btn lg:btn-sm"
onClick={onDelete}
disabled={isDeleting || formState.isSubmitting}
>

View File

@@ -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;

View File

@@ -27,9 +27,9 @@ const CustomToast: FC<{ children: ReactNode }> = ({ children }) => {
const alertType = toastToClassName(t.type);
return (
<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' && (
<div>
<button

View File

@@ -12,11 +12,11 @@ const DesktopLinks: FC = () => {
return (
<div className="block flex-none">
<ul className="menu menu-horizontal p-0">
<ul className="menu menu-horizontal menu-sm">
{pages.map((page) => {
return (
<li key={page.slug}>
<Link tabIndex={0} href={page.slug}>
<Link tabIndex={0} href={page.slug} className="hover:bg-primary-focus">
<span
className={`text-lg uppercase ${
currentURL === page.slug ? 'font-black' : 'font-medium'
@@ -43,7 +43,7 @@ const MobileLinks: FC = () => {
</label>
<ul
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) => (
<li key={page.slug}>
@@ -64,7 +64,7 @@ const Navbar = () => {
const { theme, setTheme } = useTheme();
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">
<Link className="btn-ghost btn normal-case" href="/">
<span className="cursor-pointer text-lg font-bold">The Biergarten App</span>
@@ -98,7 +98,7 @@ const Navbar = () => {
</div>
</div>
<div>{isDesktopView ? <DesktopLinks /> : <MobileLinks />}</div>
</nav>
</div>
);
};
export default Navbar;

View File

@@ -14,7 +14,8 @@ const Button: FunctionComponent<FormButtonProps> = ({
// eslint-disable-next-line react/button-has-type
<button
type={type}
className={`btn-primary btn w-full rounded-xl ${isSubmitting ? 'loading' : ''}`}
className={`btn-primary btn w-full rounded-xl`}
disabled={isSubmitting}
>
{children}
</button>

View File

@@ -4,12 +4,21 @@ import { NextPage } from 'next';
import { Tab } from '@headlessui/react';
import Head from 'next/head';
import AccountInfo from '@/components/Account/AccountInfo';
import { useContext } from 'react';
import { useContext, useReducer } from 'react';
import UserContext from '@/contexts/UserContext';
import Security from '@/components/Account/Security';
import DeleteAccount from '@/components/Account/DeleteAccount';
import accountPageReducer from '@/reducers/accountPageReducer';
const AccountPage: NextPage = () => {
const { user } = useContext(UserContext);
const [pageState, dispatch] = useReducer(accountPageReducer, {
accountInfoOpen: false,
securityOpen: false,
deleteAccountOpen: false,
});
if (!user) return null;
return (
@@ -46,8 +55,9 @@ const AccountPage: NextPage = () => {
</Tab.List>
<Tab.Panels>
<Tab.Panel className="h-full space-y-5">
<AccountInfo />
<Security />
<AccountInfo pageState={pageState} dispatch={dispatch} />
<Security pageState={pageState} dispatch={dispatch} />
<DeleteAccount pageState={pageState} dispatch={dispatch} />
</Tab.Panel>
<Tab.Panel>Your posts!</Tab.Panel>
</Tab.Panels>

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Location" ADD COLUMN "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMPTZ(3);

View File

@@ -106,6 +106,8 @@ model Location {
postedBy User @relation(fields: [postedById], references: [id], onDelete: Cascade)
postedById String
BreweryPost BreweryPost?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime? @updatedAt @db.Timestamptz(3)
}
model BreweryPost {

View File

@@ -22,7 +22,6 @@ const createNewBeerImages = async ({
joinData: { beerPosts, users },
}: CreateNewBeerImagesArgs) => {
const prisma = DBClient.instance;
const createdAt = faker.date.past(1);
const beerImageData: BeerImageData[] = [];
@@ -33,6 +32,7 @@ const createNewBeerImages = async ({
const caption = faker.lorem.sentence();
const alt = faker.lorem.sentence();
const path = imageUrls[Math.floor(Math.random() * imageUrls.length)];
const createdAt = faker.date.past({ years: 1 });
beerImageData.push({
path,

View File

@@ -34,7 +34,7 @@ const createNewBeerComments = async ({
const content = faker.lorem.lines(5);
const user = users[Math.floor(Math.random() * users.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;
beerCommentData.push({

View File

@@ -1,10 +1,13 @@
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';
interface BeerPostLikeData {
beerPostId: string;
likedById: string;
createdAt: Date;
}
const createNewBeerPostLikes = async ({
@@ -19,10 +22,11 @@ const createNewBeerPostLikes = async ({
for (let i = 0; i < numberOfLikes; i++) {
const beerPost = beerPosts[Math.floor(Math.random() * beerPosts.length)];
const user = users[Math.floor(Math.random() * users.length)];
const createdAt = faker.date.past({ years: 1 });
beerPostLikeData.push({
beerPostId: beerPost.id,
likedById: user.id,
createdAt,
});
}

View File

@@ -36,7 +36,7 @@ const createNewBeerPosts = async ({
const user = users[Math.floor(Math.random() * users.length)];
const beerType = beerTypes[Math.floor(Math.random() * beerTypes.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 ibu = Math.floor(Math.random() * (60 - 10) + 10);

View File

@@ -45,7 +45,7 @@ const createNewBeerTypes = async ({ joinData }: CreateNewBeerTypesArgs) => {
types.forEach((type) => {
const user = users[Math.floor(Math.random() * users.length)];
const createdAt = faker.date.past(1);
const createdAt = faker.date.past({ years: 1 });
beerTypeData.push({
name: type,

View File

@@ -26,7 +26,7 @@ const createNewBreweryImages = async ({
joinData: { breweryPosts, users },
}: CreateBreweryImagesArgs) => {
const prisma = DBClient.instance;
const createdAt = faker.date.past(1);
const createdAt = faker.date.past({ years: 1 });
const breweryImageData: BreweryImageData[] = [];
// eslint-disable-next-line no-plusplus

View File

@@ -26,13 +26,14 @@ const createNewBreweryPostComments = async ({
const { breweryPosts, users } = joinData;
const prisma = DBClient.instance;
const breweryPostCommentData: BreweryPostCommentData[] = [];
const createdAt = faker.date.past(1);
const rating = Math.floor(Math.random() * 5) + 1;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < numberOfComments; i++) {
const content = faker.lorem.lines(3).replace(/\n/g, ' ');
const user = users[Math.floor(Math.random() * users.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({
content,

View File

@@ -1,9 +1,12 @@
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';
interface BreweryPostLikeData {
breweryPostId: string;
likedById: string;
createdAt: Date;
}
const createNewBreweryPostLikes = async ({
@@ -21,10 +24,12 @@ const createNewBreweryPostLikes = async ({
for (let i = 0; i < numberOfLikes; i++) {
const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)];
const user = users[Math.floor(Math.random() * users.length)];
const createdAt = faker.date.past({ years: 1 });
breweryPostLikeData.push({
breweryPostId: breweryPost.id,
likedById: user.id,
createdAt,
});
}
await DBClient.instance.breweryPostLike.createMany({

View File

@@ -36,8 +36,8 @@ const createNewBreweryPosts = async ({
locations.splice(locationIndex, 1); // Remove the location from the array
const description = faker.lorem.lines(20).replace(/(\r\n|\n|\r)/gm, ' ');
const user = users[Math.floor(Math.random() * users.length)];
const createdAt = faker.date.past(1);
const dateEstablished = faker.date.past(40);
const createdAt = faker.date.past({ years: 1 });
const dateEstablished = faker.date.past({ years: 40 });
breweryData.push({
name,

View File

@@ -1,9 +1,8 @@
/* eslint-disable import/no-extraneous-dependencies */
import { faker } from '@faker-js/faker';
import { User } from '@prisma/client';
import { GeocodeFeature } from '@mapbox/mapbox-sdk/services/geocoding';
import DBClient from '../../DBClient';
import geocode from '../../../config/mapbox/geocoder';
import canadianCities from '../util/canadianCities';
interface CreateNewLocationsArgs {
numberOfLocations: number;
@@ -19,6 +18,7 @@ interface LocationData {
coordinates: number[];
address: string;
postedById: string;
createdAt: Date;
}
const createNewLocations = async ({
@@ -27,42 +27,25 @@ const createNewLocations = async ({
}: CreateNewLocationsArgs) => {
const prisma = DBClient.instance;
const locationNames: string[] = [];
const locationData: LocationData[] = [];
// eslint-disable-next-line no-plusplus
for (let i = 0; i < numberOfLocations; i++) {
locationNames.push(faker.address.cityName());
}
const geocodePromises: Promise<GeocodeFeature>[] = [];
locationNames.forEach((locationName) => {
geocodePromises.push(geocode(locationName));
});
const geocodedLocations = await Promise.all(geocodePromises);
const locationData: LocationData[] = [];
geocodedLocations.forEach((geodata) => {
const randomIndex = Math.floor(Math.random() * canadianCities.length);
const randomCity = canadianCities[randomIndex];
const randomUser = joinData.users[Math.floor(Math.random() * joinData.users.length)];
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;
canadianCities.splice(randomIndex, 1);
locationData.push({
city,
stateOrProvince,
country,
coordinates,
address,
postedById,
address: randomCity.city,
city: randomCity.city,
coordinates: [randomCity.longitude, randomCity.latitude],
createdAt: faker.date.past({ years: 1 }),
postedById: randomUser.id,
stateOrProvince: randomCity.province,
country: 'Canada',
});
});
}
await prisma.location.createMany({ data: locationData, skipDuplicates: true });

View File

@@ -31,11 +31,11 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => {
// eslint-disable-next-line no-plusplus
for (let i = 0; i < numberOfUsers; i++) {
const randomValue = crypto.randomBytes(1).toString('hex');
const firstName = faker.name.firstName();
const lastName = faker.name.lastName();
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const username = `${firstName[0]}.${lastName}.${randomValue}`.toLowerCase();
const email = faker.internet
.email(firstName, randomValue, 'example.com')
.email({ firstName, lastName, provider: 'example.com' })
.toLowerCase();
const userAvailable =
@@ -51,7 +51,7 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => {
takenEmails.push(email);
const dateOfBirth = faker.date.birthdate({ mode: 'age', min: 19 });
const createdAt = faker.date.past(1);
const createdAt = faker.date.past({ years: 4 });
const user = {
firstName,

File diff suppressed because it is too large Load Diff

View 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;

View File

@@ -5,6 +5,7 @@ import Welcome from '@/emails/Welcome';
import { render } from '@react-email/render';
import { z } from 'zod';
import { BASE_URL } from '@/config/env';
import { ReactElement } from 'react';
import GetUserSchema from './schema/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 address = email;
const html = render(Welcome({ name, url, subject })!);
const text = render(Welcome({ name, url, subject })!, { plainText: true });
const component = Welcome({ name, url, subject })! as ReactElement<unknown, string>;
const html = render(component);
const text = render(component, { plainText: true });
await sendEmail({ address, subject, text, html });
};

View File

@@ -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"
}