diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml index 4f6e54d..dca0c7e 100644 --- a/.github/workflows/github-actions-demo.yml +++ b/.github/workflows/github-actions-demo.yml @@ -18,8 +18,3 @@ jobs: - name: Install dependencies run: | npm ci - - - name: Prettier Action - # You may pin to the exact commit or the version. - # uses: creyD/prettier_action@6602189cf8bac1ce73ffe601925f6127ab7f21ac - uses: creyD/prettier_action@v4.2 diff --git a/.gitignore b/.gitignore index 49df5aa..f95f7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ next-env.d.ts *.http # uploaded images -public/uploads \ No newline at end of file +public/uploads + +# vscode +.vscode diff --git a/components/BeerById/BeerCommentForm.tsx b/components/BeerById/BeerCommentForm.tsx index 3187ed5..9164279 100644 --- a/components/BeerById/BeerCommentForm.tsx +++ b/components/BeerById/BeerCommentForm.tsx @@ -1,13 +1,16 @@ import sendCreateBeerCommentRequest from '@/requests/sendCreateBeerCommentRequest'; import BeerCommentValidationSchema from '@/services/BeerComment/schema/CreateBeerCommentValidationSchema'; -import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult'; +import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useRouter } from 'next/router'; + import { FunctionComponent, useState, useEffect } from 'react'; import { Rating } from 'react-daisyui'; import { useForm, SubmitHandler } from 'react-hook-form'; import { z } from 'zod'; +import { KeyedMutator } from 'swr'; +import BeerCommentQueryResult from '@/services/BeerComment/schema/BeerCommentQueryResult'; +import { useRouter } from 'next/router'; import Button from '../ui/forms/Button'; import FormError from '../ui/forms/FormError'; import FormInfo from '../ui/forms/FormInfo'; @@ -16,10 +19,17 @@ import FormSegment from '../ui/forms/FormSegment'; import FormTextArea from '../ui/forms/FormTextArea'; interface BeerCommentFormProps { - beerPost: BeerPostQueryResult; + beerPost: z.infer; + mutate: KeyedMutator<{ + comments: z.infer[]; + pageCount: number; + }>; } -const BeerCommentForm: FunctionComponent = ({ beerPost }) => { +const BeerCommentForm: FunctionComponent = ({ + beerPost, + mutate, +}) => { const { register, handleSubmit, formState, reset, setValue } = useForm< z.infer >({ @@ -47,44 +57,58 @@ const BeerCommentForm: FunctionComponent = ({ beerPost }) beerPostId: beerPost.id, }); reset(); - router.replace(`/beers/${beerPost.id}?comments_page=1`, undefined, { scroll: false }); + + const submitTasks: Promise[] = [ + router.push(`/beers/${beerPost.id}`, undefined, { scroll: false }), + mutate(), + ]; + + await Promise.all(submitTasks); }; const { errors } = formState; return ( -
- - Leave a comment - {errors.content?.message} - - - - - - Rating - {errors.rating?.message} - - { - setRating(value); - setValue('rating', value); - }} - > - - - - - - - + +
+ + Leave a comment + {errors.content?.message} + + + + + + Rating + {errors.rating?.message} + + { + setRating(value); + setValue('rating', value); + }} + > + + + + + + +
+ +
+ +
); }; diff --git a/components/BeerById/BeerInfoHeader.tsx b/components/BeerById/BeerInfoHeader.tsx index 58b6b55..44a1936 100644 --- a/components/BeerById/BeerInfoHeader.tsx +++ b/components/BeerById/BeerInfoHeader.tsx @@ -1,42 +1,26 @@ import Link from 'next/link'; -import formatDistanceStrict from 'date-fns/formatDistanceStrict'; import format from 'date-fns/format'; -import { FC, useContext, useEffect, useState } from 'react'; -import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult'; +import { FC, useContext } from 'react'; import UserContext from '@/contexts/userContext'; import { FaRegEdit } from 'react-icons/fa'; +import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import { z } from 'zod'; +import useGetLikeCount from '@/hooks/useGetLikeCount'; +import useTimeDistance from '@/hooks/useTimeDistance'; import BeerPostLikeButton from './BeerPostLikeButton'; -const BeerInfoHeader: FC<{ beerPost: BeerPostQueryResult; initialLikeCount: number }> = ({ - beerPost, - initialLikeCount, -}) => { - const createdAtDate = new Date(beerPost.createdAt); - const [timeDistance, setTimeDistance] = useState(''); +const BeerInfoHeader: FC<{ + beerPost: z.infer; +}> = ({ beerPost }) => { + const createdAt = new Date(beerPost.createdAt); + const timeDistance = useTimeDistance(createdAt); + const { user } = useContext(UserContext); + const idMatches = user && beerPost.postedBy.id === user.id; + const isPostOwner = !!(user && idMatches); - const [likeCount, setLikeCount] = useState(initialLikeCount); - const [isPostOwner, setIsPostOwner] = useState(false); - - useEffect(() => { - const idMatches = user && beerPost.postedBy.id === user.id; - - if (!(user && idMatches)) { - setIsPostOwner(false); - return; - } - - setIsPostOwner(true); - }, [user, beerPost]); - - useEffect(() => { - setLikeCount(initialLikeCount); - }, [initialLikeCount]); - - useEffect(() => { - setTimeDistance(formatDistanceStrict(new Date(beerPost.createdAt), new Date())); - }, [beerPost.createdAt]); + const { likeCount, mutate } = useGetLikeCount(beerPost.id); return (
@@ -67,16 +51,18 @@ const BeerInfoHeader: FC<{ beerPost: BeerPostQueryResult; initialLikeCount: numb

- posted by{' '} + {' posted by '} - {beerPost.postedBy.username} + {`${beerPost.postedBy.username} `} - - {` ${timeDistance}`} ago - + {timeDistance && ( + + {`${timeDistance} ago`} + + )}

{beerPost.description}

@@ -95,15 +81,15 @@ const BeerInfoHeader: FC<{ beerPost: BeerPostQueryResult; initialLikeCount: numb {beerPost.ibu} IBU
- - Liked by {likeCount} user{likeCount !== 1 && 's'} - + {(!!likeCount || likeCount === 0) && ( + + Liked by {likeCount} user{likeCount !== 1 && 's'} + + )}
- {user && ( - - )} + {user && }
diff --git a/components/BeerById/BeerPostCommentsPaginationBar.tsx b/components/BeerById/BeerPostCommentsPaginationBar.tsx index 7f084e0..1925357 100644 --- a/components/BeerById/BeerPostCommentsPaginationBar.tsx +++ b/components/BeerById/BeerPostCommentsPaginationBar.tsx @@ -1,11 +1,14 @@ -import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult'; import { FC } from 'react'; import Link from 'next/link'; +import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import { z } from 'zod'; + +import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'; interface BeerCommentsPaginationBarProps { commentsPageNum: number; commentsPageCount: number; - beerPost: BeerPostQueryResult; + beerPost: z.infer; } const BeerCommentsPaginationBar: FC = ({ @@ -14,9 +17,9 @@ const BeerCommentsPaginationBar: FC = ({ beerPost, }) => (
-
+
= ({ }} scroll={false} > - Next Comments + + = ({ }} scroll={false} > - Previous Comments +
diff --git a/components/BeerById/BeerPostCommentsSection.tsx b/components/BeerById/BeerPostCommentsSection.tsx index a0824ba..1f9d167 100644 --- a/components/BeerById/BeerPostCommentsSection.tsx +++ b/components/BeerById/BeerPostCommentsSection.tsx @@ -1,35 +1,41 @@ +/* eslint-disable no-nested-ternary */ 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 beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; + import { FC, useContext } from 'react'; +import { z } from 'zod'; +import useBeerPostComments from '@/hooks/useBeerPostComments'; +import { useRouter } from 'next/router'; import BeerCommentForm from './BeerCommentForm'; import BeerCommentsPaginationBar from './BeerPostCommentsPaginationBar'; -import CommentCard from './CommentCard'; +import CommentCardBody from './CommentCardBody'; +import NoCommentsCard from './NoCommentsCard'; +import CommentLoadingCardBody from './CommentLoadingCardBody'; interface BeerPostCommentsSectionProps { - beerPost: BeerPostQueryResult; - comments: BeerCommentQueryResultArrayT; - commentsPageCount: number; + beerPost: z.infer; } -const BeerPostCommentsSection: FC = ({ - beerPost, - - comments, - commentsPageCount, -}) => { +const BeerPostCommentsSection: FC = ({ beerPost }) => { const { user } = useContext(UserContext); const router = useRouter(); + const { id } = beerPost; + const pageNum = parseInt(router.query.comments_page as string, 10) || 1; + const pageSize = 5; - const commentsPageNum = parseInt(router.query.comments_page as string, 10) || 1; + const { comments, commentsPageCount, isLoading, mutate } = useBeerPostComments({ + id, + pageNum, + pageSize, + }); return (
{user ? ( - + ) : (
Log in to leave a comment. @@ -37,23 +43,34 @@ const BeerPostCommentsSection: FC = ({ )}
- {comments.length ? ( + + {comments && !!comments.length && !!commentsPageCount && !isLoading && (
{comments.map((comment) => ( - + ))}
- ) : ( -
-
- No comments yet. -
+ )} + + {!comments?.length && !isLoading && } + + {isLoading && ( +
+ {Array.from({ length: pageSize }).map((_, i) => ( + + ))} + +
)}
diff --git a/components/BeerById/BeerPostLikeButton.tsx b/components/BeerById/BeerPostLikeButton.tsx index 84b6ae9..07311ad 100644 --- a/components/BeerById/BeerPostLikeButton.tsx +++ b/components/BeerById/BeerPostLikeButton.tsx @@ -1,41 +1,28 @@ -import UserContext from '@/contexts/userContext'; +import useCheckIfUserLikesBeerPost from '@/hooks/useCheckIfUserLikesBeerPost'; import sendLikeRequest from '@/requests/sendLikeRequest'; -import { Dispatch, FC, SetStateAction, useContext, useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import { FaThumbsUp, FaRegThumbsUp } from 'react-icons/fa'; -import sendCheckIfUserLikesBeerPostRequest from '@/requests/sendCheckIfUserLikesBeerPostRequest'; +import { KeyedMutator } from 'swr'; const BeerPostLikeButton: FC<{ beerPostId: string; - setLikeCount: Dispatch>; -}> = ({ beerPostId, setLikeCount }) => { + mutateCount: KeyedMutator; +}> = ({ beerPostId, mutateCount }) => { + const { isLiked, mutate: mutateLikeStatus } = useCheckIfUserLikesBeerPost(beerPostId); const [loading, setLoading] = useState(true); - const [isLiked, setIsLiked] = useState(false); - - const { user } = useContext(UserContext); useEffect(() => { - if (!user) { - setLoading(false); - return; - } - sendCheckIfUserLikesBeerPostRequest(beerPostId) - .then((currentLikeStatus) => { - setIsLiked(currentLikeStatus); - setLoading(false); - }) - .catch(() => { - setLoading(false); - }); - }, [user, beerPostId]); + setLoading(false); + }, [isLiked]); const handleLike = async () => { try { setLoading(true); await sendLikeRequest(beerPostId); - setIsLiked(!isLiked); - setLikeCount((prevCount) => prevCount + (isLiked ? -1 : 1)); + await mutateCount(); + await mutateLikeStatus(); setLoading(false); - } catch (error) { + } catch (e) { setLoading(false); } }; diff --git a/components/BeerById/BeerRecommendations.tsx b/components/BeerById/BeerRecommendations.tsx index 1bda780..e849488 100644 --- a/components/BeerById/BeerRecommendations.tsx +++ b/components/BeerById/BeerRecommendations.tsx @@ -10,7 +10,7 @@ const BeerRecommendations: FunctionComponent = ({ }) => { return (
-
+
{beerRecommendations.map((beerPost) => (
diff --git a/components/BeerById/CommentCard.tsx b/components/BeerById/CommentCardBody.tsx similarity index 67% rename from components/BeerById/CommentCard.tsx rename to components/BeerById/CommentCardBody.tsx index 5784ce0..d9864d0 100644 --- a/components/BeerById/CommentCard.tsx +++ b/components/BeerById/CommentCardBody.tsx @@ -1,18 +1,22 @@ import UserContext from '@/contexts/userContext'; -import { BeerCommentQueryResultT } from '@/services/BeerComment/schema/BeerCommentQueryResult'; -import { format, formatDistanceStrict } from 'date-fns'; +import useTimeDistance from '@/hooks/useTimeDistance'; +import BeerCommentQueryResult from '@/services/BeerComment/schema/BeerCommentQueryResult'; +import format from 'date-fns/format'; import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { useContext, useEffect, useState } from 'react'; +import { useContext } from 'react'; import { Rating } from 'react-daisyui'; import { FaEllipsisH } from 'react-icons/fa'; +import { KeyedMutator } from 'swr'; +import { z } from 'zod'; const CommentCardDropdown: React.FC<{ - comment: BeerCommentQueryResultT; - beerPostId: string; -}> = ({ comment, beerPostId }) => { - const router = useRouter(); + comment: z.infer; + mutate: KeyedMutator<{ + comments: z.infer[]; + pageCount: number; + }>; +}> = ({ comment, mutate }) => { const { user } = useContext(UserContext); const isCommentOwner = user?.id === comment.postedBy.id; @@ -21,16 +25,17 @@ const CommentCardDropdown: React.FC<{ 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 }); + await mutate(); }; return (
-
-
-