From 472ead9abdf7545d2dbd52ce5cc4e24c17bdbd98 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Fri, 3 Mar 2023 21:19:18 -0500 Subject: [PATCH] Refactoring beer by id page, add delete comment Refactored the comments ui into various new components, added the delete beer comment by id feature. --- components/BeerById/BeerCommentForm.tsx | 2 +- components/BeerById/BeerInfoHeader.tsx | 15 +-- .../BeerPostCommentsPaginationBar.tsx | 50 +++++++++ .../BeerById/BeerPostCommentsSection.tsx | 64 +++++++++++ components/BeerById/CommentCard.tsx | 86 ++++++++++++--- components/EditBeerPostForm.tsx | 25 +---- ...tFormPageLayout.tsx => FormPageLayout.tsx} | 15 ++- config/auth/session.ts | 1 + getServerSideProps/redirectIfLoggedIn.ts | 12 +- pages/api/beer-comments/[id].tsx | 60 ++++++++++ pages/api/beers/[id]/index.ts | 4 +- pages/beers/[id]/edit.tsx | 9 +- pages/beers/[id]/index.tsx | 104 +++++------------- pages/beers/create.tsx | 9 +- pages/register/index.tsx | 9 +- prisma/seed/create/createNewBeerPosts.ts | 2 +- 16 files changed, 331 insertions(+), 136 deletions(-) create mode 100644 components/BeerById/BeerPostCommentsPaginationBar.tsx create mode 100644 components/BeerById/BeerPostCommentsSection.tsx rename components/ui/forms/{BeerPostFormPageLayout.tsx => FormPageLayout.tsx} (55%) create mode 100644 pages/api/beer-comments/[id].tsx diff --git a/components/BeerById/BeerCommentForm.tsx b/components/BeerById/BeerCommentForm.tsx index 3cd4099..f85972d 100644 --- a/components/BeerById/BeerCommentForm.tsx +++ b/components/BeerById/BeerCommentForm.tsx @@ -49,7 +49,7 @@ const BeerCommentForm: FunctionComponent = ({ beerPost }) beerPostId: beerPost.id, }); reset(); - router.replace(router.asPath, undefined, { scroll: false }); + router.replace(`/beers/${beerPost.id}?comments_page=1`, undefined, { scroll: false }); }; const { errors } = formState; diff --git a/components/BeerById/BeerInfoHeader.tsx b/components/BeerById/BeerInfoHeader.tsx index 8f59674..66994f5 100644 --- a/components/BeerById/BeerInfoHeader.tsx +++ b/components/BeerById/BeerInfoHeader.tsx @@ -56,13 +56,14 @@ const BeerInfoHeader: FC<{ beerPost: BeerPostQueryResult; initialLikeCount: numb
{isPostOwner && ( - - - Edit - +
+ + + +
)}
diff --git a/components/BeerById/BeerPostCommentsPaginationBar.tsx b/components/BeerById/BeerPostCommentsPaginationBar.tsx new file mode 100644 index 0000000..7f084e0 --- /dev/null +++ b/components/BeerById/BeerPostCommentsPaginationBar.tsx @@ -0,0 +1,50 @@ +import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult'; +import { FC } from 'react'; +import Link from 'next/link'; + +interface BeerCommentsPaginationBarProps { + commentsPageNum: number; + commentsPageCount: number; + beerPost: BeerPostQueryResult; +} + +const BeerCommentsPaginationBar: FC = ({ + commentsPageNum, + commentsPageCount, + beerPost, +}) => ( +
+
+ + Next Comments + + + Previous Comments + +
+
+); + +export default BeerCommentsPaginationBar; diff --git a/components/BeerById/BeerPostCommentsSection.tsx b/components/BeerById/BeerPostCommentsSection.tsx new file mode 100644 index 0000000..6a61623 --- /dev/null +++ b/components/BeerById/BeerPostCommentsSection.tsx @@ -0,0 +1,64 @@ +import UserContext from '@/contexts/userContext'; +import { BeerCommentQueryResultArrayT } from '@/services/BeerComment/schema/BeerCommentQueryResult'; +import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult'; +import { useRouter } from 'next/router'; +import { FC, useContext } from 'react'; +import BeerCommentForm from './BeerCommentForm'; +import BeerCommentsPaginationBar from './BeerPostCommentsPaginationBar'; +import CommentCard from './CommentCard'; + +interface BeerPostCommentsSectionProps { + beerPost: BeerPostQueryResult; + setComments: React.Dispatch>; + comments: BeerCommentQueryResultArrayT; + commentsPageCount: number; +} + +const BeerPostCommentsSection: FC = ({ + beerPost, + setComments, + comments, + commentsPageCount, +}) => { + const { user } = useContext(UserContext); + const router = useRouter(); + + const commentsPageNum = parseInt(router.query.comments_page as string, 10) || 1; + + return ( +
+
+
+ {user ? ( + + ) : ( +
+ Log in to leave a comment. +
+ )} +
+
+ {comments.length ? ( +
+ {comments.map((comment) => ( + + ))} + + +
+ ) : ( +
+
+ No comments yet. +
+
+ )} +
+ ); +}; + +export default BeerPostCommentsSection; diff --git a/components/BeerById/CommentCard.tsx b/components/BeerById/CommentCard.tsx index bd2cd4c..5784ce0 100644 --- a/components/BeerById/CommentCard.tsx +++ b/components/BeerById/CommentCard.tsx @@ -1,20 +1,69 @@ +import UserContext from '@/contexts/userContext'; import { BeerCommentQueryResultT } from '@/services/BeerComment/schema/BeerCommentQueryResult'; import { format, formatDistanceStrict } from 'date-fns'; import Link from 'next/link'; -import { useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; +import { useContext, useEffect, useState } from 'react'; import { Rating } from 'react-daisyui'; +import { FaEllipsisH } from 'react-icons/fa'; + +const CommentCardDropdown: React.FC<{ + comment: BeerCommentQueryResultT; + beerPostId: string; +}> = ({ comment, beerPostId }) => { + const router = useRouter(); + const { user } = useContext(UserContext); + + const isCommentOwner = user?.id === comment.postedBy.id; + + const handleDelete = async () => { + const response = await fetch(`/api/beer-comments/${comment.id}`, { + method: 'DELETE', + }); + if (!response.ok) { + throw new Error('Failed to delete comment'); + } + + router.replace(`/beers/${beerPostId}?comments_page=1`, undefined, { scroll: false }); + }; + + return ( +
+ +
    + {isCommentOwner ? ( +
  • + +
  • + ) : ( +
  • + +
  • + )} +
+
+ ); +}; + const CommentCard: React.FC<{ comment: BeerCommentQueryResultT; -}> = ({ comment }) => { + beerPostId: string; +}> = ({ comment, beerPostId }) => { const [timeDistance, setTimeDistance] = useState(''); + const { user } = useContext(UserContext); useEffect(() => { setTimeDistance(formatDistanceStrict(new Date(comment.createdAt), new Date())); }, [comment.createdAt]); return ( -
+

@@ -33,21 +82,24 @@ const CommentCard: React.FC<{ ago

-
- - {Array.from({ length: 5 }).map((val, index) => ( - - ))} - -
+ + {user && } +
+ +
+ + {Array.from({ length: 5 }).map((val, index) => ( + + ))} + +

{comment.content}

-

{comment.content}

); }; diff --git a/components/EditBeerPostForm.tsx b/components/EditBeerPostForm.tsx index ee8185a..07163a7 100644 --- a/components/EditBeerPostForm.tsx +++ b/components/EditBeerPostForm.tsx @@ -1,7 +1,7 @@ import sendEditBeerPostRequest from '@/requests/sendEditBeerPostRequest'; import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema'; import { zodResolver } from '@hookform/resolvers/zod'; -import Link from 'next/link'; + import { useRouter } from 'next/router'; import { FC, useState } from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; @@ -52,7 +52,7 @@ const EditBeerPostForm: FC = ({ previousValues }) => { return (
-
+
{error && }
@@ -116,23 +116,10 @@ const EditBeerPostForm: FC = ({ previousValues }) => { /> -
-
- - Discard Changes - -
- -
- -
+
+
); diff --git a/components/ui/forms/BeerPostFormPageLayout.tsx b/components/ui/forms/FormPageLayout.tsx similarity index 55% rename from components/ui/forms/BeerPostFormPageLayout.tsx rename to components/ui/forms/FormPageLayout.tsx index 7a4a955..fddea6c 100644 --- a/components/ui/forms/BeerPostFormPageLayout.tsx +++ b/components/ui/forms/FormPageLayout.tsx @@ -1,22 +1,33 @@ import { ReactNode, FC } from 'react'; +import Link from 'next/link'; import { IconType } from 'react-icons'; +import { BiArrowBack } from 'react-icons/bi'; interface FormPageLayoutProps { children: ReactNode; headingText: string; headingIcon: IconType; + backLink: string; + backLinkText: string; } const FormPageLayout: FC = ({ children: FormComponent, headingIcon, headingText, + backLink, + backLinkText, }) => { return (
-
- {headingIcon({ className: 'text-4xl' })} +
+ + + +
+
+ {headingIcon({ className: 'text-4xl' })}{' '}

{headingText}

{FormComponent}
diff --git a/config/auth/session.ts b/config/auth/session.ts index c05bf4b..569ded9 100644 --- a/config/auth/session.ts +++ b/config/auth/session.ts @@ -42,6 +42,7 @@ export async function getLoginSession(req: SessionRequest) { if (!parsed.success) { throw new ServerError('Session is invalid.', 401); } + const { createdAt, maxAge } = parsed.data; const expiresAt = createdAt + maxAge * 1000; diff --git a/getServerSideProps/redirectIfLoggedIn.ts b/getServerSideProps/redirectIfLoggedIn.ts index 61bdfca..5d2b2a7 100644 --- a/getServerSideProps/redirectIfLoggedIn.ts +++ b/getServerSideProps/redirectIfLoggedIn.ts @@ -1,11 +1,21 @@ import { GetServerSideProps, GetServerSidePropsContext, Redirect } from 'next'; import { getLoginSession } from '@/config/auth/session'; +import findUserById from '@/services/User/findUserById'; const redirectIfLoggedIn = (redirect: Redirect) => { const fn: GetServerSideProps = async (context: GetServerSidePropsContext) => { try { const { req } = context; - await getLoginSession(req); + const session = await getLoginSession(req); + + const { id } = session; + + const user = await findUserById(id); + + if (!user) { + throw new Error('Could not get user.'); + } + return { redirect }; } catch { return { props: {} }; diff --git a/pages/api/beer-comments/[id].tsx b/pages/api/beer-comments/[id].tsx new file mode 100644 index 0000000..8a2803d --- /dev/null +++ b/pages/api/beer-comments/[id].tsx @@ -0,0 +1,60 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; +import validateRequest from '@/config/nextConnect/middleware/validateRequest'; +import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; +import ServerError from '@/config/util/ServerError'; +import DBClient from '@/prisma/DBClient'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { createRouter } from 'next-connect'; +import { z } from 'zod'; + +interface DeleteCommentRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} + +const deleteComment = async ( + req: DeleteCommentRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + const user = req.user!; + + const comment = await DBClient.instance.beerComment.findUnique({ + where: { id }, + }); + + if (!comment) { + throw new ServerError('Comment not found', 404); + } + + if (comment.postedById !== user.id) { + throw new ServerError('You are not authorized to delete this comment', 403); + } + + await DBClient.instance.beerComment.delete({ + where: { id }, + }); + + res.status(200).json({ + success: true, + message: 'Comment deleted successfully', + statusCode: 200, + }); +}; + +const router = createRouter< + DeleteCommentRequest, + NextApiResponse> +>(); + +router.delete( + validateRequest({ + querySchema: z.object({ id: z.string().uuid() }), + }), + getCurrentUser, + deleteComment, +); + +const handler = router.handler(NextConnectOptions); +export default handler; diff --git a/pages/api/beers/[id]/index.ts b/pages/api/beers/[id]/index.ts index 407eab6..001b6b7 100644 --- a/pages/api/beers/[id]/index.ts +++ b/pages/api/beers/[id]/index.ts @@ -32,9 +32,7 @@ const editBeerPost = async ( throw new ServerError('You cannot edit that beer post.', 403); } - const updated = await editBeerPostById(id, body); - - console.log(updated); + await editBeerPostById(id, body); res.status(200).json({ message: 'Beer post updated successfully', diff --git a/pages/beers/[id]/edit.tsx b/pages/beers/[id]/edit.tsx index 6341b88..51fa045 100644 --- a/pages/beers/[id]/edit.tsx +++ b/pages/beers/[id]/edit.tsx @@ -7,7 +7,7 @@ import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired'; import getBeerPostById from '@/services/BeerPost/getBeerPostById'; import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult'; import EditBeerPostForm from '@/components/EditBeerPostForm'; -import FormPageLayout from '@/components/ui/forms/BeerPostFormPageLayout'; +import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import { BiBeer } from 'react-icons/bi'; interface EditPageProps { @@ -24,7 +24,12 @@ const EditPage: NextPage = ({ beerPost }) => { - + = ({ commentsPageCount, likeCount, }) => { - const { user } = useContext(UserContext); const [comments, setComments] = useState(beerComments); - const router = useRouter(); - - const commentsPageNum = router.query.comments_page - ? parseInt(router.query.comments_page as string, 10) - : 1; - useEffect(() => { setComments(beerComments); }, [beerComments]); @@ -67,63 +60,16 @@ const BeerByIdPage: NextPage = ({ )}
-
+
-
-
-
-
- {user ? ( - - ) : ( -
- - Log in to leave a comment. - -
- )} -
-
-
- {comments.map((comment) => ( - - ))} - -
-
- - Next Comments - - - Previous Comments - -
-
-
-
-
+
+ +
diff --git a/pages/beers/create.tsx b/pages/beers/create.tsx index 734a5de..6d33d4a 100644 --- a/pages/beers/create.tsx +++ b/pages/beers/create.tsx @@ -1,5 +1,5 @@ import CreateBeerPostForm from '@/components/CreateBeerPostForm'; -import FormPageLayout from '@/components/ui/forms/BeerPostFormPageLayout'; +import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import Layout from '@/components/ui/Layout'; import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired'; import DBClient from '@/prisma/DBClient'; @@ -17,7 +17,12 @@ interface CreateBeerPageProps { const Create: NextPage = ({ breweries, types }) => { return ( - + diff --git a/pages/register/index.tsx b/pages/register/index.tsx index b6e937a..b34c605 100644 --- a/pages/register/index.tsx +++ b/pages/register/index.tsx @@ -1,5 +1,5 @@ import RegisterUserForm from '@/components/RegisterUserForm'; -import FormPageLayout from '@/components/ui/forms/BeerPostFormPageLayout'; +import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import Layout from '@/components/ui/Layout'; import redirectIfLoggedIn from '@/getServerSideProps/redirectIfLoggedIn'; import { NextPage } from 'next'; @@ -13,7 +13,12 @@ const RegisterUserPage: NextPage = () => { Register User - + diff --git a/prisma/seed/create/createNewBeerPosts.ts b/prisma/seed/create/createNewBeerPosts.ts index a63ec70..6b37095 100644 --- a/prisma/seed/create/createNewBeerPosts.ts +++ b/prisma/seed/create/createNewBeerPosts.ts @@ -32,7 +32,7 @@ const createNewBeerPosts = async ({ abv: Math.floor(Math.random() * (12 - 4) + 4), ibu: Math.floor(Math.random() * (60 - 10) + 10), name: faker.commerce.productName(), - description: faker.lorem.lines(42).replace(/(\r\n|\n|\r)/gm, ' '), + description: faker.lorem.lines(12).replace(/(\r\n|\n|\r)/gm, ' '), brewery: { connect: { id: breweryPost.id } }, postedBy: { connect: { id: user.id } }, type: { connect: { id: beerType.id } },