From f6880deeb6aa4f107e436974477b40fdcdb01447 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Wed, 8 Feb 2023 07:43:59 -0500 Subject: [PATCH 1/6] add user context and likes --- components/BeerById/BeerInfoHeader.tsx | 113 ++++++++++++++---- components/ui/forms/FormTextArea.tsx | 5 + components/ui/forms/FormTextInput.tsx | 5 + config/nextConnect/NextConnectConfig.ts | 3 +- pages/_app.tsx | 10 +- pages/api/beers/[id]/like/index.ts | 72 +++++++++++ pages/api/beers/[id]/like/is-liked.ts | 45 +++++++ pages/beers/[id].tsx | 19 ++- pages/contexts/userContext.ts | 11 ++ pages/user/current.tsx | 6 +- .../migrations/20230208021759_/migration.sql | 16 +++ prisma/schema.prisma | 42 ++++--- 12 files changed, 300 insertions(+), 47 deletions(-) create mode 100644 pages/api/beers/[id]/like/index.ts create mode 100644 pages/api/beers/[id]/like/is-liked.ts create mode 100644 pages/contexts/userContext.ts create mode 100644 prisma/migrations/20230208021759_/migration.sql diff --git a/components/BeerById/BeerInfoHeader.tsx b/components/BeerById/BeerInfoHeader.tsx index 4b21cdf..8d1f2b9 100644 --- a/components/BeerById/BeerInfoHeader.tsx +++ b/components/BeerById/BeerInfoHeader.tsx @@ -1,20 +1,84 @@ import Link from 'next/link'; import formatDistanceStrict from 'date-fns/formatDistanceStrict'; import format from 'date-fns/format'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { FaRegThumbsUp, FaThumbsUp } from 'react-icons/fa'; import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import UserContext from '@/pages/contexts/userContext'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost }) => { const createdAtDate = new Date(beerPost.createdAt); const [timeDistance, setTimeDistance] = useState(''); - + const { user } = useContext(UserContext); useEffect(() => { setTimeDistance(formatDistanceStrict(new Date(beerPost.createdAt), new Date())); }, [beerPost.createdAt]); + const [loading, setLoading] = useState(true); const [isLiked, setIsLiked] = useState(false); + useEffect(() => { + if (!user) { + setLoading(false); + return; + } + + fetch(`/api/beers/${beerPost.id}/like/is-liked`) + .then((response) => response.json()) + .then((data) => { + const parsed = APIResponseValidationSchema.safeParse(data); + + if (!parsed.success) { + throw new Error('Invalid API response.'); + } + const { payload } = parsed.data; + + const parsedPayload = z + .object({ + isLiked: z.boolean(), + }) + .safeParse(payload); + + if (!parsedPayload.success) { + throw new Error('Invalid API response payload.'); + } + + const { isLiked: alreadyLiked } = parsedPayload.data; + + setIsLiked(alreadyLiked); + setLoading(false); + }); + }, [user, beerPost.id]); + + const handleLike = async () => { + const response = await fetch(`/api/beers/${beerPost.id}/like`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: '', + }); + + if (!response.ok) { + throw new Error('Something went wrong.'); + } + + const data = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(data); + + if (!parsed.success) { + throw new Error('Invalid API response.'); + } + + const { success, message } = parsed.data; + + setIsLiked(!isLiked); + console.log({ success, message }); + }; + return (
@@ -59,27 +123,30 @@ const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost
- + {user && ( + + )}
diff --git a/components/ui/forms/FormTextArea.tsx b/components/ui/forms/FormTextArea.tsx index 786952d..9101455 100644 --- a/components/ui/forms/FormTextArea.tsx +++ b/components/ui/forms/FormTextArea.tsx @@ -7,6 +7,7 @@ interface FormTextAreaProps { error: boolean; id: string; rows: number; + disabled?: boolean; } /** @@ -17,6 +18,7 @@ interface FormTextAreaProps { * error={true} * placeholder="Test" * rows={5} + * disabled * />; * * @param props @@ -25,6 +27,7 @@ interface FormTextAreaProps { * @param props.error Whether or not the textarea has an error. * @param props.id The id of the textarea. * @param props.rows The number of rows to display in the textarea. + * @param props.disabled Whether or not the textarea is disabled. */ const FormTextArea: FunctionComponent = ({ placeholder = '', @@ -32,6 +35,7 @@ const FormTextArea: FunctionComponent = ({ error, id, rows, + disabled = false, }) => (