From 386556bb41e8940872b6d5f0fbc08fa6d755daa9 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 18 Dec 2023 13:24:22 -0500 Subject: [PATCH 01/13] Refactor paginated query response schema --- src/pages/api/beers/[id]/comments/index.ts | 7 ++----- src/pages/api/beers/[id]/recommendations.ts | 7 ++----- src/pages/api/beers/styles/[id]/beers/index.ts | 7 ++----- src/pages/api/beers/styles/[id]/comments/index.ts | 7 ++----- src/pages/api/breweries/[id]/beers/index.ts | 7 ++----- src/pages/api/breweries/[id]/comments/index.ts | 7 ++----- src/pages/api/users/[id]/followers.ts | 7 ++----- src/pages/api/users/[id]/following.ts | 7 ++----- src/services/schema/PaginatedQueryResponseSchema.ts | 4 ++-- 9 files changed, 18 insertions(+), 42 deletions(-) diff --git a/src/pages/api/beers/[id]/comments/index.ts b/src/pages/api/beers/[id]/comments/index.ts index f9bc088..1efb734 100644 --- a/src/pages/api/beers/[id]/comments/index.ts +++ b/src/pages/api/beers/[id]/comments/index.ts @@ -11,6 +11,7 @@ import { createBeerPostComment, getAllBeerPostComments, } from '@/controllers/comments/beer-comments'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; const router = createRouter< // @TODO: Fix this any type @@ -29,11 +30,7 @@ router.post( router.get( validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - page_size: z.coerce.number().int().positive(), - page_num: z.coerce.number().int().positive(), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getAllBeerPostComments, ); diff --git a/src/pages/api/beers/[id]/recommendations.ts b/src/pages/api/beers/[id]/recommendations.ts index 15b5d36..a3f6f92 100644 --- a/src/pages/api/beers/[id]/recommendations.ts +++ b/src/pages/api/beers/[id]/recommendations.ts @@ -2,6 +2,7 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { getBeerPostRecommendations } from '@/controllers/posts/beer-posts'; import { GetBeerRecommendationsRequest } from '@/controllers/posts/beer-posts/types'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; @@ -14,11 +15,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - page_num: z.string().regex(/^[0-9]+$/), - page_size: z.string().regex(/^[0-9]+$/), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getBeerPostRecommendations, ); diff --git a/src/pages/api/beers/styles/[id]/beers/index.ts b/src/pages/api/beers/styles/[id]/beers/index.ts index 5d89585..7da569f 100644 --- a/src/pages/api/beers/styles/[id]/beers/index.ts +++ b/src/pages/api/beers/styles/[id]/beers/index.ts @@ -1,6 +1,7 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { getAllBeersByBeerStyle } from '@/controllers/posts/beer-styles'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiRequest, NextApiResponse } from 'next'; @@ -18,11 +19,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: z.object({ - page_size: z.string().min(1), - page_num: z.string().min(1), - id: z.string().min(1), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getAllBeersByBeerStyle, ); diff --git a/src/pages/api/beers/styles/[id]/comments/index.ts b/src/pages/api/beers/styles/[id]/comments/index.ts index 174d413..b8e9b8c 100644 --- a/src/pages/api/beers/styles/[id]/comments/index.ts +++ b/src/pages/api/beers/styles/[id]/comments/index.ts @@ -8,6 +8,7 @@ import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { NextApiResponse } from 'next'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import { createComment, getAll } from '@/controllers/comments/beer-style-comments'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; const router = createRouter< // I don't want to use any, but I can't figure out how to get the types to work @@ -26,11 +27,7 @@ router.post( router.get( validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - page_size: z.coerce.number().int().positive(), - page_num: z.coerce.number().int().positive(), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getAll, ); diff --git a/src/pages/api/breweries/[id]/beers/index.ts b/src/pages/api/breweries/[id]/beers/index.ts index c50ebe4..b3a51f0 100644 --- a/src/pages/api/breweries/[id]/beers/index.ts +++ b/src/pages/api/breweries/[id]/beers/index.ts @@ -2,6 +2,7 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { getAllBeersByBrewery } from '@/controllers/posts/breweries'; import { GetAllPostsByConnectedPostId } from '@/controllers/posts/types'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; @@ -14,11 +15,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: z.object({ - page_size: z.string().nonempty(), - page_num: z.string().nonempty(), - id: z.string().nonempty(), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getAllBeersByBrewery, ); diff --git a/src/pages/api/breweries/[id]/comments/index.ts b/src/pages/api/breweries/[id]/comments/index.ts index 2e0175d..338ff16 100644 --- a/src/pages/api/breweries/[id]/comments/index.ts +++ b/src/pages/api/breweries/[id]/comments/index.ts @@ -9,6 +9,7 @@ import { NextApiResponse } from 'next'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import { createComment, getAll } from '@/controllers/comments/brewery-comments'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; const router = createRouter< // I don't want to use any, but I can't figure out how to get the types to work @@ -27,11 +28,7 @@ router.post( router.get( validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - page_size: z.coerce.number().int().positive(), - page_num: z.coerce.number().int().positive(), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getAll, ); diff --git a/src/pages/api/users/[id]/followers.ts b/src/pages/api/users/[id]/followers.ts index 761e1a3..5eaf28f 100644 --- a/src/pages/api/users/[id]/followers.ts +++ b/src/pages/api/users/[id]/followers.ts @@ -2,6 +2,7 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { getUserFollowers } from '@/controllers/users/profile'; import { GetUserFollowInfoRequest } from '@/controllers/users/profile/types'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; @@ -15,11 +16,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - page_size: z.string().regex(/^\d+$/), - page_num: z.string().regex(/^\d+$/), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getUserFollowers, ); diff --git a/src/pages/api/users/[id]/following.ts b/src/pages/api/users/[id]/following.ts index 9e99111..55ed2c9 100644 --- a/src/pages/api/users/[id]/following.ts +++ b/src/pages/api/users/[id]/following.ts @@ -2,6 +2,7 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { getUsersFollowed } from '@/controllers/users/profile'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; @@ -19,11 +20,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - page_size: z.string().regex(/^\d+$/), - page_num: z.string().regex(/^\d+$/), - }), + querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), }), getUsersFollowed, ); diff --git a/src/services/schema/PaginatedQueryResponseSchema.ts b/src/services/schema/PaginatedQueryResponseSchema.ts index 22acabc..a9b880f 100644 --- a/src/services/schema/PaginatedQueryResponseSchema.ts +++ b/src/services/schema/PaginatedQueryResponseSchema.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; const PaginatedQueryResponseSchema = z.object({ - page_num: z.string(), - page_size: z.string(), + page_num: z.string().regex(/^\d+$/), + page_size: z.string().regex(/^\d+$/), }); export default PaginatedQueryResponseSchema; From e3eeb8cf460e9968d6a1c8a27e7725c4080bd7af Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 18 Dec 2023 16:43:53 -0500 Subject: [PATCH 02/13] Restructure requests directory --- src/components/Account/AccountInfo.tsx | 6 +++--- src/components/Account/Security.tsx | 2 +- src/components/BeerById/BeerCommentForm.tsx | 2 +- src/components/BeerById/BeerPostLikeButton.tsx | 2 +- src/components/BeerStyleById/BeerStyleCommentForm.tsx | 2 +- src/components/BeerStyleById/BeerStyleLikeButton.tsx | 2 +- src/components/BreweryById/BreweryCommentForm.tsx | 2 +- src/components/BreweryIndex/BreweryPostLikeButton.tsx | 2 +- src/components/BreweryPost/CreateBreweryPostForm.tsx | 4 ++-- src/components/CreateBeerPostForm.tsx | 4 ++-- src/components/EditBeerPostForm.tsx | 4 ++-- src/components/Login/LoginForm.tsx | 2 +- src/components/RegisterUserForm.tsx | 2 +- src/components/UserPage/UserFollowButton.tsx | 2 +- src/pages/users/account/edit-profile.tsx | 4 ++-- src/pages/users/forgot-password.tsx | 2 +- .../beer-comment}/sendCreateBeerCommentRequest.ts | 0 .../sendCreateBeerStyleCommentRequest.ts | 0 .../brewery-comment}/sendCreateBreweryCommentRequest.ts | 0 .../beer-image}/sendUploadBeerImageRequest.ts | 0 .../brewery-image}/sendUploadBreweryImageRequest.ts | 0 .../beer-post-like}/sendBeerPostLikeRequest.ts | 0 .../beer-style-like}/sendBeerStyleLikeRequest.ts | 0 .../brewery-post-like}/sendBreweryPostLikeRequest.ts | 0 .../{BeerPost => posts/beer-post}/deleteBeerPostRequest.ts | 0 .../beer-post}/sendCreateBeerPostRequest.ts | 0 .../beer-post}/sendEditBeerPostRequest.ts | 0 .../brewery-post}/sendCreateBreweryPostRequest.ts | 0 src/requests/{User => users/auth}/sendEditUserRequest.ts | 0 .../{User => users/auth}/sendForgotPasswordRequest.ts | 0 src/requests/{User => users/auth}/sendLoginUserRequest.ts | 0 .../{User => users/auth}/sendRegisterUserRequest.ts | 0 .../{User => users/auth}/sendUpdatePasswordRequest.ts | 0 .../{UserFollow => users/auth}/sendUserFollowRequest.ts | 0 src/requests/{User => users/auth}/validateEmailRequest.ts | 0 .../profile}/sendUpdateUserAvatarRequest.ts | 0 .../profile}/sendUpdateUserProfileRequest.ts | 0 src/requests/{ => users/profile}/validateUsernameRequest.ts | 0 .../users/auth/schema/CreateUserValidationSchemas.ts | 4 ++-- 39 files changed, 24 insertions(+), 24 deletions(-) rename src/requests/{BeerComment => comments/beer-comment}/sendCreateBeerCommentRequest.ts (100%) rename src/requests/{BeerStyleComment => comments/beer-style-comment}/sendCreateBeerStyleCommentRequest.ts (100%) rename src/requests/{BreweryComment => comments/brewery-comment}/sendCreateBreweryCommentRequest.ts (100%) rename src/requests/{BeerImage => images/beer-image}/sendUploadBeerImageRequest.ts (100%) rename src/requests/{BreweryImage => images/brewery-image}/sendUploadBreweryImageRequest.ts (100%) rename src/requests/{BeerPostLike => likes/beer-post-like}/sendBeerPostLikeRequest.ts (100%) rename src/requests/{BeerStyleLike => likes/beer-style-like}/sendBeerStyleLikeRequest.ts (100%) rename src/requests/{BreweryPostLike => likes/brewery-post-like}/sendBreweryPostLikeRequest.ts (100%) rename src/requests/{BeerPost => posts/beer-post}/deleteBeerPostRequest.ts (100%) rename src/requests/{BeerPost => posts/beer-post}/sendCreateBeerPostRequest.ts (100%) rename src/requests/{BeerPost => posts/beer-post}/sendEditBeerPostRequest.ts (100%) rename src/requests/{BreweryPost => posts/brewery-post}/sendCreateBreweryPostRequest.ts (100%) rename src/requests/{User => users/auth}/sendEditUserRequest.ts (100%) rename src/requests/{User => users/auth}/sendForgotPasswordRequest.ts (100%) rename src/requests/{User => users/auth}/sendLoginUserRequest.ts (100%) rename src/requests/{User => users/auth}/sendRegisterUserRequest.ts (100%) rename src/requests/{User => users/auth}/sendUpdatePasswordRequest.ts (100%) rename src/requests/{UserFollow => users/auth}/sendUserFollowRequest.ts (100%) rename src/requests/{User => users/auth}/validateEmailRequest.ts (100%) rename src/requests/{Account => users/profile}/sendUpdateUserAvatarRequest.ts (100%) rename src/requests/{Account => users/profile}/sendUpdateUserProfileRequest.ts (100%) rename src/requests/{ => users/profile}/validateUsernameRequest.ts (100%) diff --git a/src/components/Account/AccountInfo.tsx b/src/components/Account/AccountInfo.tsx index 63cf7c5..9eaf183 100644 --- a/src/components/Account/AccountInfo.tsx +++ b/src/components/Account/AccountInfo.tsx @@ -1,5 +1,5 @@ -import validateEmailRequest from '@/requests/User/validateEmailRequest'; -import validateUsernameRequest from '@/requests/validateUsernameRequest'; +import validateEmailRequest from '@/requests/users/auth/validateEmailRequest'; +import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest'; import { BaseCreateUserSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { Switch } from '@headlessui/react'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -7,7 +7,7 @@ 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 sendEditUserRequest from '@/requests/users/auth/sendEditUserRequest'; import createErrorToast from '@/util/createErrorToast'; import { toast } from 'react-hot-toast'; import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer'; diff --git a/src/components/Account/Security.tsx b/src/components/Account/Security.tsx index f9e0b79..7ed1bbf 100644 --- a/src/components/Account/Security.tsx +++ b/src/components/Account/Security.tsx @@ -4,7 +4,7 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; -import sendUpdatePasswordRequest from '@/requests/User/sendUpdatePasswordRequest'; +import sendUpdatePasswordRequest from '@/requests/users/auth/sendUpdatePasswordRequest'; import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer'; import toast from 'react-hot-toast'; import createErrorToast from '@/util/createErrorToast'; diff --git a/src/components/BeerById/BeerCommentForm.tsx b/src/components/BeerById/BeerCommentForm.tsx index ce7d752..aef448b 100644 --- a/src/components/BeerById/BeerCommentForm.tsx +++ b/src/components/BeerById/BeerCommentForm.tsx @@ -1,4 +1,4 @@ -import sendCreateBeerCommentRequest from '@/requests/BeerComment/sendCreateBeerCommentRequest'; +import sendCreateBeerCommentRequest from '@/requests/comments/beer-comment/sendCreateBeerCommentRequest'; import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { zodResolver } from '@hookform/resolvers/zod'; diff --git a/src/components/BeerById/BeerPostLikeButton.tsx b/src/components/BeerById/BeerPostLikeButton.tsx index 4ecad09..5ab4fdc 100644 --- a/src/components/BeerById/BeerPostLikeButton.tsx +++ b/src/components/BeerById/BeerPostLikeButton.tsx @@ -3,7 +3,7 @@ import useCheckIfUserLikesBeerPost from '@/hooks/data-fetching/beer-likes/useChe import { FC, useEffect, useState } from 'react'; import useGetBeerPostLikeCount from '@/hooks/data-fetching/beer-likes/useBeerPostLikeCount'; -import sendBeerPostLikeRequest from '@/requests/BeerPostLike/sendBeerPostLikeRequest'; +import sendBeerPostLikeRequest from '@/requests/likes/beer-post-like/sendBeerPostLikeRequest'; import LikeButton from '../ui/LikeButton'; const BeerPostLikeButton: FC<{ diff --git a/src/components/BeerStyleById/BeerStyleCommentForm.tsx b/src/components/BeerStyleById/BeerStyleCommentForm.tsx index 7e9f0e4..7ab0df9 100644 --- a/src/components/BeerStyleById/BeerStyleCommentForm.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentForm.tsx @@ -10,7 +10,7 @@ import createErrorToast from '@/util/createErrorToast'; import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; -import sendCreateBeerStyleCommentRequest from '@/requests/BeerStyleComment/sendCreateBeerStyleCommentRequest'; +import sendCreateBeerStyleCommentRequest from '@/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest'; import CommentForm from '../ui/CommentForm'; interface BeerCommentFormProps { diff --git a/src/components/BeerStyleById/BeerStyleLikeButton.tsx b/src/components/BeerStyleById/BeerStyleLikeButton.tsx index 0d31c14..1e9f841 100644 --- a/src/components/BeerStyleById/BeerStyleLikeButton.tsx +++ b/src/components/BeerStyleById/BeerStyleLikeButton.tsx @@ -2,7 +2,7 @@ import { FC, useEffect, useState } from 'react'; import useGetBeerPostLikeCount from '@/hooks/data-fetching/beer-likes/useBeerPostLikeCount'; import useCheckIfUserLikesBeerStyle from '@/hooks/data-fetching/beer-style-likes/useCheckIfUserLikesBeerPost'; -import sendBeerStyleLikeRequest from '@/requests/BeerStyleLike/sendBeerStyleLikeRequest'; +import sendBeerStyleLikeRequest from '@/requests/likes/beer-style-like/sendBeerStyleLikeRequest'; import LikeButton from '../ui/LikeButton'; const BeerStyleLikeButton: FC<{ diff --git a/src/components/BreweryById/BreweryCommentForm.tsx b/src/components/BreweryById/BreweryCommentForm.tsx index 162302c..a42ada7 100644 --- a/src/components/BreweryById/BreweryCommentForm.tsx +++ b/src/components/BreweryById/BreweryCommentForm.tsx @@ -6,7 +6,7 @@ import { FC } from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; import toast from 'react-hot-toast'; import { z } from 'zod'; -import sendCreateBreweryCommentRequest from '@/requests/BreweryComment/sendCreateBreweryCommentRequest'; +import sendCreateBreweryCommentRequest from '@/requests/comments/brewery-comment/sendCreateBreweryCommentRequest'; import createErrorToast from '@/util/createErrorToast'; import CommentForm from '../ui/CommentForm'; diff --git a/src/components/BreweryIndex/BreweryPostLikeButton.tsx b/src/components/BreweryIndex/BreweryPostLikeButton.tsx index c52c263..46534ec 100644 --- a/src/components/BreweryIndex/BreweryPostLikeButton.tsx +++ b/src/components/BreweryIndex/BreweryPostLikeButton.tsx @@ -1,6 +1,6 @@ import useCheckIfUserLikesBreweryPost from '@/hooks/data-fetching/brewery-likes/useCheckIfUserLikesBreweryPost'; import useGetBreweryPostLikeCount from '@/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount'; -import sendBreweryPostLikeRequest from '@/requests/BreweryPostLike/sendBreweryPostLikeRequest'; +import sendBreweryPostLikeRequest from '@/requests/likes/brewery-post-like/sendBreweryPostLikeRequest'; import { FC, useState } from 'react'; import LikeButton from '../ui/LikeButton'; diff --git a/src/components/BreweryPost/CreateBreweryPostForm.tsx b/src/components/BreweryPost/CreateBreweryPostForm.tsx index 4b1d2b6..7a7aaae 100644 --- a/src/components/BreweryPost/CreateBreweryPostForm.tsx +++ b/src/components/BreweryPost/CreateBreweryPostForm.tsx @@ -1,5 +1,5 @@ -import sendUploadBreweryImagesRequest from '@/requests/BreweryImage/sendUploadBreweryImageRequest'; -import sendCreateBreweryPostRequest from '@/requests/BreweryPost/sendCreateBreweryPostRequest'; +import sendUploadBreweryImagesRequest from '@/requests/images/brewery-image/sendUploadBreweryImageRequest'; +import sendCreateBreweryPostRequest from '@/requests/posts/brewery-post/sendCreateBreweryPostRequest'; import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; import UploadImageValidationSchema from '@/services/schema/ImageSchema/UploadImageValidationSchema'; import createErrorToast from '@/util/createErrorToast'; diff --git a/src/components/CreateBeerPostForm.tsx b/src/components/CreateBeerPostForm.tsx index fdba62f..42bea2e 100644 --- a/src/components/CreateBeerPostForm.tsx +++ b/src/components/CreateBeerPostForm.tsx @@ -6,9 +6,9 @@ import { useForm, SubmitHandler, FieldError } from 'react-hook-form'; import { z } from 'zod'; import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; -import sendCreateBeerPostRequest from '@/requests/BeerPost/sendCreateBeerPostRequest'; +import sendCreateBeerPostRequest from '@/requests/posts/beer-post/sendCreateBeerPostRequest'; import UploadImageValidationSchema from '@/services/schema/ImageSchema/UploadImageValidationSchema'; -import sendUploadBeerImagesRequest from '@/requests/BeerImage/sendUploadBeerImageRequest'; +import sendUploadBeerImagesRequest from '@/requests/images/beer-image/sendUploadBeerImageRequest'; import toast from 'react-hot-toast'; diff --git a/src/components/EditBeerPostForm.tsx b/src/components/EditBeerPostForm.tsx index 63727a8..3f6ed0b 100644 --- a/src/components/EditBeerPostForm.tsx +++ b/src/components/EditBeerPostForm.tsx @@ -6,9 +6,9 @@ import { z } from 'zod'; import { useForm, SubmitHandler } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import deleteBeerPostRequest from '@/requests/BeerPost/deleteBeerPostRequest'; +import deleteBeerPostRequest from '@/requests/posts/beer-post/deleteBeerPostRequest'; import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; -import sendEditBeerPostRequest from '@/requests/BeerPost/sendEditBeerPostRequest'; +import sendEditBeerPostRequest from '@/requests/posts/beer-post/sendEditBeerPostRequest'; import createErrorToast from '@/util/createErrorToast'; import Button from './ui/forms/Button'; import FormError from './ui/forms/FormError'; diff --git a/src/components/Login/LoginForm.tsx b/src/components/Login/LoginForm.tsx index 1c7fec9..f79bc0d 100644 --- a/src/components/Login/LoginForm.tsx +++ b/src/components/Login/LoginForm.tsx @@ -1,4 +1,4 @@ -import sendLoginUserRequest from '@/requests/User/sendLoginUserRequest'; +import sendLoginUserRequest from '@/requests/users/auth/sendLoginUserRequest'; import LoginValidationSchema from '@/services/users/auth/schema/LoginValidationSchema'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/router'; diff --git a/src/components/RegisterUserForm.tsx b/src/components/RegisterUserForm.tsx index 588338e..4970bbb 100644 --- a/src/components/RegisterUserForm.tsx +++ b/src/components/RegisterUserForm.tsx @@ -1,4 +1,4 @@ -import sendRegisterUserRequest from '@/requests/User/sendRegisterUserRequest'; +import sendRegisterUserRequest from '@/requests/users/auth/sendRegisterUserRequest'; import { CreateUserValidationSchemaWithUsernameAndEmailCheck } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/router'; diff --git a/src/components/UserPage/UserFollowButton.tsx b/src/components/UserPage/UserFollowButton.tsx index 1f1dc58..0b0166c 100644 --- a/src/components/UserPage/UserFollowButton.tsx +++ b/src/components/UserPage/UserFollowButton.tsx @@ -1,7 +1,7 @@ import useFollowStatus from '@/hooks/data-fetching/user-follows/useFollowStatus'; import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser'; import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; -import sendUserFollowRequest from '@/requests/UserFollow/sendUserFollowRequest'; +import sendUserFollowRequest from '@/requests/users/auth/sendUserFollowRequest'; import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import { FC, useState } from 'react'; import { FaUserCheck, FaUserPlus } from 'react-icons/fa'; diff --git a/src/pages/users/account/edit-profile.tsx b/src/pages/users/account/edit-profile.tsx index 70703c5..1dc0a1b 100644 --- a/src/pages/users/account/edit-profile.tsx +++ b/src/pages/users/account/edit-profile.tsx @@ -22,8 +22,8 @@ import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGet import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; import UpdateProfileSchema from '@/services/users/auth/schema/UpdateProfileSchema'; -import sendUpdateUserAvatarRequest from '@/requests/Account/sendUpdateUserAvatarRequest'; -import sendUpdateUserProfileRequest from '@/requests/Account/sendUpdateUserProfileRequest'; +import sendUpdateUserAvatarRequest from '@/requests/users/profile/sendUpdateUserAvatarRequest'; +import sendUpdateUserProfileRequest from '@/requests/users/profile/sendUpdateUserProfileRequest'; import Spinner from '@/components/ui/Spinner'; const ProfilePage: NextPage = () => { diff --git a/src/pages/users/forgot-password.tsx b/src/pages/users/forgot-password.tsx index a68adc3..d27ca0a 100644 --- a/src/pages/users/forgot-password.tsx +++ b/src/pages/users/forgot-password.tsx @@ -12,7 +12,7 @@ import { NextPage } from 'next'; import { SubmitHandler, useForm } from 'react-hook-form'; import toast from 'react-hot-toast'; import { useRouter } from 'next/router'; -import sendForgotPasswordRequest from '@/requests/User/sendForgotPasswordRequest'; +import sendForgotPasswordRequest from '@/requests/users/auth/sendForgotPasswordRequest'; import { FaUserCircle } from 'react-icons/fa'; interface ForgotPasswordPageProps {} diff --git a/src/requests/BeerComment/sendCreateBeerCommentRequest.ts b/src/requests/comments/beer-comment/sendCreateBeerCommentRequest.ts similarity index 100% rename from src/requests/BeerComment/sendCreateBeerCommentRequest.ts rename to src/requests/comments/beer-comment/sendCreateBeerCommentRequest.ts diff --git a/src/requests/BeerStyleComment/sendCreateBeerStyleCommentRequest.ts b/src/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest.ts similarity index 100% rename from src/requests/BeerStyleComment/sendCreateBeerStyleCommentRequest.ts rename to src/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest.ts diff --git a/src/requests/BreweryComment/sendCreateBreweryCommentRequest.ts b/src/requests/comments/brewery-comment/sendCreateBreweryCommentRequest.ts similarity index 100% rename from src/requests/BreweryComment/sendCreateBreweryCommentRequest.ts rename to src/requests/comments/brewery-comment/sendCreateBreweryCommentRequest.ts diff --git a/src/requests/BeerImage/sendUploadBeerImageRequest.ts b/src/requests/images/beer-image/sendUploadBeerImageRequest.ts similarity index 100% rename from src/requests/BeerImage/sendUploadBeerImageRequest.ts rename to src/requests/images/beer-image/sendUploadBeerImageRequest.ts diff --git a/src/requests/BreweryImage/sendUploadBreweryImageRequest.ts b/src/requests/images/brewery-image/sendUploadBreweryImageRequest.ts similarity index 100% rename from src/requests/BreweryImage/sendUploadBreweryImageRequest.ts rename to src/requests/images/brewery-image/sendUploadBreweryImageRequest.ts diff --git a/src/requests/BeerPostLike/sendBeerPostLikeRequest.ts b/src/requests/likes/beer-post-like/sendBeerPostLikeRequest.ts similarity index 100% rename from src/requests/BeerPostLike/sendBeerPostLikeRequest.ts rename to src/requests/likes/beer-post-like/sendBeerPostLikeRequest.ts diff --git a/src/requests/BeerStyleLike/sendBeerStyleLikeRequest.ts b/src/requests/likes/beer-style-like/sendBeerStyleLikeRequest.ts similarity index 100% rename from src/requests/BeerStyleLike/sendBeerStyleLikeRequest.ts rename to src/requests/likes/beer-style-like/sendBeerStyleLikeRequest.ts diff --git a/src/requests/BreweryPostLike/sendBreweryPostLikeRequest.ts b/src/requests/likes/brewery-post-like/sendBreweryPostLikeRequest.ts similarity index 100% rename from src/requests/BreweryPostLike/sendBreweryPostLikeRequest.ts rename to src/requests/likes/brewery-post-like/sendBreweryPostLikeRequest.ts diff --git a/src/requests/BeerPost/deleteBeerPostRequest.ts b/src/requests/posts/beer-post/deleteBeerPostRequest.ts similarity index 100% rename from src/requests/BeerPost/deleteBeerPostRequest.ts rename to src/requests/posts/beer-post/deleteBeerPostRequest.ts diff --git a/src/requests/BeerPost/sendCreateBeerPostRequest.ts b/src/requests/posts/beer-post/sendCreateBeerPostRequest.ts similarity index 100% rename from src/requests/BeerPost/sendCreateBeerPostRequest.ts rename to src/requests/posts/beer-post/sendCreateBeerPostRequest.ts diff --git a/src/requests/BeerPost/sendEditBeerPostRequest.ts b/src/requests/posts/beer-post/sendEditBeerPostRequest.ts similarity index 100% rename from src/requests/BeerPost/sendEditBeerPostRequest.ts rename to src/requests/posts/beer-post/sendEditBeerPostRequest.ts diff --git a/src/requests/BreweryPost/sendCreateBreweryPostRequest.ts b/src/requests/posts/brewery-post/sendCreateBreweryPostRequest.ts similarity index 100% rename from src/requests/BreweryPost/sendCreateBreweryPostRequest.ts rename to src/requests/posts/brewery-post/sendCreateBreweryPostRequest.ts diff --git a/src/requests/User/sendEditUserRequest.ts b/src/requests/users/auth/sendEditUserRequest.ts similarity index 100% rename from src/requests/User/sendEditUserRequest.ts rename to src/requests/users/auth/sendEditUserRequest.ts diff --git a/src/requests/User/sendForgotPasswordRequest.ts b/src/requests/users/auth/sendForgotPasswordRequest.ts similarity index 100% rename from src/requests/User/sendForgotPasswordRequest.ts rename to src/requests/users/auth/sendForgotPasswordRequest.ts diff --git a/src/requests/User/sendLoginUserRequest.ts b/src/requests/users/auth/sendLoginUserRequest.ts similarity index 100% rename from src/requests/User/sendLoginUserRequest.ts rename to src/requests/users/auth/sendLoginUserRequest.ts diff --git a/src/requests/User/sendRegisterUserRequest.ts b/src/requests/users/auth/sendRegisterUserRequest.ts similarity index 100% rename from src/requests/User/sendRegisterUserRequest.ts rename to src/requests/users/auth/sendRegisterUserRequest.ts diff --git a/src/requests/User/sendUpdatePasswordRequest.ts b/src/requests/users/auth/sendUpdatePasswordRequest.ts similarity index 100% rename from src/requests/User/sendUpdatePasswordRequest.ts rename to src/requests/users/auth/sendUpdatePasswordRequest.ts diff --git a/src/requests/UserFollow/sendUserFollowRequest.ts b/src/requests/users/auth/sendUserFollowRequest.ts similarity index 100% rename from src/requests/UserFollow/sendUserFollowRequest.ts rename to src/requests/users/auth/sendUserFollowRequest.ts diff --git a/src/requests/User/validateEmailRequest.ts b/src/requests/users/auth/validateEmailRequest.ts similarity index 100% rename from src/requests/User/validateEmailRequest.ts rename to src/requests/users/auth/validateEmailRequest.ts diff --git a/src/requests/Account/sendUpdateUserAvatarRequest.ts b/src/requests/users/profile/sendUpdateUserAvatarRequest.ts similarity index 100% rename from src/requests/Account/sendUpdateUserAvatarRequest.ts rename to src/requests/users/profile/sendUpdateUserAvatarRequest.ts diff --git a/src/requests/Account/sendUpdateUserProfileRequest.ts b/src/requests/users/profile/sendUpdateUserProfileRequest.ts similarity index 100% rename from src/requests/Account/sendUpdateUserProfileRequest.ts rename to src/requests/users/profile/sendUpdateUserProfileRequest.ts diff --git a/src/requests/validateUsernameRequest.ts b/src/requests/users/profile/validateUsernameRequest.ts similarity index 100% rename from src/requests/validateUsernameRequest.ts rename to src/requests/users/profile/validateUsernameRequest.ts diff --git a/src/services/users/auth/schema/CreateUserValidationSchemas.ts b/src/services/users/auth/schema/CreateUserValidationSchemas.ts index 3d86980..38aae9e 100644 --- a/src/services/users/auth/schema/CreateUserValidationSchemas.ts +++ b/src/services/users/auth/schema/CreateUserValidationSchemas.ts @@ -1,5 +1,5 @@ -import validateEmailRequest from '@/requests/User/validateEmailRequest'; -import validateUsernameRequest from '@/requests/validateUsernameRequest'; +import validateEmailRequest from '@/requests/users/auth/validateEmailRequest'; +import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest'; import sub from 'date-fns/sub'; import { z } from 'zod'; From b21924b89c81b37295d88f128d51d4454bf4ed46 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 18 Dec 2023 22:00:46 -0500 Subject: [PATCH 03/13] Refactor beer comment requests --- src/components/BeerById/BeerCommentForm.tsx | 9 +- .../BeerById/BeerPostCommentsSection.tsx | 20 ++--- src/components/ui/CommentsComponent.tsx | 49 +++++------ src/requests/comments/beer-comment/index.ts | 87 +++++++++++++++++++ .../sendCreateBeerCommentRequest.ts | 53 ----------- .../comments/beer-comment/types/index.ts | 17 ++++ 6 files changed, 136 insertions(+), 99 deletions(-) create mode 100644 src/requests/comments/beer-comment/index.ts delete mode 100644 src/requests/comments/beer-comment/sendCreateBeerCommentRequest.ts create mode 100644 src/requests/comments/beer-comment/types/index.ts diff --git a/src/components/BeerById/BeerCommentForm.tsx b/src/components/BeerById/BeerCommentForm.tsx index aef448b..cbd4a64 100644 --- a/src/components/BeerById/BeerCommentForm.tsx +++ b/src/components/BeerById/BeerCommentForm.tsx @@ -1,5 +1,3 @@ -import sendCreateBeerCommentRequest from '@/requests/comments/beer-comment/sendCreateBeerCommentRequest'; - import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -11,6 +9,7 @@ import useBeerPostComments from '@/hooks/data-fetching/beer-comments/useBeerPost import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import toast from 'react-hot-toast'; import createErrorToast from '@/util/createErrorToast'; +import { sendCreateBeerCommentRequest } from '@/requests/comments/beer-comment'; import CommentForm from '../ui/CommentForm'; interface BeerCommentFormProps { @@ -34,11 +33,7 @@ const BeerCommentForm: FunctionComponent = ({ ) => { const loadingToast = toast.loading('Posting a new comment...'); try { - await sendCreateBeerCommentRequest({ - content: data.content, - rating: data.rating, - beerPostId: beerPost.id, - }); + await sendCreateBeerCommentRequest({ body: data, beerPostId: beerPost.id }); reset(); toast.remove(loadingToast); toast.success('Comment posted successfully.'); diff --git a/src/components/BeerById/BeerPostCommentsSection.tsx b/src/components/BeerById/BeerPostCommentsSection.tsx index 5f83c91..6d574e4 100644 --- a/src/components/BeerById/BeerPostCommentsSection.tsx +++ b/src/components/BeerById/BeerPostCommentsSection.tsx @@ -7,6 +7,10 @@ import { z } from 'zod'; import useBeerPostComments from '@/hooks/data-fetching/beer-comments/useBeerPostComments'; import { useRouter } from 'next/router'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; +import { + deleteBeerPostCommentRequest, + editBeerPostCommentRequest, +} from '@/requests/comments/beer-comment'; import BeerCommentForm from './BeerCommentForm'; import LoadingComponent from './LoadingComponent'; @@ -29,26 +33,14 @@ const BeerPostCommentsSection: FC = ({ beerPost }) const commentSectionRef: MutableRefObject = useRef(null); const handleDeleteRequest = async (id: string) => { - const response = await fetch(`/api/beer-comments/${id}`, { method: 'DELETE' }); - - if (!response.ok) { - throw new Error('Failed to delete comment.'); - } + deleteBeerPostCommentRequest({ commentId: id }); }; const handleEditRequest = async ( id: string, data: z.infer, ) => { - const response = await fetch(`/api/beer-comments/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content: data.content, rating: data.rating }), - }); - - if (!response.ok) { - throw new Error('Failed to update comment.'); - } + editBeerPostCommentRequest({ body: data, commentId: id }); }; return ( diff --git a/src/components/ui/CommentsComponent.tsx b/src/components/ui/CommentsComponent.tsx index 20253aa..64c1d10 100644 --- a/src/components/ui/CommentsComponent.tsx +++ b/src/components/ui/CommentsComponent.tsx @@ -6,47 +6,46 @@ import { useInView } from 'react-intersection-observer'; import useBeerPostComments from '@/hooks/data-fetching/beer-comments/useBeerPostComments'; import useBreweryPostComments from '@/hooks/data-fetching/brewery-comments/useBreweryPostComments'; +import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; import NoCommentsCard from '../BeerById/NoCommentsCard'; import LoadingComponent from '../BeerById/LoadingComponent'; import CommentCardBody from '../BeerBreweryComments/CommentCardBody'; +type HookReturnType = ReturnType< + typeof useBeerPostComments | typeof useBreweryPostComments | typeof useBeerStyleComments +>; + +type HandleDeleteRequest = (id: string) => Promise; + +type HandleEditRequest = ( + id: string, + data: { content: string; rating: number }, +) => Promise; + interface CommentsComponentProps { + comments: HookReturnType['comments']; commentSectionRef: MutableRefObject; + handleDeleteRequest: HandleDeleteRequest; + handleEditRequest: HandleEditRequest; + isAtEnd: HookReturnType['isAtEnd']; + isLoadingMore: HookReturnType['isLoadingMore']; + mutate: HookReturnType['mutate']; pageSize: number; - size: ReturnType['size']; - setSize: ReturnType< - typeof useBeerPostComments | typeof useBreweryPostComments - >['setSize']; - comments: ReturnType< - typeof useBeerPostComments | typeof useBreweryPostComments - >['comments']; - isAtEnd: ReturnType< - typeof useBeerPostComments | typeof useBreweryPostComments - >['isAtEnd']; - isLoadingMore: ReturnType< - typeof useBeerPostComments | typeof useBreweryPostComments - >['isLoadingMore']; - mutate: ReturnType< - typeof useBeerPostComments | typeof useBreweryPostComments - >['mutate']; - handleDeleteRequest: (id: string) => Promise; - handleEditRequest: ( - id: string, - data: { content: string; rating: number }, - ) => Promise; + setSize: HookReturnType['setSize']; + size: HookReturnType['size']; } const CommentsComponent: FC = ({ - commentSectionRef, comments, + commentSectionRef, + handleDeleteRequest, + handleEditRequest, isAtEnd, isLoadingMore, + mutate, pageSize, setSize, size, - mutate, - handleDeleteRequest, - handleEditRequest, }) => { const { ref: penultimateCommentRef } = useInView({ threshold: 0.1, diff --git a/src/requests/comments/beer-comment/index.ts b/src/requests/comments/beer-comment/index.ts new file mode 100644 index 0000000..7c2d015 --- /dev/null +++ b/src/requests/comments/beer-comment/index.ts @@ -0,0 +1,87 @@ +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import { + SendCreateBeerCommentRequest, + SendDeleteBeerPostCommentRequest, + SendEditBeerPostCommentRequest, +} from './types'; + +export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async ({ + body, + commentId, +}) => { + const response = await fetch(`/api/beer-post-comments/${commentId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error('Failed to edit comment'); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error(parsed.error.message); + } + + return parsed.data; +}; + +export const deleteBeerPostCommentRequest: SendDeleteBeerPostCommentRequest = async ({ + commentId, +}) => { + const response = await fetch(`/api/beer-post-comments/${commentId}`, { + method: 'DELETE', + }); + + if (!response.ok) { + throw new Error('Failed to delete comment'); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error(parsed.error.message); + } + + return parsed.data; +}; + +export const sendCreateBeerCommentRequest: SendCreateBeerCommentRequest = async ({ + beerPostId, + body, +}) => { + const { content, rating } = body; + const response = await fetch(`/api/beers/${beerPostId}/comments`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ beerPostId, content, rating }), + }); + if (!response.ok) { + throw new Error(response.statusText); + } + + const data = await response.json(); + + const parsedResponse = APIResponseValidationSchema.safeParse(data); + + if (!parsedResponse.success) { + throw new Error('Invalid API response'); + } + + const parsedPayload = CommentQueryResult.safeParse(parsedResponse.data.payload); + + if (!parsedPayload.success) { + throw new Error('Invalid API response payload'); + } + + return parsedPayload.data; +}; + +export default sendCreateBeerCommentRequest; diff --git a/src/requests/comments/beer-comment/sendCreateBeerCommentRequest.ts b/src/requests/comments/beer-comment/sendCreateBeerCommentRequest.ts deleted file mode 100644 index 41dbde3..0000000 --- a/src/requests/comments/beer-comment/sendCreateBeerCommentRequest.ts +++ /dev/null @@ -1,53 +0,0 @@ -import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; -import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; - -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -const BeerCommentValidationSchemaWithId = CreateCommentValidationSchema.extend({ - beerPostId: z.string().cuid(), -}); - -/** - * Sends a POST request to the server to create a new beer comment. - * - * @param data The data to be sent to the server. - * @param data.beerPostId The ID of the beer post to comment on. - * @param data.content The content of the comment. - * @param data.rating The rating of the beer. - * @returns A promise that resolves to the created comment. - * @throws An error if the request fails, the API response is invalid, or the API response - * payload is invalid. - */ -const sendCreateBeerCommentRequest = async ({ - beerPostId, - content, - rating, -}: z.infer) => { - const response = await fetch(`/api/beers/${beerPostId}/comments`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ beerPostId, content, rating }), - }); - if (!response.ok) { - throw new Error(response.statusText); - } - - const data = await response.json(); - - const parsedResponse = APIResponseValidationSchema.safeParse(data); - - if (!parsedResponse.success) { - throw new Error('Invalid API response'); - } - - const parsedPayload = CommentQueryResult.safeParse(parsedResponse.data.payload); - - if (!parsedPayload.success) { - throw new Error('Invalid API response payload'); - } - - return parsedPayload.data; -}; - -export default sendCreateBeerCommentRequest; diff --git a/src/requests/comments/beer-comment/types/index.ts b/src/requests/comments/beer-comment/types/index.ts new file mode 100644 index 0000000..b790ed6 --- /dev/null +++ b/src/requests/comments/beer-comment/types/index.ts @@ -0,0 +1,17 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +export type SendEditBeerPostCommentRequest = (args: { + body: { content: string; rating: number }; + commentId: string; +}) => Promise>; + +export type SendDeleteBeerPostCommentRequest = (args: { + commentId: string; +}) => Promise>; + +export type SendCreateBeerCommentRequest = (args: { + beerPostId: string; + body: { content: string; rating: number }; +}) => Promise>; From 0e9978255768d1d3ce6cac35a7b86f316f7808a2 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Sun, 24 Dec 2023 12:34:51 -0500 Subject: [PATCH 04/13] Refactor: rename [:id] to [:postId] for api routes --- .../BeerById/BeerPostCommentsSection.tsx | 8 +++++-- .../BeerStyleById/BeerStyleCommentSection.tsx | 4 ++-- .../BreweryById/BreweryCommentsSection.tsx | 21 +++++++++++------- .../comments/beer-comments/index.ts | 16 +++++++------- .../comments/beer-style-comments/index.ts | 16 ++++++++------ .../comments/brewery-comments/index.ts | 18 +++++++-------- src/controllers/comments/types/index.ts | 4 ++-- .../likes/beer-posts-likes/index.ts | 12 +++++----- .../likes/beer-style-likes/index.ts | 12 +++++----- .../likes/brewery-post-likes/index.ts | 22 +++++++++---------- src/controllers/likes/types/index.ts | 2 +- src/controllers/posts/beer-posts/index.ts | 14 ++++++------ .../posts/beer-posts/types/index.ts | 4 ++-- .../useGetBreweryPostLikeCount.ts | 2 +- .../[postId]/comments/[commentId].ts} | 6 +++-- .../{[id] => [postId]}/comments/index.ts | 4 ++-- .../beers/{[id] => [postId]}/images/index.ts | 0 .../api/beers/{[id] => [postId]}/index.ts | 4 ++-- .../beers/{[id] => [postId]}/like/index.ts | 4 ++-- .../beers/{[id] => [postId]}/like/is-liked.ts | 2 +- .../{[id] => [postId]}/recommendations.ts | 2 +- .../styles/{[id] => [postId]}/beers/index.ts | 2 +- .../styles/[postId]/comments/[commentId].ts} | 4 ++-- .../{[id] => [postId]}/comments/index.ts | 4 ++-- .../beers/styles/{[id] => [postId]}/index.ts | 2 +- .../styles/{[id] => [postId]}/like/index.ts | 4 ++-- .../{[id] => [postId]}/like/is-liked.ts | 2 +- .../{[id] => [postId]}/beers/index.ts | 2 +- .../[postId]/comments/[commentId].ts} | 4 ++-- .../{[id] => [postId]}/comments/index.ts | 4 ++-- .../{[id] => [postId]}/images/index.ts | 0 .../api/breweries/{[id] => [postId]}/index.ts | 0 .../{[id] => [postId]}/like/index.ts | 4 ++-- .../{[id] => [postId]}/like/is-liked.ts | 6 +---- src/requests/comments/beer-comment/index.ts | 6 +++-- .../comments/beer-comment/types/index.ts | 2 ++ 36 files changed, 118 insertions(+), 105 deletions(-) rename src/pages/api/{beer-comments/[id].ts => beers/[postId]/comments/[commentId].ts} (84%) rename src/pages/api/beers/{[id] => [postId]}/comments/index.ts (88%) rename src/pages/api/beers/{[id] => [postId]}/images/index.ts (100%) rename src/pages/api/beers/{[id] => [postId]}/index.ts (90%) rename src/pages/api/beers/{[id] => [postId]}/like/index.ts (85%) rename src/pages/api/beers/{[id] => [postId]}/like/is-liked.ts (91%) rename src/pages/api/beers/{[id] => [postId]}/recommendations.ts (91%) rename src/pages/api/beers/styles/{[id] => [postId]}/beers/index.ts (91%) rename src/pages/api/{beer-style-comments/[id].ts => beers/styles/[postId]/comments/[commentId].ts} (87%) rename src/pages/api/beers/styles/{[id] => [postId]}/comments/index.ts (88%) rename src/pages/api/beers/styles/{[id] => [postId]}/index.ts (89%) rename src/pages/api/beers/styles/{[id] => [postId]}/like/index.ts (85%) rename src/pages/api/beers/styles/{[id] => [postId]}/like/is-liked.ts (91%) rename src/pages/api/breweries/{[id] => [postId]}/beers/index.ts (90%) rename src/pages/api/{brewery-comments/[id].ts => breweries/[postId]/comments/[commentId].ts} (87%) rename src/pages/api/breweries/{[id] => [postId]}/comments/index.ts (88%) rename src/pages/api/breweries/{[id] => [postId]}/images/index.ts (100%) rename src/pages/api/breweries/{[id] => [postId]}/index.ts (100%) rename src/pages/api/breweries/{[id] => [postId]}/like/index.ts (85%) rename src/pages/api/breweries/{[id] => [postId]}/like/is-liked.ts (90%) diff --git a/src/components/BeerById/BeerPostCommentsSection.tsx b/src/components/BeerById/BeerPostCommentsSection.tsx index 6d574e4..f8f6738 100644 --- a/src/components/BeerById/BeerPostCommentsSection.tsx +++ b/src/components/BeerById/BeerPostCommentsSection.tsx @@ -33,14 +33,18 @@ const BeerPostCommentsSection: FC = ({ beerPost }) const commentSectionRef: MutableRefObject = useRef(null); const handleDeleteRequest = async (id: string) => { - deleteBeerPostCommentRequest({ commentId: id }); + await deleteBeerPostCommentRequest({ commentId: id, beerPostId: beerPost.id }); }; const handleEditRequest = async ( id: string, data: z.infer, ) => { - editBeerPostCommentRequest({ body: data, commentId: id }); + await editBeerPostCommentRequest({ + body: data, + commentId: id, + beerPostId: beerPost.id, + }); }; return ( diff --git a/src/components/BeerStyleById/BeerStyleCommentSection.tsx b/src/components/BeerStyleById/BeerStyleCommentSection.tsx index 5716327..470ee7e 100644 --- a/src/components/BeerStyleById/BeerStyleCommentSection.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentSection.tsx @@ -28,7 +28,7 @@ const BeerStyleCommentsSection: FC = ({ beerStyle const commentSectionRef: MutableRefObject = useRef(null); const handleDeleteRequest = async (id: string) => { - const response = await fetch(`/api/beer-style-comments/${id}`, { + const response = await fetch(`/api/beers/styles/${beerStyle.id}/comments/${id}`, { method: 'DELETE', }); @@ -41,7 +41,7 @@ const BeerStyleCommentsSection: FC = ({ beerStyle id: string, data: z.infer, ) => { - const response = await fetch(`/api/beer-style-comments/${id}`, { + const response = await fetch(`/api/beers/styles/${beerStyle.id}/comments/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: data.content, rating: data.rating }), diff --git a/src/components/BreweryById/BreweryCommentsSection.tsx b/src/components/BreweryById/BreweryCommentsSection.tsx index e7b6c2e..1ecbcfc 100644 --- a/src/components/BreweryById/BreweryCommentsSection.tsx +++ b/src/components/BreweryById/BreweryCommentsSection.tsx @@ -31,9 +31,10 @@ const BreweryCommentsSection: FC = ({ breweryPost }) => const commentSectionRef: MutableRefObject = useRef(null); const handleDeleteRequest = async (commentId: string) => { - const response = await fetch(`/api/brewery-comments/${commentId}`, { - method: 'DELETE', - }); + const response = await fetch( + `/api/breweries/${breweryPost.id}/comments/${commentId}`, + { method: 'DELETE' }, + ); if (!response.ok) { throw new Error(response.statusText); @@ -44,15 +45,19 @@ const BreweryCommentsSection: FC = ({ breweryPost }) => commentId: string, data: z.infer, ) => { - const response = await fetch(`/api/brewery-comments/${commentId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content: data.content, rating: data.rating }), - }); + const response = await fetch( + `/api/breweries/${breweryPost.id}/comments/${commentId}`, + { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: data.content, rating: data.rating }), + }, + ); if (!response.ok) { throw new Error(response.statusText); } + console.log(await response.json()); }; return ( diff --git a/src/controllers/comments/beer-comments/index.ts b/src/controllers/comments/beer-comments/index.ts index 7ca8dbc..b56cd62 100644 --- a/src/controllers/comments/beer-comments/index.ts +++ b/src/controllers/comments/beer-comments/index.ts @@ -24,9 +24,9 @@ export const checkIfBeerCommentOwner = async ( res: NextApiResponse>, next: NextHandler, ) => { - const { id } = req.query; + const { commentId } = req.query; const user = req.user!; - const comment = await getBeerPostCommentByIdService({ beerPostCommentId: id }); + const comment = await getBeerPostCommentByIdService({ beerPostCommentId: commentId }); if (!comment) { throw new ServerError('Comment not found', 404); @@ -43,9 +43,9 @@ export const editBeerPostComment = async ( req: EditAndCreateCommentRequest, res: NextApiResponse>, ) => { - const { id } = req.query; + const { commentId } = req.query; - await editBeerPostCommentByIdService({ body: req.body, beerPostCommentId: id }); + await editBeerPostCommentByIdService({ body: req.body, beerPostCommentId: commentId }); res.status(200).json({ success: true, @@ -58,9 +58,9 @@ export const deleteBeerPostComment = async ( req: CommentRequest, res: NextApiResponse>, ) => { - const { id } = req.query; + const { commentId } = req.query; - await deleteBeerCommentByIdService({ beerPostCommentId: id }); + await deleteBeerCommentByIdService({ beerPostCommentId: commentId }); res.status(200).json({ success: true, @@ -73,7 +73,7 @@ export const createBeerPostComment = async ( req: EditAndCreateCommentRequest, res: NextApiResponse>, ) => { - const beerPostId = req.query.id; + const beerPostId = req.query.postId; const newBeerComment = await createBeerPostCommentService({ body: req.body, @@ -93,7 +93,7 @@ export const getAllBeerPostComments = async ( req: GetAllCommentsRequest, res: NextApiResponse>, ) => { - const beerPostId = req.query.id; + const beerPostId = req.query.postId; // eslint-disable-next-line @typescript-eslint/naming-convention const { page_size, page_num } = req.query; diff --git a/src/controllers/comments/beer-style-comments/index.ts b/src/controllers/comments/beer-style-comments/index.ts index 9365c0c..7179fc3 100644 --- a/src/controllers/comments/beer-style-comments/index.ts +++ b/src/controllers/comments/beer-style-comments/index.ts @@ -25,10 +25,12 @@ export const checkIfBeerStyleCommentOwner = async < res: NextApiResponse>, next: NextHandler, ) => { - const { id } = req.query; + const { commentId } = req.query; const user = req.user!; - const beerStyleComment = await findBeerStyleCommentById({ beerStyleCommentId: id }); + const beerStyleComment = await findBeerStyleCommentById({ + beerStyleCommentId: commentId, + }); if (!beerStyleComment) { throw new ServerError('Beer style comment not found.', 404); @@ -49,7 +51,7 @@ export const editBeerStyleComment = async ( res: NextApiResponse>, ) => { await updateBeerStyleCommentById({ - beerStyleCommentId: req.query.id, + beerStyleCommentId: req.query.commentId, body: req.body, }); @@ -64,9 +66,9 @@ export const deleteBeerStyleComment = async ( req: CommentRequest, res: NextApiResponse>, ) => { - const { id } = req.query; + const { commentId } = req.query; - await deleteBeerStyleCommentById({ beerStyleCommentId: id }); + await deleteBeerStyleCommentById({ beerStyleCommentId: commentId }); res.status(200).json({ success: true, @@ -81,7 +83,7 @@ export const createComment = async ( ) => { const newBeerStyleComment = await createNewBeerStyleComment({ body: req.body, - beerStyleId: req.query.id, + beerStyleId: req.query.postId, userId: req.user!.id, }); @@ -97,7 +99,7 @@ export const getAll = async ( req: GetAllCommentsRequest, res: NextApiResponse>, ) => { - const beerStyleId = req.query.id; + const beerStyleId = req.query.postId; // eslint-disable-next-line @typescript-eslint/naming-convention const { page_size, page_num } = req.query; diff --git a/src/controllers/comments/brewery-comments/index.ts b/src/controllers/comments/brewery-comments/index.ts index 103dca9..cfe0b27 100644 --- a/src/controllers/comments/brewery-comments/index.ts +++ b/src/controllers/comments/brewery-comments/index.ts @@ -25,9 +25,9 @@ export const checkIfBreweryCommentOwner = async < res: NextApiResponse>, next: NextHandler, ) => { - const { id } = req.query; + const { commentId } = req.query; const user = req.user!; - const comment = await getBreweryCommentById({ breweryCommentId: id }); + const comment = await getBreweryCommentById({ breweryCommentId: commentId }); if (!comment) { throw new ServerError('Comment not found', 404); @@ -44,10 +44,10 @@ export const editBreweryPostComment = async ( req: EditAndCreateCommentRequest, res: NextApiResponse>, ) => { - const { id } = req.query; + const { commentId } = req.query; - const updated = updateBreweryCommentById({ - breweryCommentId: id, + const updated = await updateBreweryCommentById({ + breweryCommentId: commentId, body: req.body, }); @@ -63,9 +63,9 @@ export const deleteBreweryPostComment = async ( req: CommentRequest, res: NextApiResponse>, ) => { - const { id } = req.query; + const { commentId } = req.query; - await deleteBreweryCommentByIdService({ breweryCommentId: id }); + await deleteBreweryCommentByIdService({ breweryCommentId: commentId }); res.status(200).json({ success: true, @@ -78,7 +78,7 @@ export const createComment = async ( req: EditAndCreateCommentRequest, res: NextApiResponse>, ) => { - const breweryPostId = req.query.id; + const breweryPostId = req.query.postId; const user = req.user!; @@ -100,7 +100,7 @@ export const getAll = async ( req: GetAllCommentsRequest, res: NextApiResponse>, ) => { - const breweryPostId = req.query.id; + const breweryPostId = req.query.postId; // eslint-disable-next-line @typescript-eslint/naming-convention const { page_size, page_num } = req.query; diff --git a/src/controllers/comments/types/index.ts b/src/controllers/comments/types/index.ts index f56f153..9968ba2 100644 --- a/src/controllers/comments/types/index.ts +++ b/src/controllers/comments/types/index.ts @@ -3,7 +3,7 @@ import CreateCommentValidationSchema from '@/services/schema/CommentSchema/Creat import { z } from 'zod'; export interface CommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; + query: { postId: string; commentId: string }; } export interface EditAndCreateCommentRequest extends CommentRequest { @@ -11,5 +11,5 @@ export interface EditAndCreateCommentRequest extends CommentRequest { } export interface GetAllCommentsRequest extends UserExtendedNextApiRequest { - query: { id: string; page_size: string; page_num: string }; + query: { postId: string; page_size: string; page_num: string }; } diff --git a/src/controllers/likes/beer-posts-likes/index.ts b/src/controllers/likes/beer-posts-likes/index.ts index 0211164..2215978 100644 --- a/src/controllers/likes/beer-posts-likes/index.ts +++ b/src/controllers/likes/beer-posts-likes/index.ts @@ -2,7 +2,7 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; import ServerError from '@/config/util/ServerError'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiResponse, NextApiRequest } from 'next'; +import { NextApiResponse } from 'next'; import { z } from 'zod'; import { getBeerPostById } from '@/services/posts/beer-post'; @@ -19,9 +19,9 @@ export const sendBeerPostLikeRequest = async ( res: NextApiResponse>, ) => { const user = req.user!; - const id = req.query.id as string; + const { postId } = req.query; - const beer = await getBeerPostById({ beerPostId: id }); + const beer = await getBeerPostById({ beerPostId: postId }); if (!beer) { throw new ServerError('Could not find a beer post with that id.', 404); } @@ -53,12 +53,12 @@ export const sendBeerPostLikeRequest = async ( }; export const getBeerPostLikeCount = async ( - req: NextApiRequest, + req: LikeRequest, res: NextApiResponse>, ) => { - const id = req.query.id as string; + const { postId } = req.query; - const likeCount = await getBeerPostLikeCountService({ beerPostId: id }); + const likeCount = await getBeerPostLikeCountService({ beerPostId: postId }); res.status(200).json({ success: true, diff --git a/src/controllers/likes/beer-style-likes/index.ts b/src/controllers/likes/beer-style-likes/index.ts index fd2499d..85a8403 100644 --- a/src/controllers/likes/beer-style-likes/index.ts +++ b/src/controllers/likes/beer-style-likes/index.ts @@ -1,7 +1,7 @@ import ServerError from '@/config/util/ServerError'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiResponse, NextApiRequest } from 'next'; +import { NextApiResponse } from 'next'; import { z } from 'zod'; import { UserExtendedNextApiRequest } from '@/config/auth/types'; import { @@ -18,9 +18,9 @@ export const sendBeerStyleLikeRequest = async ( res: NextApiResponse>, ) => { const user = req.user!; - const { id } = req.query; + const { postId } = req.query; - const beerStyle = await getBeerStyleByIdService({ beerStyleId: id }); + const beerStyle = await getBeerStyleByIdService({ beerStyleId: postId }); if (!beerStyle) { throw new ServerError('Could not find a beer style with that id.', 404); } @@ -48,11 +48,11 @@ export const sendBeerStyleLikeRequest = async ( }; export const getBeerStyleLikeCountRequest = async ( - req: NextApiRequest, + req: LikeRequest, res: NextApiResponse>, ) => { - const id = req.query.id as string; - const likeCount = await getBeerStyleLikeCountService({ beerStyleId: id }); + const { postId } = req.query; + const likeCount = await getBeerStyleLikeCountService({ beerStyleId: postId }); res.status(200).json({ success: true, diff --git a/src/controllers/likes/brewery-post-likes/index.ts b/src/controllers/likes/brewery-post-likes/index.ts index 6eeb790..af4374f 100644 --- a/src/controllers/likes/brewery-post-likes/index.ts +++ b/src/controllers/likes/brewery-post-likes/index.ts @@ -1,4 +1,3 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import ServerError from '@/config/util/ServerError'; import { @@ -9,17 +8,18 @@ import { } from '@/services/likes/brewery-post-like'; import { getBreweryPostByIdService } from '@/services/posts/brewery-post'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiResponse, NextApiRequest } from 'next'; +import { NextApiResponse } from 'next'; import { z } from 'zod'; +import { LikeRequest } from '../types'; export const sendBreweryPostLikeRequest = async ( - req: UserExtendedNextApiRequest, + req: LikeRequest, res: NextApiResponse>, ) => { - const id = req.query.id! as string; + const { postId } = req.query; const user = req.user!; - const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); + const breweryPost = await getBreweryPostByIdService({ breweryPostId: postId }); if (!breweryPost) { throw new ServerError('Could not find a brewery post with that id', 404); @@ -53,12 +53,12 @@ export const sendBreweryPostLikeRequest = async ( }; export const getBreweryPostLikeCount = async ( - req: NextApiRequest, + req: LikeRequest, res: NextApiResponse>, ) => { - const id = req.query.id! as string; + const { postId } = req.query; - const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); + const breweryPost = await getBreweryPostByIdService({ breweryPostId: postId }); if (!breweryPost) { throw new ServerError('Could not find a brewery post with that id', 404); } @@ -76,14 +76,14 @@ export const getBreweryPostLikeCount = async ( }; export const getBreweryPostLikeStatus = async ( - req: UserExtendedNextApiRequest, + req: LikeRequest, res: NextApiResponse>, ) => { const user = req.user!; - const id = req.query.id as string; + const { postId } = req.query; const liked = await findBreweryPostLikeService({ - breweryPostId: id, + breweryPostId: postId, likedById: user.id, }); diff --git a/src/controllers/likes/types/index.ts b/src/controllers/likes/types/index.ts index 424a4e4..8efc87b 100644 --- a/src/controllers/likes/types/index.ts +++ b/src/controllers/likes/types/index.ts @@ -1,5 +1,5 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; export interface LikeRequest extends UserExtendedNextApiRequest { - query: { id: string }; + query: { postId: string }; } diff --git a/src/controllers/posts/beer-posts/index.ts b/src/controllers/posts/beer-posts/index.ts index 76cacf7..0e8bda5 100644 --- a/src/controllers/posts/beer-posts/index.ts +++ b/src/controllers/posts/beer-posts/index.ts @@ -28,9 +28,9 @@ export const checkIfBeerPostOwner = async { const { user, query } = req; - const { id } = query; + const { postId } = query; - const beerPost = await getBeerPostById({ beerPostId: id }); + const beerPost = await getBeerPostById({ beerPostId: postId }); if (!beerPost) { throw new ServerError('Beer post not found', 404); @@ -47,7 +47,7 @@ export const editBeerPost = async ( req: EditBeerPostRequest, res: NextApiResponse>, ) => { - await editBeerPostByIdService({ beerPostId: req.query.id, body: req.body }); + await editBeerPostByIdService({ beerPostId: req.query.postId, body: req.body }); res.status(200).json({ message: 'Beer post updated successfully', @@ -57,9 +57,9 @@ export const editBeerPost = async ( }; export const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => { - const { id } = req.query; + const { postId } = req.query; - const deleted = await deleteBeerPostByIdService({ beerPostId: id }); + const deleted = await deleteBeerPostByIdService({ beerPostId: postId }); if (!deleted) { throw new ServerError('Beer post not found', 404); } @@ -75,9 +75,9 @@ export const getBeerPostRecommendations = async ( req: GetBeerRecommendationsRequest, res: NextApiResponse>, ) => { - const { id } = req.query; + const { postId } = req.query; - const beerPost = await getBeerPostById({ beerPostId: id }); + const beerPost = await getBeerPostById({ beerPostId: postId }); if (!beerPost) { throw new ServerError('Beer post not found', 404); diff --git a/src/controllers/posts/beer-posts/types/index.ts b/src/controllers/posts/beer-posts/types/index.ts index a1b5977..d4bed49 100644 --- a/src/controllers/posts/beer-posts/types/index.ts +++ b/src/controllers/posts/beer-posts/types/index.ts @@ -5,7 +5,7 @@ import { NextApiRequest } from 'next'; import { z } from 'zod'; export interface BeerPostRequest extends UserExtendedNextApiRequest { - query: { id: string }; + query: { postId: string }; } export interface EditBeerPostRequest extends BeerPostRequest { @@ -17,7 +17,7 @@ export interface GetAllBeerPostsRequest extends NextApiRequest { } export interface GetBeerRecommendationsRequest extends BeerPostRequest { - query: { id: string; page_num: string; page_size: string }; + query: { postId: string; page_num: string; page_size: string }; } export interface CreateBeerPostRequest extends UserExtendedNextApiRequest { diff --git a/src/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount.ts b/src/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount.ts index c2ba167..7391410 100644 --- a/src/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount.ts +++ b/src/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount.ts @@ -41,7 +41,7 @@ const useGetBreweryPostLikeCount = (breweryPostId: string) => { error: error as unknown, isLoading, mutate, - likeCount: data as number | undefined, + likeCount: data, }; }; diff --git a/src/pages/api/beer-comments/[id].ts b/src/pages/api/beers/[postId]/comments/[commentId].ts similarity index 84% rename from src/pages/api/beer-comments/[id].ts rename to src/pages/api/beers/[postId]/comments/[commentId].ts index 8acf718..aa30b1d 100644 --- a/src/pages/api/beer-comments/[id].ts +++ b/src/pages/api/beers/[postId]/comments/[commentId].ts @@ -22,14 +22,16 @@ const router = createRouter< router .delete( - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ + querySchema: z.object({ postId: z.string().cuid(), commentId: z.string().cuid() }), + }), getCurrentUser, checkIfBeerCommentOwner, deleteBeerPostComment, ) .put( validateRequest({ - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ postId: z.string().cuid(), commentId: z.string().cuid() }), bodySchema: CreateCommentValidationSchema, }), getCurrentUser, diff --git a/src/pages/api/beers/[id]/comments/index.ts b/src/pages/api/beers/[postId]/comments/index.ts similarity index 88% rename from src/pages/api/beers/[id]/comments/index.ts rename to src/pages/api/beers/[postId]/comments/index.ts index 1efb734..17b0608 100644 --- a/src/pages/api/beers/[id]/comments/index.ts +++ b/src/pages/api/beers/[postId]/comments/index.ts @@ -22,7 +22,7 @@ const router = createRouter< router.post( validateRequest({ bodySchema: CreateCommentValidationSchema, - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ postId: z.string().cuid() }), }), getCurrentUser, createBeerPostComment, @@ -30,7 +30,7 @@ router.post( router.get( validateRequest({ - querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), + querySchema: PaginatedQueryResponseSchema.extend({ postId: z.string().cuid() }), }), getAllBeerPostComments, ); diff --git a/src/pages/api/beers/[id]/images/index.ts b/src/pages/api/beers/[postId]/images/index.ts similarity index 100% rename from src/pages/api/beers/[id]/images/index.ts rename to src/pages/api/beers/[postId]/images/index.ts diff --git a/src/pages/api/beers/[id]/index.ts b/src/pages/api/beers/[postId]/index.ts similarity index 90% rename from src/pages/api/beers/[id]/index.ts rename to src/pages/api/beers/[postId]/index.ts index 8a67ec7..f513eaf 100644 --- a/src/pages/api/beers/[id]/index.ts +++ b/src/pages/api/beers/[postId]/index.ts @@ -26,14 +26,14 @@ router .put( validateRequest({ bodySchema: EditBeerPostValidationSchema, - querySchema: z.object({ id: z.string() }), + querySchema: z.object({ postId: z.string() }), }), getCurrentUser, checkIfBeerPostOwner, editBeerPost, ) .delete( - validateRequest({ querySchema: z.object({ id: z.string() }) }), + validateRequest({ querySchema: z.object({ postId: z.string() }) }), getCurrentUser, checkIfBeerPostOwner, deleteBeerPost, diff --git a/src/pages/api/beers/[id]/like/index.ts b/src/pages/api/beers/[postId]/like/index.ts similarity index 85% rename from src/pages/api/beers/[id]/like/index.ts rename to src/pages/api/beers/[postId]/like/index.ts index a6e7eb8..ffbc659 100644 --- a/src/pages/api/beers/[id]/like/index.ts +++ b/src/pages/api/beers/[postId]/like/index.ts @@ -19,12 +19,12 @@ const router = createRouter< router.post( getCurrentUser, - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), sendBeerPostLikeRequest, ); router.get( - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), getBeerPostLikeCount, ); diff --git a/src/pages/api/beers/[id]/like/is-liked.ts b/src/pages/api/beers/[postId]/like/is-liked.ts similarity index 91% rename from src/pages/api/beers/[id]/like/is-liked.ts rename to src/pages/api/beers/[postId]/like/is-liked.ts index aa0f9a3..3146f48 100644 --- a/src/pages/api/beers/[id]/like/is-liked.ts +++ b/src/pages/api/beers/[postId]/like/is-liked.ts @@ -16,7 +16,7 @@ const router = createRouter< router.get( getCurrentUser, - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), checkIfBeerPostIsLiked, ); diff --git a/src/pages/api/beers/[id]/recommendations.ts b/src/pages/api/beers/[postId]/recommendations.ts similarity index 91% rename from src/pages/api/beers/[id]/recommendations.ts rename to src/pages/api/beers/[postId]/recommendations.ts index a3f6f92..65cf82a 100644 --- a/src/pages/api/beers/[id]/recommendations.ts +++ b/src/pages/api/beers/[postId]/recommendations.ts @@ -15,7 +15,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), + querySchema: PaginatedQueryResponseSchema.extend({ postId: z.string().cuid() }), }), getBeerPostRecommendations, ); diff --git a/src/pages/api/beers/styles/[id]/beers/index.ts b/src/pages/api/beers/styles/[postId]/beers/index.ts similarity index 91% rename from src/pages/api/beers/styles/[id]/beers/index.ts rename to src/pages/api/beers/styles/[postId]/beers/index.ts index 7da569f..46440b2 100644 --- a/src/pages/api/beers/styles/[id]/beers/index.ts +++ b/src/pages/api/beers/styles/[postId]/beers/index.ts @@ -19,7 +19,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), + querySchema: PaginatedQueryResponseSchema.extend({ postId: z.string().cuid() }), }), getAllBeersByBeerStyle, ); diff --git a/src/pages/api/beer-style-comments/[id].ts b/src/pages/api/beers/styles/[postId]/comments/[commentId].ts similarity index 87% rename from src/pages/api/beer-style-comments/[id].ts rename to src/pages/api/beers/styles/[postId]/comments/[commentId].ts index ec6dc8f..4d7f0b3 100644 --- a/src/pages/api/beer-style-comments/[id].ts +++ b/src/pages/api/beers/styles/[postId]/comments/[commentId].ts @@ -22,7 +22,7 @@ const router = createRouter< router .delete( validateRequest({ - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ postId: z.string().cuid(), commentId: z.string().cuid() }), }), getCurrentUser, checkIfBeerStyleCommentOwner, @@ -30,7 +30,7 @@ router ) .put( validateRequest({ - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ postId: z.string().cuid(), commentId: z.string().cuid() }), bodySchema: CreateCommentValidationSchema, }), getCurrentUser, diff --git a/src/pages/api/beers/styles/[id]/comments/index.ts b/src/pages/api/beers/styles/[postId]/comments/index.ts similarity index 88% rename from src/pages/api/beers/styles/[id]/comments/index.ts rename to src/pages/api/beers/styles/[postId]/comments/index.ts index b8e9b8c..6345d20 100644 --- a/src/pages/api/beers/styles/[id]/comments/index.ts +++ b/src/pages/api/beers/styles/[postId]/comments/index.ts @@ -19,7 +19,7 @@ const router = createRouter< router.post( validateRequest({ bodySchema: CreateCommentValidationSchema, - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ postId: z.string().cuid() }), }), getCurrentUser, createComment, @@ -27,7 +27,7 @@ router.post( router.get( validateRequest({ - querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), + querySchema: PaginatedQueryResponseSchema.extend({ postId: z.string().cuid() }), }), getAll, ); diff --git a/src/pages/api/beers/styles/[id]/index.ts b/src/pages/api/beers/styles/[postId]/index.ts similarity index 89% rename from src/pages/api/beers/styles/[id]/index.ts rename to src/pages/api/beers/styles/[postId]/index.ts index c392ab7..1d8cfcc 100644 --- a/src/pages/api/beers/styles/[id]/index.ts +++ b/src/pages/api/beers/styles/[postId]/index.ts @@ -13,7 +13,7 @@ const router = createRouter< >(); router.get( - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), getBeerStyle, ); diff --git a/src/pages/api/beers/styles/[id]/like/index.ts b/src/pages/api/beers/styles/[postId]/like/index.ts similarity index 85% rename from src/pages/api/beers/styles/[id]/like/index.ts rename to src/pages/api/beers/styles/[postId]/like/index.ts index 87ff024..688c83c 100644 --- a/src/pages/api/beers/styles/[id]/like/index.ts +++ b/src/pages/api/beers/styles/[postId]/like/index.ts @@ -20,12 +20,12 @@ const router = createRouter< router.post( getCurrentUser, - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), sendBeerStyleLikeRequest, ); router.get( - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), getBeerStyleLikeCountRequest, ); diff --git a/src/pages/api/beers/styles/[id]/like/is-liked.ts b/src/pages/api/beers/styles/[postId]/like/is-liked.ts similarity index 91% rename from src/pages/api/beers/styles/[id]/like/is-liked.ts rename to src/pages/api/beers/styles/[postId]/like/is-liked.ts index 3289cb6..d4709a7 100644 --- a/src/pages/api/beers/styles/[id]/like/is-liked.ts +++ b/src/pages/api/beers/styles/[postId]/like/is-liked.ts @@ -15,7 +15,7 @@ const router = createRouter< router.get( getCurrentUser, - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), checkIfBeerStyleIsLiked, ); diff --git a/src/pages/api/breweries/[id]/beers/index.ts b/src/pages/api/breweries/[postId]/beers/index.ts similarity index 90% rename from src/pages/api/breweries/[id]/beers/index.ts rename to src/pages/api/breweries/[postId]/beers/index.ts index b3a51f0..62c0564 100644 --- a/src/pages/api/breweries/[id]/beers/index.ts +++ b/src/pages/api/breweries/[postId]/beers/index.ts @@ -15,7 +15,7 @@ const router = createRouter< router.get( validateRequest({ - querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), + querySchema: PaginatedQueryResponseSchema.extend({ postId: z.string().cuid() }), }), getAllBeersByBrewery, ); diff --git a/src/pages/api/brewery-comments/[id].ts b/src/pages/api/breweries/[postId]/comments/[commentId].ts similarity index 87% rename from src/pages/api/brewery-comments/[id].ts rename to src/pages/api/breweries/[postId]/comments/[commentId].ts index 57e13c4..9865356 100644 --- a/src/pages/api/brewery-comments/[id].ts +++ b/src/pages/api/breweries/[postId]/comments/[commentId].ts @@ -24,7 +24,7 @@ const router = createRouter< router .delete( validateRequest({ - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ commentId: z.string().cuid(), postId: z.string().cuid() }), }), getCurrentUser, checkIfBreweryCommentOwner, @@ -32,7 +32,7 @@ router ) .put( validateRequest({ - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ commentId: z.string().cuid(), postId: z.string().cuid() }), bodySchema: CreateCommentValidationSchema, }), getCurrentUser, diff --git a/src/pages/api/breweries/[id]/comments/index.ts b/src/pages/api/breweries/[postId]/comments/index.ts similarity index 88% rename from src/pages/api/breweries/[id]/comments/index.ts rename to src/pages/api/breweries/[postId]/comments/index.ts index 338ff16..c97b67b 100644 --- a/src/pages/api/breweries/[id]/comments/index.ts +++ b/src/pages/api/breweries/[postId]/comments/index.ts @@ -20,7 +20,7 @@ const router = createRouter< router.post( validateRequest({ bodySchema: CreateCommentValidationSchema, - querySchema: z.object({ id: z.string().cuid() }), + querySchema: z.object({ postId: z.string().cuid() }), }), getCurrentUser, createComment, @@ -28,7 +28,7 @@ router.post( router.get( validateRequest({ - querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }), + querySchema: PaginatedQueryResponseSchema.extend({ postId: z.string().cuid() }), }), getAll, ); diff --git a/src/pages/api/breweries/[id]/images/index.ts b/src/pages/api/breweries/[postId]/images/index.ts similarity index 100% rename from src/pages/api/breweries/[id]/images/index.ts rename to src/pages/api/breweries/[postId]/images/index.ts diff --git a/src/pages/api/breweries/[id]/index.ts b/src/pages/api/breweries/[postId]/index.ts similarity index 100% rename from src/pages/api/breweries/[id]/index.ts rename to src/pages/api/breweries/[postId]/index.ts diff --git a/src/pages/api/breweries/[id]/like/index.ts b/src/pages/api/breweries/[postId]/like/index.ts similarity index 85% rename from src/pages/api/breweries/[id]/like/index.ts rename to src/pages/api/breweries/[postId]/like/index.ts index 40b6c14..1bdc931 100644 --- a/src/pages/api/breweries/[id]/like/index.ts +++ b/src/pages/api/breweries/[postId]/like/index.ts @@ -19,12 +19,12 @@ const router = createRouter< router.post( getCurrentUser, - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), sendBreweryPostLikeRequest, ); router.get( - validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), getBreweryPostLikeCount, ); diff --git a/src/pages/api/breweries/[id]/like/is-liked.ts b/src/pages/api/breweries/[postId]/like/is-liked.ts similarity index 90% rename from src/pages/api/breweries/[id]/like/is-liked.ts rename to src/pages/api/breweries/[postId]/like/is-liked.ts index 4a41897..16e97bf 100644 --- a/src/pages/api/breweries/[id]/like/is-liked.ts +++ b/src/pages/api/breweries/[postId]/like/is-liked.ts @@ -16,11 +16,7 @@ const router = createRouter< router.get( getCurrentUser, - validateRequest({ - querySchema: z.object({ - id: z.string().cuid(), - }), - }), + validateRequest({ querySchema: z.object({ postId: z.string().cuid() }) }), getBreweryPostLikeStatus, ); diff --git a/src/requests/comments/beer-comment/index.ts b/src/requests/comments/beer-comment/index.ts index 7c2d015..d922500 100644 --- a/src/requests/comments/beer-comment/index.ts +++ b/src/requests/comments/beer-comment/index.ts @@ -9,8 +9,9 @@ import { export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async ({ body, commentId, + beerPostId, }) => { - const response = await fetch(`/api/beer-post-comments/${commentId}`, { + const response = await fetch(`/api/beers/${beerPostId}/comments/${commentId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), @@ -33,8 +34,9 @@ export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async export const deleteBeerPostCommentRequest: SendDeleteBeerPostCommentRequest = async ({ commentId, + beerPostId, }) => { - const response = await fetch(`/api/beer-post-comments/${commentId}`, { + const response = await fetch(`/api/beers/${beerPostId}/comments/${commentId}`, { method: 'DELETE', }); diff --git a/src/requests/comments/beer-comment/types/index.ts b/src/requests/comments/beer-comment/types/index.ts index b790ed6..45f8d96 100644 --- a/src/requests/comments/beer-comment/types/index.ts +++ b/src/requests/comments/beer-comment/types/index.ts @@ -5,10 +5,12 @@ import { z } from 'zod'; export type SendEditBeerPostCommentRequest = (args: { body: { content: string; rating: number }; commentId: string; + beerPostId: string; }) => Promise>; export type SendDeleteBeerPostCommentRequest = (args: { commentId: string; + beerPostId: string; }) => Promise>; export type SendCreateBeerCommentRequest = (args: { From 623855682bedc868032615e37d1943f8e55e2d30 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 25 Dec 2023 14:26:35 -0500 Subject: [PATCH 05/13] Refactor: further refactoring of api requests. --- .gitignore | 1 + .../BeerStyleById/BeerStyleCommentForm.tsx | 5 +- .../BeerStyleById/BeerStyleCommentSection.tsx | 24 ++---- .../BreweryById/BreweryCommentsSection.tsx | 1 - .../api/breweries/[postId]/like/index.ts | 4 +- .../api/breweries/[postId]/like/is-liked.ts | 4 +- src/requests/comments/beer-comment/index.ts | 37 ++++++-- .../comments/beer-style-comment/index.ts | 85 +++++++++++++++++++ .../sendCreateBeerStyleCommentRequest.ts | 53 ------------ .../beer-style-comment/types/index.ts | 19 +++++ 10 files changed, 152 insertions(+), 81 deletions(-) create mode 100644 src/requests/comments/beer-style-comment/index.ts delete mode 100644 src/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest.ts create mode 100644 src/requests/comments/beer-style-comment/types/index.ts diff --git a/.gitignore b/.gitignore index 2306282..a87bdfa 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ next-env.d.ts /cloudinary-images +.obsidian \ No newline at end of file diff --git a/src/components/BeerStyleById/BeerStyleCommentForm.tsx b/src/components/BeerStyleById/BeerStyleCommentForm.tsx index 7ab0df9..d6376eb 100644 --- a/src/components/BeerStyleById/BeerStyleCommentForm.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentForm.tsx @@ -10,7 +10,7 @@ import createErrorToast from '@/util/createErrorToast'; import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; -import sendCreateBeerStyleCommentRequest from '@/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest'; +import { sendCreateBeerStyleCommentRequest } from '@/requests/comments/beer-style-comment'; import CommentForm from '../ui/CommentForm'; interface BeerCommentFormProps { @@ -35,8 +35,7 @@ const BeerStyleCommentForm: FunctionComponent = ({ const loadingToast = toast.loading('Posting a new comment...'); try { await sendCreateBeerStyleCommentRequest({ - content: data.content, - rating: data.rating, + body: { content: data.content, rating: data.rating }, beerStyleId: beerStyle.id, }); reset(); diff --git a/src/components/BeerStyleById/BeerStyleCommentSection.tsx b/src/components/BeerStyleById/BeerStyleCommentSection.tsx index 470ee7e..7d742f9 100644 --- a/src/components/BeerStyleById/BeerStyleCommentSection.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentSection.tsx @@ -10,6 +10,10 @@ import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useB import LoadingComponent from '../BeerById/LoadingComponent'; import CommentsComponent from '../ui/CommentsComponent'; import BeerStyleCommentForm from './BeerStyleCommentForm'; +import { + sendDeleteBeerStyleCommentRequest, + sendEditBeerStyleCommentRequest, +} from '@/requests/comments/beer-style-comment'; interface BeerStyleCommentsSectionProps { beerStyle: z.infer; @@ -28,28 +32,18 @@ const BeerStyleCommentsSection: FC = ({ beerStyle const commentSectionRef: MutableRefObject = useRef(null); const handleDeleteRequest = async (id: string) => { - const response = await fetch(`/api/beers/styles/${beerStyle.id}/comments/${id}`, { - method: 'DELETE', - }); - - if (!response.ok) { - throw new Error('Failed to delete comment.'); - } + await sendDeleteBeerStyleCommentRequest({ beerStyleId: beerStyle.id, commentId: id }); }; const handleEditRequest = async ( id: string, data: z.infer, ) => { - const response = await fetch(`/api/beers/styles/${beerStyle.id}/comments/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content: data.content, rating: data.rating }), + await sendEditBeerStyleCommentRequest({ + beerStyleId: beerStyle.id, + commentId: id, + body: data, }); - - if (!response.ok) { - throw new Error(response.statusText); - } }; return ( diff --git a/src/components/BreweryById/BreweryCommentsSection.tsx b/src/components/BreweryById/BreweryCommentsSection.tsx index 1ecbcfc..a40da94 100644 --- a/src/components/BreweryById/BreweryCommentsSection.tsx +++ b/src/components/BreweryById/BreweryCommentsSection.tsx @@ -57,7 +57,6 @@ const BreweryCommentsSection: FC = ({ breweryPost }) => if (!response.ok) { throw new Error(response.statusText); } - console.log(await response.json()); }; return ( diff --git a/src/pages/api/breweries/[postId]/like/index.ts b/src/pages/api/breweries/[postId]/like/index.ts index 1bdc931..5073764 100644 --- a/src/pages/api/breweries/[postId]/like/index.ts +++ b/src/pages/api/breweries/[postId]/like/index.ts @@ -1,4 +1,3 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; @@ -6,6 +5,7 @@ import { sendBreweryPostLikeRequest, getBreweryPostLikeCount, } from '@/controllers/likes/brewery-post-likes'; +import { LikeRequest } from '@/controllers/likes/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; @@ -13,7 +13,7 @@ import { createRouter } from 'next-connect'; import { z } from 'zod'; const router = createRouter< - UserExtendedNextApiRequest, + LikeRequest, NextApiResponse> >(); diff --git a/src/pages/api/breweries/[postId]/like/is-liked.ts b/src/pages/api/breweries/[postId]/like/is-liked.ts index 16e97bf..c169b1b 100644 --- a/src/pages/api/breweries/[postId]/like/is-liked.ts +++ b/src/pages/api/breweries/[postId]/like/is-liked.ts @@ -1,8 +1,8 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { getBreweryPostLikeStatus } from '@/controllers/likes/brewery-post-likes'; +import { LikeRequest } from '@/controllers/likes/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; @@ -10,7 +10,7 @@ import { createRouter } from 'next-connect'; import { z } from 'zod'; const router = createRouter< - UserExtendedNextApiRequest, + LikeRequest, NextApiResponse> >(); diff --git a/src/requests/comments/beer-comment/index.ts b/src/requests/comments/beer-comment/index.ts index d922500..dc39c1b 100644 --- a/src/requests/comments/beer-comment/index.ts +++ b/src/requests/comments/beer-comment/index.ts @@ -6,6 +6,16 @@ import { SendEditBeerPostCommentRequest, } from './types'; +/** + * Sends an api request to edit a beer post comment. + * + * @param params - The parameters for the request. + * @param params.body - The body of the request. + * @param params.commentId - The id of the comment to edit. + * @param params.beerPostId - The id of the beer post the comment belongs to. + * @returns The edited comment. + * @throws An error if the request fails or the response is invalid. + */ export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async ({ body, commentId, @@ -22,7 +32,6 @@ export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async } const json = await response.json(); - const parsed = APIResponseValidationSchema.safeParse(json); if (!parsed.success) { @@ -32,6 +41,15 @@ export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async return parsed.data; }; +/** + * Sends an api request to delete a beer post comment. + * + * @param params - The parameters for the request. + * @param params.commentId - The id of the comment to delete. + * @param params.beerPostId - The id of the beer post the comment belongs to. + * @returns The deleted comment. + * @throws An error if the request fails or the response is invalid. + */ export const deleteBeerPostCommentRequest: SendDeleteBeerPostCommentRequest = async ({ commentId, beerPostId, @@ -45,7 +63,6 @@ export const deleteBeerPostCommentRequest: SendDeleteBeerPostCommentRequest = as } const json = await response.json(); - const parsed = APIResponseValidationSchema.safeParse(json); if (!parsed.success) { @@ -55,22 +72,32 @@ export const deleteBeerPostCommentRequest: SendDeleteBeerPostCommentRequest = as return parsed.data; }; +/** + * Send an api request to create a comment on a beer post. + * + * @param params - The parameters for the request. + * @param params.beerPostId - The id of the beer post to create the comment on. + * @param params.body - The body of the request. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + * @returns The created comment. + * @throws An error if the request fails or the response is invalid. + */ export const sendCreateBeerCommentRequest: SendCreateBeerCommentRequest = async ({ beerPostId, - body, + body: { content, rating }, }) => { - const { content, rating } = body; const response = await fetch(`/api/beers/${beerPostId}/comments`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ beerPostId, content, rating }), }); + if (!response.ok) { throw new Error(response.statusText); } const data = await response.json(); - const parsedResponse = APIResponseValidationSchema.safeParse(data); if (!parsedResponse.success) { diff --git a/src/requests/comments/beer-style-comment/index.ts b/src/requests/comments/beer-style-comment/index.ts new file mode 100644 index 0000000..5c8629c --- /dev/null +++ b/src/requests/comments/beer-style-comment/index.ts @@ -0,0 +1,85 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { + SendCreateBeerStyleCommentRequest, + SendDeleteBeerStyleCommentRequest, + SendEditBeerStyleCommentRequest, +} from './types'; + +export const sendCreateBeerStyleCommentRequest: SendCreateBeerStyleCommentRequest = + async ({ beerStyleId, body: { content, rating } }) => { + const response = await fetch(`/api/beers/styles/${beerStyleId}/comments`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ beerStyleId, content, rating }), + }); + if (!response.ok) { + throw new Error(response.statusText); + } + + const data = await response.json(); + + const parsedResponse = APIResponseValidationSchema.safeParse(data); + + if (!parsedResponse.success) { + throw new Error('Invalid API response'); + } + + const parsedPayload = CommentQueryResult.safeParse(parsedResponse.data.payload); + + if (!parsedPayload.success) { + throw new Error('Invalid API response payload'); + } + + return parsedPayload.data; + }; + +export const sendEditBeerStyleCommentRequest: SendEditBeerStyleCommentRequest = async ({ + commentId, + body: { content, rating }, + beerStyleId, +}) => { + const response = await fetch(`/api/beers/styles/${beerStyleId}/comments/${commentId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content, rating }), + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('Invalid API response'); + } + + return parsed.data; +}; + +export const sendDeleteBeerStyleCommentRequest: SendDeleteBeerStyleCommentRequest = + async ({ beerStyleId, commentId }) => { + const response = await fetch( + `/api/beers/styles/${beerStyleId}/comments/${commentId}`, + { + method: 'DELETE', + }, + ); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('Invalid API response'); + } + + return parsed.data; + }; diff --git a/src/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest.ts b/src/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest.ts deleted file mode 100644 index 89a0cd9..0000000 --- a/src/requests/comments/beer-style-comment/sendCreateBeerStyleCommentRequest.ts +++ /dev/null @@ -1,53 +0,0 @@ -import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; -import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; - -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -const BeerStyleCommentValidationSchemaWithId = CreateCommentValidationSchema.extend({ - beerStyleId: z.string().cuid(), -}); - -/** - * Sends a POST request to the server to create a new beer comment. - * - * @param data The data to be sent to the server. - * @param data.beerPostId The ID of the beer post to comment on. - * @param data.content The content of the comment. - * @param data.rating The rating of the beer. - * @returns A promise that resolves to the created comment. - * @throws An error if the request fails, the API response is invalid, or the API response - * payload is invalid. - */ -const sendCreateBeerStyleCommentRequest = async ({ - beerStyleId, - content, - rating, -}: z.infer) => { - const response = await fetch(`/api/beers/styles/${beerStyleId}/comments`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ beerStyleId, content, rating }), - }); - if (!response.ok) { - throw new Error(response.statusText); - } - - const data = await response.json(); - - const parsedResponse = APIResponseValidationSchema.safeParse(data); - - if (!parsedResponse.success) { - throw new Error('Invalid API response'); - } - - const parsedPayload = CommentQueryResult.safeParse(parsedResponse.data.payload); - - if (!parsedPayload.success) { - throw new Error('Invalid API response payload'); - } - - return parsedPayload.data; -}; - -export default sendCreateBeerStyleCommentRequest; diff --git a/src/requests/comments/beer-style-comment/types/index.ts b/src/requests/comments/beer-style-comment/types/index.ts new file mode 100644 index 0000000..e9b695a --- /dev/null +++ b/src/requests/comments/beer-style-comment/types/index.ts @@ -0,0 +1,19 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +export type SendEditBeerStyleCommentRequest = (args: { + body: { content: string; rating: number }; + commentId: string; + beerStyleId: string; +}) => Promise>; + +export type SendDeleteBeerStyleCommentRequest = (args: { + commentId: string; + beerStyleId: string; +}) => Promise>; + +export type SendCreateBeerStyleCommentRequest = (args: { + beerStyleId: string; + body: { content: string; rating: number }; +}) => Promise>; From 2b90bb2e5df4cc0dd93411fa07ae039be7a56d72 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 25 Dec 2023 21:04:15 -0500 Subject: [PATCH 06/13] Refactor: update comment components --- src/components/BeerById/BeerCommentForm.tsx | 2 +- .../BeerById/BeerPostCommentsSection.tsx | 38 +++-- .../BeerStyleById/BeerStyleCommentForm.tsx | 2 +- .../BeerStyleById/BeerStyleCommentSection.tsx | 39 +++--- .../BreweryById/BreweryCommentForm.tsx | 2 +- .../BreweryById/BreweryCommentsSection.tsx | 55 +++----- .../CommentCardBody.tsx | 18 ++- .../CommentCardDropdown.tsx | 0 .../CommentContentBody.tsx | 0 .../{ui => Comments}/CommentForm.tsx | 12 +- .../CommentLoadingCardBody.tsx | 0 .../CommentLoadingComponent.tsx} | 8 +- .../{ui => Comments}/CommentsComponent.tsx | 36 ++--- .../EditCommentBody.tsx | 30 ++-- .../{BeerById => Comments}/NoCommentsCard.tsx | 0 src/components/Comments/types/index.ts | 12 ++ src/requests/comments/beer-comment/index.ts | 6 +- .../comments/beer-style-comment/index.ts | 132 +++++++++++------- .../comments/brewery-comment/index.ts | 109 +++++++++++++++ .../comments/brewery-comment/types/index.ts | 19 +++ 20 files changed, 331 insertions(+), 189 deletions(-) rename src/components/{BeerBreweryComments => Comments}/CommentCardBody.tsx (76%) rename src/components/{BeerBreweryComments => Comments}/CommentCardDropdown.tsx (100%) rename src/components/{BeerBreweryComments => Comments}/CommentContentBody.tsx (100%) rename src/components/{ui => Comments}/CommentForm.tsx (88%) rename src/components/{BeerBreweryComments => Comments}/CommentLoadingCardBody.tsx (100%) rename src/components/{BeerById/LoadingComponent.tsx => Comments/CommentLoadingComponent.tsx} (56%) rename src/components/{ui => Comments}/CommentsComponent.tsx (82%) rename src/components/{BeerBreweryComments => Comments}/EditCommentBody.tsx (87%) rename src/components/{BeerById => Comments}/NoCommentsCard.tsx (100%) create mode 100644 src/components/Comments/types/index.ts create mode 100644 src/requests/comments/brewery-comment/index.ts create mode 100644 src/requests/comments/brewery-comment/types/index.ts diff --git a/src/components/BeerById/BeerCommentForm.tsx b/src/components/BeerById/BeerCommentForm.tsx index cbd4a64..a1bf202 100644 --- a/src/components/BeerById/BeerCommentForm.tsx +++ b/src/components/BeerById/BeerCommentForm.tsx @@ -10,7 +10,7 @@ import CreateCommentValidationSchema from '@/services/schema/CommentSchema/Creat import toast from 'react-hot-toast'; import createErrorToast from '@/util/createErrorToast'; import { sendCreateBeerCommentRequest } from '@/requests/comments/beer-comment'; -import CommentForm from '../ui/CommentForm'; +import CommentForm from '../Comments/CommentForm'; interface BeerCommentFormProps { beerPost: z.infer; diff --git a/src/components/BeerById/BeerPostCommentsSection.tsx b/src/components/BeerById/BeerPostCommentsSection.tsx index f8f6738..d2cee4a 100644 --- a/src/components/BeerById/BeerPostCommentsSection.tsx +++ b/src/components/BeerById/BeerPostCommentsSection.tsx @@ -6,15 +6,15 @@ import { FC, MutableRefObject, useContext, useRef } from 'react'; import { z } from 'zod'; import useBeerPostComments from '@/hooks/data-fetching/beer-comments/useBeerPostComments'; import { useRouter } from 'next/router'; -import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; + import { deleteBeerPostCommentRequest, editBeerPostCommentRequest, } from '@/requests/comments/beer-comment'; import BeerCommentForm from './BeerCommentForm'; -import LoadingComponent from './LoadingComponent'; -import CommentsComponent from '../ui/CommentsComponent'; +import CommentLoadingComponent from '../Comments/CommentLoadingComponent'; +import CommentsComponent from '../Comments/CommentsComponent'; interface BeerPostCommentsSectionProps { beerPost: z.infer; @@ -32,21 +32,6 @@ const BeerPostCommentsSection: FC = ({ beerPost }) const commentSectionRef: MutableRefObject = useRef(null); - const handleDeleteRequest = async (id: string) => { - await deleteBeerPostCommentRequest({ commentId: id, beerPostId: beerPost.id }); - }; - - const handleEditRequest = async ( - id: string, - data: z.infer, - ) => { - await editBeerPostCommentRequest({ - body: data, - commentId: id, - beerPostId: beerPost.id, - }); - }; - return (
@@ -68,7 +53,7 @@ const BeerPostCommentsSection: FC = ({ beerPost }) */ isLoading ? (
- +
) : ( = ({ beerPost }) setSize={setSize} size={size} mutate={mutate} - handleDeleteRequest={handleDeleteRequest} - handleEditRequest={handleEditRequest} + handleDeleteCommentRequest={(id) => { + return deleteBeerPostCommentRequest({ + commentId: id, + beerPostId: beerPost.id, + }); + }} + handleEditCommentRequest={(id, data) => { + return editBeerPostCommentRequest({ + body: data, + commentId: id, + beerPostId: beerPost.id, + }); + }} /> ) } diff --git a/src/components/BeerStyleById/BeerStyleCommentForm.tsx b/src/components/BeerStyleById/BeerStyleCommentForm.tsx index d6376eb..829ee33 100644 --- a/src/components/BeerStyleById/BeerStyleCommentForm.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentForm.tsx @@ -11,7 +11,7 @@ import createErrorToast from '@/util/createErrorToast'; import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; import { sendCreateBeerStyleCommentRequest } from '@/requests/comments/beer-style-comment'; -import CommentForm from '../ui/CommentForm'; +import CommentForm from '../Comments/CommentForm'; interface BeerCommentFormProps { beerStyle: z.infer; diff --git a/src/components/BeerStyleById/BeerStyleCommentSection.tsx b/src/components/BeerStyleById/BeerStyleCommentSection.tsx index 7d742f9..aa597c9 100644 --- a/src/components/BeerStyleById/BeerStyleCommentSection.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentSection.tsx @@ -3,17 +3,16 @@ import UserContext from '@/contexts/UserContext'; import { FC, MutableRefObject, useContext, useRef } from 'react'; import { z } from 'zod'; import { useRouter } from 'next/router'; -import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; -import LoadingComponent from '../BeerById/LoadingComponent'; -import CommentsComponent from '../ui/CommentsComponent'; -import BeerStyleCommentForm from './BeerStyleCommentForm'; import { sendDeleteBeerStyleCommentRequest, sendEditBeerStyleCommentRequest, } from '@/requests/comments/beer-style-comment'; +import CommentLoadingComponent from '../Comments/CommentLoadingComponent'; +import CommentsComponent from '../Comments/CommentsComponent'; +import BeerStyleCommentForm from './BeerStyleCommentForm'; interface BeerStyleCommentsSectionProps { beerStyle: z.infer; @@ -31,21 +30,6 @@ const BeerStyleCommentsSection: FC = ({ beerStyle const commentSectionRef: MutableRefObject = useRef(null); - const handleDeleteRequest = async (id: string) => { - await sendDeleteBeerStyleCommentRequest({ beerStyleId: beerStyle.id, commentId: id }); - }; - - const handleEditRequest = async ( - id: string, - data: z.infer, - ) => { - await sendEditBeerStyleCommentRequest({ - beerStyleId: beerStyle.id, - commentId: id, - body: data, - }); - }; - return (
@@ -67,7 +51,7 @@ const BeerStyleCommentsSection: FC = ({ beerStyle */ isLoading ? (
- +
) : ( = ({ beerStyle setSize={setSize} size={size} mutate={mutate} - handleDeleteRequest={handleDeleteRequest} - handleEditRequest={handleEditRequest} + handleDeleteCommentRequest={(id) => { + return sendDeleteBeerStyleCommentRequest({ + beerStyleId: beerStyle.id, + commentId: id, + }); + }} + handleEditCommentRequest={(id, data) => { + return sendEditBeerStyleCommentRequest({ + beerStyleId: beerStyle.id, + commentId: id, + body: data, + }); + }} /> ) } diff --git a/src/components/BreweryById/BreweryCommentForm.tsx b/src/components/BreweryById/BreweryCommentForm.tsx index a42ada7..1ef4cbc 100644 --- a/src/components/BreweryById/BreweryCommentForm.tsx +++ b/src/components/BreweryById/BreweryCommentForm.tsx @@ -8,7 +8,7 @@ import toast from 'react-hot-toast'; import { z } from 'zod'; import sendCreateBreweryCommentRequest from '@/requests/comments/brewery-comment/sendCreateBreweryCommentRequest'; import createErrorToast from '@/util/createErrorToast'; -import CommentForm from '../ui/CommentForm'; +import CommentForm from '../Comments/CommentForm'; interface BreweryCommentFormProps { breweryPost: z.infer; diff --git a/src/components/BreweryById/BreweryCommentsSection.tsx b/src/components/BreweryById/BreweryCommentsSection.tsx index a40da94..7da4df5 100644 --- a/src/components/BreweryById/BreweryCommentsSection.tsx +++ b/src/components/BreweryById/BreweryCommentsSection.tsx @@ -2,11 +2,14 @@ import UserContext from '@/contexts/UserContext'; import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { FC, MutableRefObject, useContext, useRef } from 'react'; import { z } from 'zod'; -import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import useBreweryPostComments from '@/hooks/data-fetching/brewery-comments/useBreweryPostComments'; -import LoadingComponent from '../BeerById/LoadingComponent'; -import CommentsComponent from '../ui/CommentsComponent'; +import { + sendDeleteBreweryPostCommentRequest, + sendEditBreweryPostCommentRequest, +} from '@/requests/comments/brewery-comment'; +import CommentLoadingComponent from '../Comments/CommentLoadingComponent'; +import CommentsComponent from '../Comments/CommentsComponent'; import BreweryCommentForm from './BreweryCommentForm'; interface BreweryBeerSectionProps { @@ -30,35 +33,6 @@ const BreweryCommentsSection: FC = ({ breweryPost }) => const commentSectionRef: MutableRefObject = useRef(null); - const handleDeleteRequest = async (commentId: string) => { - const response = await fetch( - `/api/breweries/${breweryPost.id}/comments/${commentId}`, - { method: 'DELETE' }, - ); - - if (!response.ok) { - throw new Error(response.statusText); - } - }; - - const handleEditRequest = async ( - commentId: string, - data: z.infer, - ) => { - const response = await fetch( - `/api/breweries/${breweryPost.id}/comments/${commentId}`, - { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content: data.content, rating: data.rating }), - }, - ); - - if (!response.ok) { - throw new Error(response.statusText); - } - }; - return (
@@ -79,7 +53,7 @@ const BreweryCommentsSection: FC = ({ breweryPost }) => */ isLoading ? (
- +
) : ( = ({ breweryPost }) => size={size} commentSectionRef={commentSectionRef} mutate={mutate} - handleDeleteRequest={handleDeleteRequest} - handleEditRequest={handleEditRequest} + handleDeleteCommentRequest={(id) => { + return sendDeleteBreweryPostCommentRequest({ + breweryPostId: breweryPost.id, + commentId: id, + }); + }} + handleEditCommentRequest={(commentId, data) => { + return sendEditBreweryPostCommentRequest({ + breweryPostId: breweryPost.id, + commentId, + body: { content: data.content, rating: data.rating }, + }); + }} /> ) } diff --git a/src/components/BeerBreweryComments/CommentCardBody.tsx b/src/components/Comments/CommentCardBody.tsx similarity index 76% rename from src/components/BeerBreweryComments/CommentCardBody.tsx rename to src/components/Comments/CommentCardBody.tsx index 2c57d4d..74c0e0a 100644 --- a/src/components/BeerBreweryComments/CommentCardBody.tsx +++ b/src/components/Comments/CommentCardBody.tsx @@ -3,28 +3,26 @@ import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResu import { FC, useState } from 'react'; import { useInView } from 'react-intersection-observer'; import { z } from 'zod'; -import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; + import CommentContentBody from './CommentContentBody'; import EditCommentBody from './EditCommentBody'; import UserAvatar from '../Account/UserAvatar'; +import { HandleDeleteCommentRequest, HandleEditCommentRequest } from './types'; interface CommentCardProps { comment: z.infer; mutate: ReturnType['mutate']; ref?: ReturnType['ref']; - handleDeleteRequest: (id: string) => Promise; - handleEditRequest: ( - id: string, - data: z.infer, - ) => Promise; + handleDeleteCommentRequest: HandleDeleteCommentRequest; + handleEditCommentRequest: HandleEditCommentRequest; } const CommentCardBody: FC = ({ comment, mutate, ref, - handleDeleteRequest, - handleEditRequest, + handleDeleteCommentRequest, + handleEditCommentRequest, }) => { const [inEditMode, setInEditMode] = useState(false); @@ -44,8 +42,8 @@ const CommentCardBody: FC = ({ comment={comment} mutate={mutate} setInEditMode={setInEditMode} - handleDeleteRequest={handleDeleteRequest} - handleEditRequest={handleEditRequest} + handleDeleteCommentRequest={handleDeleteCommentRequest} + handleEditCommentRequest={handleEditCommentRequest} /> )}
diff --git a/src/components/BeerBreweryComments/CommentCardDropdown.tsx b/src/components/Comments/CommentCardDropdown.tsx similarity index 100% rename from src/components/BeerBreweryComments/CommentCardDropdown.tsx rename to src/components/Comments/CommentCardDropdown.tsx diff --git a/src/components/BeerBreweryComments/CommentContentBody.tsx b/src/components/Comments/CommentContentBody.tsx similarity index 100% rename from src/components/BeerBreweryComments/CommentContentBody.tsx rename to src/components/Comments/CommentContentBody.tsx diff --git a/src/components/ui/CommentForm.tsx b/src/components/Comments/CommentForm.tsx similarity index 88% rename from src/components/ui/CommentForm.tsx rename to src/components/Comments/CommentForm.tsx index f091cd6..62102f0 100644 --- a/src/components/ui/CommentForm.tsx +++ b/src/components/Comments/CommentForm.tsx @@ -8,12 +8,12 @@ import type { UseFormSetValue, UseFormWatch, } from 'react-hook-form'; -import FormError from './forms/FormError'; -import FormInfo from './forms/FormInfo'; -import FormLabel from './forms/FormLabel'; -import FormSegment from './forms/FormSegment'; -import FormTextArea from './forms/FormTextArea'; -import Button from './forms/Button'; +import FormError from '../ui/forms/FormError'; +import FormInfo from '../ui/forms/FormInfo'; +import FormLabel from '../ui/forms/FormLabel'; +import FormSegment from '../ui/forms/FormSegment'; +import FormTextArea from '../ui/forms/FormTextArea'; +import Button from '../ui/forms/Button'; interface Comment { content: string; diff --git a/src/components/BeerBreweryComments/CommentLoadingCardBody.tsx b/src/components/Comments/CommentLoadingCardBody.tsx similarity index 100% rename from src/components/BeerBreweryComments/CommentLoadingCardBody.tsx rename to src/components/Comments/CommentLoadingCardBody.tsx diff --git a/src/components/BeerById/LoadingComponent.tsx b/src/components/Comments/CommentLoadingComponent.tsx similarity index 56% rename from src/components/BeerById/LoadingComponent.tsx rename to src/components/Comments/CommentLoadingComponent.tsx index fc2dca4..4d55920 100644 --- a/src/components/BeerById/LoadingComponent.tsx +++ b/src/components/Comments/CommentLoadingComponent.tsx @@ -1,12 +1,12 @@ import { FC } from 'react'; import Spinner from '../ui/Spinner'; -import CommentLoadingCardBody from '../BeerBreweryComments/CommentLoadingCardBody'; +import CommentLoadingCardBody from './CommentLoadingCardBody'; -interface LoadingComponentProps { +interface CommentLoadingComponentProps { length: number; } -const LoadingComponent: FC = ({ length }) => { +const CommentLoadingComponent: FC = ({ length }) => { return ( <> {Array.from({ length }).map((_, i) => ( @@ -19,4 +19,4 @@ const LoadingComponent: FC = ({ length }) => { ); }; -export default LoadingComponent; +export default CommentLoadingComponent; diff --git a/src/components/ui/CommentsComponent.tsx b/src/components/Comments/CommentsComponent.tsx similarity index 82% rename from src/components/ui/CommentsComponent.tsx rename to src/components/Comments/CommentsComponent.tsx index 64c1d10..18a0d4d 100644 --- a/src/components/ui/CommentsComponent.tsx +++ b/src/components/Comments/CommentsComponent.tsx @@ -7,39 +7,33 @@ import useBeerPostComments from '@/hooks/data-fetching/beer-comments/useBeerPost import useBreweryPostComments from '@/hooks/data-fetching/brewery-comments/useBreweryPostComments'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; -import NoCommentsCard from '../BeerById/NoCommentsCard'; -import LoadingComponent from '../BeerById/LoadingComponent'; -import CommentCardBody from '../BeerBreweryComments/CommentCardBody'; +import NoCommentsCard from './NoCommentsCard'; +import CommentLoadingComponent from './CommentLoadingComponent'; +import CommentCardBody from './CommentCardBody'; +import { HandleDeleteCommentRequest, HandleEditCommentRequest } from './types'; type HookReturnType = ReturnType< typeof useBeerPostComments | typeof useBreweryPostComments | typeof useBeerStyleComments >; -type HandleDeleteRequest = (id: string) => Promise; - -type HandleEditRequest = ( - id: string, - data: { content: string; rating: number }, -) => Promise; - interface CommentsComponentProps { comments: HookReturnType['comments']; - commentSectionRef: MutableRefObject; - handleDeleteRequest: HandleDeleteRequest; - handleEditRequest: HandleEditRequest; isAtEnd: HookReturnType['isAtEnd']; isLoadingMore: HookReturnType['isLoadingMore']; mutate: HookReturnType['mutate']; - pageSize: number; setSize: HookReturnType['setSize']; size: HookReturnType['size']; + commentSectionRef: MutableRefObject; + handleDeleteCommentRequest: HandleDeleteCommentRequest; + handleEditCommentRequest: HandleEditCommentRequest; + pageSize: number; } const CommentsComponent: FC = ({ comments, commentSectionRef, - handleDeleteRequest, - handleEditRequest, + handleDeleteCommentRequest, + handleEditCommentRequest, isAtEnd, isLoadingMore, mutate, @@ -50,8 +44,8 @@ const CommentsComponent: FC = ({ const { ref: penultimateCommentRef } = useInView({ threshold: 0.1, /** - * When the last comment comes into view, call setSize from useBeerPostComments to - * load more comments. + * When the last comment comes into view, call setSize from the comment fetching hook + * to load more comments. */ onChange: (visible) => { if (!visible || isAtEnd) return; @@ -78,8 +72,8 @@ const CommentsComponent: FC = ({
); @@ -90,7 +84,7 @@ const CommentsComponent: FC = ({ * If there are more comments to load, show a loading component with a * skeleton loader and a loading spinner. */ - !!isLoadingMore && + !!isLoadingMore && } { diff --git a/src/components/BeerBreweryComments/EditCommentBody.tsx b/src/components/Comments/EditCommentBody.tsx similarity index 87% rename from src/components/BeerBreweryComments/EditCommentBody.tsx rename to src/components/Comments/EditCommentBody.tsx index e638baf..cb5d985 100644 --- a/src/components/BeerBreweryComments/EditCommentBody.tsx +++ b/src/components/Comments/EditCommentBody.tsx @@ -14,6 +14,7 @@ import FormInfo from '../ui/forms/FormInfo'; import FormLabel from '../ui/forms/FormLabel'; import FormSegment from '../ui/forms/FormSegment'; import FormTextArea from '../ui/forms/FormTextArea'; +import { HandleDeleteCommentRequest, HandleEditCommentRequest } from './types'; interface EditCommentBodyProps { comment: z.infer; @@ -22,19 +23,16 @@ interface EditCommentBodyProps { mutate: ReturnType< typeof useBeerPostComments | typeof useBreweryPostComments >['mutate']; - handleDeleteRequest: (id: string) => Promise; - handleEditRequest: ( - id: string, - data: z.infer, - ) => Promise; + handleDeleteCommentRequest: HandleDeleteCommentRequest; + handleEditCommentRequest: HandleEditCommentRequest; } const EditCommentBody: FC = ({ comment, setInEditMode, mutate, - handleDeleteRequest, - handleEditRequest, + handleDeleteCommentRequest, + handleEditCommentRequest, }) => { const { register, handleSubmit, formState, setValue, watch } = useForm< z.infer @@ -51,7 +49,7 @@ const EditCommentBody: FC = ({ const loadingToast = toast.loading('Deleting comment...'); setIsDeleting(true); try { - await handleDeleteRequest(comment.id); + await handleDeleteCommentRequest(comment.id); await mutate(); toast.remove(loadingToast); toast.success('Deleted comment.'); @@ -68,7 +66,7 @@ const EditCommentBody: FC = ({ try { setInEditMode(true); - await handleEditRequest(comment.id, data); + await handleEditCommentRequest(comment.id, data); await mutate(); toast.remove(loadingToast); toast.success('Comment edits submitted successfully.'); @@ -80,6 +78,8 @@ const EditCommentBody: FC = ({ } }; + const disableForm = isSubmitting || isDeleting; + return (
@@ -95,7 +95,7 @@ const EditCommentBody: FC = ({ placeholder="Comment" rows={2} error={!!errors.content?.message} - disabled={isSubmitting || isDeleting} + disabled={disableForm} />
@@ -114,8 +114,8 @@ const EditCommentBody: FC = ({ ))} @@ -125,7 +125,7 @@ const EditCommentBody: FC = ({ diff --git a/src/components/BeerById/NoCommentsCard.tsx b/src/components/Comments/NoCommentsCard.tsx similarity index 100% rename from src/components/BeerById/NoCommentsCard.tsx rename to src/components/Comments/NoCommentsCard.tsx diff --git a/src/components/Comments/types/index.ts b/src/components/Comments/types/index.ts new file mode 100644 index 0000000..597d37d --- /dev/null +++ b/src/components/Comments/types/index.ts @@ -0,0 +1,12 @@ +import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +type APIResponse = z.infer; + +export type HandleEditCommentRequest = ( + id: string, + data: z.infer, +) => Promise; + +export type HandleDeleteCommentRequest = (id: string) => Promise; diff --git a/src/requests/comments/beer-comment/index.ts b/src/requests/comments/beer-comment/index.ts index dc39c1b..cc6863c 100644 --- a/src/requests/comments/beer-comment/index.ts +++ b/src/requests/comments/beer-comment/index.ts @@ -11,9 +11,11 @@ import { * * @param params - The parameters for the request. * @param params.body - The body of the request. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. * @param params.commentId - The id of the comment to edit. * @param params.beerPostId - The id of the beer post the comment belongs to. - * @returns The edited comment. + * @returns The JSON response from the server. * @throws An error if the request fails or the response is invalid. */ export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async ({ @@ -47,7 +49,7 @@ export const editBeerPostCommentRequest: SendEditBeerPostCommentRequest = async * @param params - The parameters for the request. * @param params.commentId - The id of the comment to delete. * @param params.beerPostId - The id of the beer post the comment belongs to. - * @returns The deleted comment. + * @returns The JSON response from the server. * @throws An error if the request fails or the response is invalid. */ export const deleteBeerPostCommentRequest: SendDeleteBeerPostCommentRequest = async ({ diff --git a/src/requests/comments/beer-style-comment/index.ts b/src/requests/comments/beer-style-comment/index.ts index 5c8629c..1beded3 100644 --- a/src/requests/comments/beer-style-comment/index.ts +++ b/src/requests/comments/beer-style-comment/index.ts @@ -6,6 +6,88 @@ import { SendEditBeerStyleCommentRequest, } from './types'; +/** + * Sends an api request to edit a beer style comment. + * + * @param params - The parameters for the request. + * @param params.body - The body of the request. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + * @param params.beerStyleId - The id of the beer style the comment belongs to. + * @param params.commentId - The id of the comment to edit. + * @returns The JSON response from the server. + * @throws An error if the request fails or the response is invalid. + */ +export const sendEditBeerStyleCommentRequest: SendEditBeerStyleCommentRequest = async ({ + commentId, + body: { content, rating }, + beerStyleId, +}) => { + const response = await fetch(`/api/beers/styles/${beerStyleId}/comments/${commentId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content, rating }), + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('Invalid API response'); + } + + return parsed.data; +}; + +/** + * Sends an api request to delete a beer style comment. + * + * @param params - The parameters for the request. + * @param params.beerStyleId - The id of the beer style the comment belongs to. + * @param params.commentId - The id of the comment to delete. + * @returns The json response from the server. + * @throws An error if the request fails or the response is invalid. + */ +export const sendDeleteBeerStyleCommentRequest: SendDeleteBeerStyleCommentRequest = + async ({ beerStyleId, commentId }) => { + const response = await fetch( + `/api/beers/styles/${beerStyleId}/comments/${commentId}`, + { + method: 'DELETE', + }, + ); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('Invalid API response'); + } + + return parsed.data; + }; + +/** + * Sends an api request to create a beer style comment. + * + * @param params - The parameters for the request. + * @param params.body - The body of the request. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + * @param params.beerStyleId - The id of the beer style the comment belongs to. + * @returns The created comment. + * @throws An error if the request fails or the response is invalid. + */ export const sendCreateBeerStyleCommentRequest: SendCreateBeerStyleCommentRequest = async ({ beerStyleId, body: { content, rating } }) => { const response = await fetch(`/api/beers/styles/${beerStyleId}/comments`, { @@ -33,53 +115,3 @@ export const sendCreateBeerStyleCommentRequest: SendCreateBeerStyleCommentReques return parsedPayload.data; }; - -export const sendEditBeerStyleCommentRequest: SendEditBeerStyleCommentRequest = async ({ - commentId, - body: { content, rating }, - beerStyleId, -}) => { - const response = await fetch(`/api/beers/styles/${beerStyleId}/comments/${commentId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content, rating }), - }); - - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('Invalid API response'); - } - - return parsed.data; -}; - -export const sendDeleteBeerStyleCommentRequest: SendDeleteBeerStyleCommentRequest = - async ({ beerStyleId, commentId }) => { - const response = await fetch( - `/api/beers/styles/${beerStyleId}/comments/${commentId}`, - { - method: 'DELETE', - }, - ); - - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('Invalid API response'); - } - - return parsed.data; - }; diff --git a/src/requests/comments/brewery-comment/index.ts b/src/requests/comments/brewery-comment/index.ts new file mode 100644 index 0000000..cd3b794 --- /dev/null +++ b/src/requests/comments/brewery-comment/index.ts @@ -0,0 +1,109 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { + SendCreateBreweryPostCommentRequest, + SendDeleteBreweryPostCommentRequest, + SendEditBreweryPostCommentRequest, +} from './types'; +/** + * Sends an api request to edit a brewery comment. + * + * @param params - The parameters for the request. + * @param params.breweryId - The id of the brewery the comment belongs to. + * @param params.commentId - The id of the comment to edit. + * @param params.body - The body of the request. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + */ +export const sendEditBreweryPostCommentRequest: SendEditBreweryPostCommentRequest = + async ({ body: { content, rating }, breweryPostId, commentId }) => { + const response = await fetch( + `/api/breweries/${breweryPostId}/comments/${commentId}`, + { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content, rating }), + }, + ); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error(parsed.error.message); + } + + return parsed.data; + }; + +/** + * Sends an api request to delete a brewery comment. + * + * @param params - The parameters for the request. + * @param params.breweryId - The id of the brewery the comment belongs to. + * @param params.commentId - The id of the comment to delete. + * @returns The JSON response from the server. + * @throws An error if the request fails or the response is invalid. + */ +export const sendDeleteBreweryPostCommentRequest: SendDeleteBreweryPostCommentRequest = + async ({ breweryPostId, commentId }) => { + const response = await fetch( + `/api/breweries/${breweryPostId}/comments/${commentId}`, + { + method: 'DELETE', + }, + ); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error(parsed.error.message); + } + + return parsed.data; + }; +/** + * Sends an api request to create a brewery comment. + * + * @param params - The parameters for the request. + * @param params.body - The body of the request. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + * @param params.breweryId - The id of the brewery the comment belongs to. + * @returns The created comment. + * @throws An error if the request fails or the response is invalid. + */ +export const sendCreateBreweryCommentRequest: SendCreateBreweryPostCommentRequest = + async ({ body: { content, rating }, breweryPostId }) => { + const response = await fetch(`/api/breweries/${breweryPostId}/comments`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content, rating }), + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const data = await response.json(); + const parsedResponse = APIResponseValidationSchema.safeParse(data); + if (!parsedResponse.success) { + throw new Error('Invalid API response'); + } + + const parsedPayload = CommentQueryResult.safeParse(parsedResponse.data.payload); + if (!parsedPayload.success) { + throw new Error('Invalid API response payload'); + } + + return parsedPayload.data; + }; diff --git a/src/requests/comments/brewery-comment/types/index.ts b/src/requests/comments/brewery-comment/types/index.ts new file mode 100644 index 0000000..be3eac6 --- /dev/null +++ b/src/requests/comments/brewery-comment/types/index.ts @@ -0,0 +1,19 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +export type SendEditBreweryPostCommentRequest = (args: { + body: { content: string; rating: number }; + commentId: string; + breweryPostId: string; +}) => Promise>; + +export type SendDeleteBreweryPostCommentRequest = (args: { + commentId: string; + breweryPostId: string; +}) => Promise>; + +export type SendCreateBreweryPostCommentRequest = (args: { + breweryPostId: string; + body: { content: string; rating: number }; +}) => Promise>; From 7c4a4bde80f49c628af5abb6bcf6d1af23bdc1b2 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Wed, 27 Dec 2023 20:42:37 -0500 Subject: [PATCH 07/13] Refactor: update beer post api requests --- src/components/CreateBeerPostForm.tsx | 25 +++- src/components/EditBeerPostForm.tsx | 19 ++- src/controllers/images/beer-images/index.ts | 2 +- .../images/brewery-images/index.ts | 2 +- src/controllers/images/types/index.ts | 2 +- src/controllers/posts/breweries/index.ts | 12 +- .../posts/breweries/types/index.ts | 2 +- .../posts/beer-post/deleteBeerPostRequest.ts | 29 ---- src/requests/posts/beer-post/index.ts | 134 ++++++++++++++++++ .../beer-post/sendCreateBeerPostRequest.ts | 67 --------- .../beer-post/sendEditBeerPostRequest.ts | 42 ------ src/requests/posts/beer-post/types/index.ts | 31 ++++ 12 files changed, 208 insertions(+), 159 deletions(-) delete mode 100644 src/requests/posts/beer-post/deleteBeerPostRequest.ts create mode 100644 src/requests/posts/beer-post/index.ts delete mode 100644 src/requests/posts/beer-post/sendCreateBeerPostRequest.ts delete mode 100644 src/requests/posts/beer-post/sendEditBeerPostRequest.ts create mode 100644 src/requests/posts/beer-post/types/index.ts diff --git a/src/components/CreateBeerPostForm.tsx b/src/components/CreateBeerPostForm.tsx index 42bea2e..3b21043 100644 --- a/src/components/CreateBeerPostForm.tsx +++ b/src/components/CreateBeerPostForm.tsx @@ -1,18 +1,20 @@ +import { FunctionComponent } from 'react'; +import router from 'next/router'; import { zodResolver } from '@hookform/resolvers/zod'; import { BeerStyle } from '@prisma/client'; -import router from 'next/router'; -import { FunctionComponent } from 'react'; +import toast from 'react-hot-toast'; import { useForm, SubmitHandler, FieldError } from 'react-hook-form'; import { z } from 'zod'; + import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; -import sendCreateBeerPostRequest from '@/requests/posts/beer-post/sendCreateBeerPostRequest'; import UploadImageValidationSchema from '@/services/schema/ImageSchema/UploadImageValidationSchema'; -import sendUploadBeerImagesRequest from '@/requests/images/beer-image/sendUploadBeerImageRequest'; - -import toast from 'react-hot-toast'; import createErrorToast from '@/util/createErrorToast'; + +import { sendCreateBeerPostRequest } from '@/requests/posts/beer-post'; +import sendUploadBeerImagesRequest from '@/requests/images/beer-image/sendUploadBeerImageRequest'; + import Button from './ui/forms/Button'; import FormError from './ui/forms/FormError'; import FormInfo from './ui/forms/FormInfo'; @@ -53,7 +55,16 @@ const CreateBeerPostForm: FunctionComponent = ({ try { const loadingToast = toast.loading('Creating beer post...'); - const beerPost = await sendCreateBeerPostRequest(data); + const beerPost = await sendCreateBeerPostRequest({ + body: { + name: data.name, + description: data.description, + abv: data.abv, + ibu: data.ibu, + }, + breweryId: data.breweryId, + styleId: data.styleId, + }); await sendUploadBeerImagesRequest({ beerPost, images: data.images }); await router.push(`/beers/${beerPost.id}`); toast.dismiss(loadingToast); diff --git a/src/components/EditBeerPostForm.tsx b/src/components/EditBeerPostForm.tsx index 3f6ed0b..c735de3 100644 --- a/src/components/EditBeerPostForm.tsx +++ b/src/components/EditBeerPostForm.tsx @@ -6,10 +6,13 @@ import { z } from 'zod'; import { useForm, SubmitHandler } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import deleteBeerPostRequest from '@/requests/posts/beer-post/deleteBeerPostRequest'; import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; -import sendEditBeerPostRequest from '@/requests/posts/beer-post/sendEditBeerPostRequest'; + import createErrorToast from '@/util/createErrorToast'; +import { + sendEditBeerPostRequest, + sendDeleteBeerPostRequest, +} from '@/requests/posts/beer-post'; import Button from './ui/forms/Button'; import FormError from './ui/forms/FormError'; import FormInfo from './ui/forms/FormInfo'; @@ -35,7 +38,15 @@ const EditBeerPostForm: FC = ({ previousValues }) => { const onSubmit: SubmitHandler = async (data) => { try { const loadingToast = toast.loading('Editing beer post...'); - await sendEditBeerPostRequest(data); + await sendEditBeerPostRequest({ + beerPostId: data.id, + body: { + name: data.name, + abv: data.abv, + ibu: data.ibu, + description: data.description, + }, + }); await router.push(`/beers/${data.id}`); toast.success('Edited beer post.'); toast.dismiss(loadingToast); @@ -48,7 +59,7 @@ const EditBeerPostForm: FC = ({ previousValues }) => { const onDelete = async () => { try { const loadingToast = toast.loading('Deleting beer post...'); - await deleteBeerPostRequest(previousValues.id); + await sendDeleteBeerPostRequest({ beerPostId: previousValues.id }); toast.dismiss(loadingToast); await router.push('/beers'); toast.success('Deleted beer post.'); diff --git a/src/controllers/images/beer-images/index.ts b/src/controllers/images/beer-images/index.ts index 0a1d5c1..bc6a003 100644 --- a/src/controllers/images/beer-images/index.ts +++ b/src/controllers/images/beer-images/index.ts @@ -20,7 +20,7 @@ export const processBeerImageData = async ( } const beerImages = await addBeerImagesService({ - beerPostId: req.query.id, + beerPostId: req.query.postId, userId: user!.id, body, files, diff --git a/src/controllers/images/brewery-images/index.ts b/src/controllers/images/brewery-images/index.ts index a7a4a3c..24453d6 100644 --- a/src/controllers/images/brewery-images/index.ts +++ b/src/controllers/images/brewery-images/index.ts @@ -18,7 +18,7 @@ export const processBreweryImageData = async ( } const breweryImages = await addBreweryImagesService({ - breweryPostId: req.query.id, + breweryPostId: req.query.postId, userId: user!.id, body, files, diff --git a/src/controllers/images/types/index.ts b/src/controllers/images/types/index.ts index 8d4b308..d79a389 100644 --- a/src/controllers/images/types/index.ts +++ b/src/controllers/images/types/index.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; export interface UploadImagesRequest extends UserExtendedNextApiRequest { files?: Express.Multer.File[]; - query: { id: string }; + query: { postId: string }; body: z.infer; } diff --git a/src/controllers/posts/breweries/index.ts b/src/controllers/posts/breweries/index.ts index 4e5cdfd..3f1001c 100644 --- a/src/controllers/posts/breweries/index.ts +++ b/src/controllers/posts/breweries/index.ts @@ -167,9 +167,9 @@ export const checkIfBreweryPostOwner = async ( next: NextHandler, ) => { const user = req.user!; - const { id } = req.query; + const { postId } = req.query; - const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); + const breweryPost = await getBreweryPostByIdService({ breweryPostId: postId }); if (!breweryPost) { throw new ServerError('Brewery post not found', 404); } @@ -187,10 +187,10 @@ export const editBreweryPost = async ( ) => { const { body, - query: { id }, + query: { postId }, } = req; - await updateBreweryPostService({ breweryPostId: id, body }); + await updateBreweryPostService({ breweryPostId: postId, body }); res.status(200).json({ message: 'Brewery post updated successfully', @@ -203,8 +203,8 @@ export const deleteBreweryPost = async ( req: BreweryPostRequest, res: NextApiResponse, ) => { - const { id } = req.query; - const deleted = await deleteBreweryPostService({ breweryPostId: id }); + const { postId } = req.query; + const deleted = await deleteBreweryPostService({ breweryPostId: postId }); if (!deleted) { throw new ServerError('Brewery post not found', 404); diff --git a/src/controllers/posts/breweries/types/index.ts b/src/controllers/posts/breweries/types/index.ts index 85dd30a..2524b36 100644 --- a/src/controllers/posts/breweries/types/index.ts +++ b/src/controllers/posts/breweries/types/index.ts @@ -14,7 +14,7 @@ export interface CreateBreweryPostRequest extends UserExtendedNextApiRequest { } export interface BreweryPostRequest extends UserExtendedNextApiRequest { - query: { id: string }; + query: { postId: string }; } export interface EditBreweryPostRequest extends BreweryPostRequest { diff --git a/src/requests/posts/beer-post/deleteBeerPostRequest.ts b/src/requests/posts/beer-post/deleteBeerPostRequest.ts deleted file mode 100644 index ea3ee3f..0000000 --- a/src/requests/posts/beer-post/deleteBeerPostRequest.ts +++ /dev/null @@ -1,29 +0,0 @@ -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -/** - * Sends a DELETE request to the server to delete a beer post with the given ID. - * - * @param id The ID of the beer post to delete. - * @returns A Promise that resolves to the parsed API response. - * @throws An error if the response fails or the API response is invalid. - */ -const deleteBeerPostRequest = async (id: string) => { - const response = await fetch(`/api/beers/${id}`, { - method: 'DELETE', - }); - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('Could not successfully parse the response.'); - } - - return parsed; -}; - -export default deleteBeerPostRequest; diff --git a/src/requests/posts/beer-post/index.ts b/src/requests/posts/beer-post/index.ts new file mode 100644 index 0000000..921bc0b --- /dev/null +++ b/src/requests/posts/beer-post/index.ts @@ -0,0 +1,134 @@ +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { + SendCreateBeerPostRequest, + SendDeleteBeerPostRequest, + SendEditBeerPostRequest, +} from './types'; + +/** + * Sends a POST request to create a new beer post. + * + * @example + * const beerPost = await sendCreateBeerPostRequest({ + * body: { + * abv: 5.5, + * description: 'A golden delight with a touch of citrus.', + * ibu: 30, + * name: 'Yerb Sunshine Ale', + * }, + * styleId: 'clqmteqxc000008jphoy31wqw', + * breweryId: 'clqmtexfb000108jp3nsg26c6', + * }); + * + * @param data - The data to send in the request. + * @param data.body - The body of the request. + * @param data.body.abv - The ABV of the beer. + * @param data.body.description - The description of the beer. + * @param data.body.ibu - The IBU of the beer. + * @param data.body.name - The name of the beer. + * @param data.styleId - The ID of the style of the beer. + * @param data.breweryId - The ID of the brewery of the beer. + * @returns The created beer post. + * @throws An error if the request fails or the response is invalid. + */ +export const sendCreateBeerPostRequest: SendCreateBeerPostRequest = async ({ + body: { abv, description, ibu, name }, + styleId, + breweryId, +}) => { + const response = await fetch('/api/beers/create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ abv, description, ibu, name, styleId, breweryId }), + }); + if (!response.ok) { + throw new Error(response.statusText); + } + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('Invalid API response'); + } + const { payload, success, message } = parsed.data; + if (!success) { + throw new Error(message); + } + const parsedPayload = BeerPostQueryResult.safeParse(payload); + if (!parsedPayload.success) { + throw new Error('Invalid API response payload'); + } + return parsedPayload.data; +}; + +/** + * Sends a DELETE request to delete a beer post. + * + * @example + * const response = await sendDeleteBeerPostRequest({ + * beerPostId: 'clqmtexfb000108jp3nsg26c6', + * }); + * + * @param args - The arguments to send in the request. + * @param args.beerPostId - The ID of the beer post to delete. + * @returns The response from the server. + * @throws An error if the request fails or the response is invalid. + */ +export const sendDeleteBeerPostRequest: SendDeleteBeerPostRequest = async ({ + beerPostId, +}) => { + const response = await fetch(`/api/beers/${beerPostId}`, { method: 'DELETE' }); + if (!response.ok) { + throw new Error(response.statusText); + } + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('Could not successfully parse the response.'); + } + return parsed.data; +}; + +/** + * Sends a PUT request to edit a beer post. + * + * @example + * const response = await sendEditBeerPostRequest({ + * beerPostId: 'clqmtexfb000108jp3nsg26c6', + * body: { + * abv: 5.5, + * description: 'A golden delight with a touch of citrus.', + * ibu: 30, + * name: 'Yerb Sunshine Ale', + * }, + * }); + * + * @param args - The arguments to send in the request. + * @param args.beerPostId - The ID of the beer post to edit. + * @param args.body - The body of the request. + * @param args.body.abv - The ABV of the beer. + * @param args.body.description - The description of the beer. + * @param args.body.ibu - The IBU of the beer. + * @param args.body.name - The name of the beer. + * @returns The response from the server. + * @throws An error if the request fails or the response is invalid. + */ +export const sendEditBeerPostRequest: SendEditBeerPostRequest = async ({ + beerPostId, + body: { abv, description, ibu, name }, +}) => { + const response = await fetch(`/api/beers/${beerPostId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ abv, description, ibu, name }), + }); + if (!response.ok) { + throw new Error(response.statusText); + } + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('Invalid API response'); + } + return parsed.data; +}; diff --git a/src/requests/posts/beer-post/sendCreateBeerPostRequest.ts b/src/requests/posts/beer-post/sendCreateBeerPostRequest.ts deleted file mode 100644 index 7e36b1f..0000000 --- a/src/requests/posts/beer-post/sendCreateBeerPostRequest.ts +++ /dev/null @@ -1,67 +0,0 @@ -import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; -import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -/** - * Sends a POST request to the server to create a new beer post. - * - * @param data Data containing the beer post information to be sent to the server. - * @param abv The alcohol by volume of the beer. - * @param breweryId The ID of the brewery that produces the beer. - * @param description The description of the beer. - * @param ibu The International Bitterness Units of the beer. - * @param name The name of the beer. - * @param styleId The ID of the beer style. - * @returns A Promise that resolves to the created beer post. - * @throws An error if the request fails, the API response is invalid, or the API response - * payload is invalid. - */ -const sendCreateBeerPostRequest = async ({ - abv, - breweryId, - description, - ibu, - name, - styleId, -}: z.infer) => { - const response = await fetch('/api/beers/create', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - abv, - breweryId, - description, - ibu, - name, - styleId, - }), - }); - - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('Invalid API response'); - } - - const { payload, success, message } = parsed.data; - - if (!success) { - throw new Error(message); - } - - const parsedPayload = BeerPostQueryResult.safeParse(payload); - - if (!parsedPayload.success) { - throw new Error('Invalid API response payload'); - } - - return parsedPayload.data; -}; - -export default sendCreateBeerPostRequest; diff --git a/src/requests/posts/beer-post/sendEditBeerPostRequest.ts b/src/requests/posts/beer-post/sendEditBeerPostRequest.ts deleted file mode 100644 index de58f1d..0000000 --- a/src/requests/posts/beer-post/sendEditBeerPostRequest.ts +++ /dev/null @@ -1,42 +0,0 @@ -import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -/** - * Sends a PUT request to the server to update a beer post. - * - * @param data Data containing the updated beer post information to be sent to the server. - * @param data.abv The updated ABV of the beer. - * @param data.description The updated description of the beer. - * @param data.ibu The updated IBU of the beer. - * @param data.id The ID of the beer post to be updated. - * @param data.name The updated name of the beer. - * @param data.styleId The updated style ID of the beer. - * @throws If the response status is not ok or the API response is not successful. - */ -const sendEditBeerPostRequest = async ({ - abv, - description, - ibu, - id, - name, -}: z.infer) => { - const response = await fetch(`/api/beers/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ abv, description, ibu, name, id }), - }); - - if (!response.ok) { - throw new Error(`${response.status}: ${response.statusText}`); - } - - const json = await response.json(); - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error(parsed.error.message); - } -}; - -export default sendEditBeerPostRequest; diff --git a/src/requests/posts/beer-post/types/index.ts b/src/requests/posts/beer-post/types/index.ts new file mode 100644 index 0000000..0fc1d6b --- /dev/null +++ b/src/requests/posts/beer-post/types/index.ts @@ -0,0 +1,31 @@ +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +type BeerPostQueryResultT = z.infer; +type APIResponse = z.infer; + +export type SendCreateBeerPostRequest = (data: { + body: { + abv: number; + description: string; + ibu: number; + name: string; + }; + styleId: string; + breweryId: string; +}) => Promise; + +export type SendDeleteBeerPostRequest = (args: { + beerPostId: string; +}) => Promise; + +export type SendEditBeerPostRequest = (args: { + beerPostId: string; + body: { + abv: number; + description: string; + ibu: number; + name: string; + }; +}) => Promise; From 9f1d09a61d454684bc8c50e676220a128f2069f2 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Fri, 16 Feb 2024 01:56:44 -0500 Subject: [PATCH 08/13] refactor brewery post requests --- package-lock.json | 330 ++++++++++++++++++ package.json | 16 +- .../BreweryPost/CreateBreweryPostForm.tsx | 5 +- src/components/EditBreweryPostForm.tsx | 106 ++++++ src/pages/breweries/[id]/edit.tsx | 108 +----- src/requests/posts/brewery-post/index.ts | 105 ++++++ .../sendCreateBreweryPostRequest.ts | 34 -- .../posts/brewery-post/types/index.ts | 23 ++ 8 files changed, 577 insertions(+), 150 deletions(-) create mode 100644 src/components/EditBreweryPostForm.tsx create mode 100644 src/requests/posts/brewery-post/index.ts delete mode 100644 src/requests/posts/brewery-post/sendCreateBreweryPostRequest.ts create mode 100644 src/requests/posts/brewery-post/types/index.ts diff --git a/package-lock.json b/package-lock.json index 754092f..0bc327b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "react-icons": "^4.10.1", "react-intersection-observer": "^9.5.2", "react-map-gl": "^7.1.2", + "react-page-scroller": "^3.0.1", "react-responsive-carousel": "^3.2.23", "swr": "^2.2.0", "theme-change": "^2.5.0", @@ -3205,6 +3206,49 @@ "dequal": "^2.0.3" } }, + "node_modules/babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", + "dependencies": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "node_modules/babel-polyfill/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/babel-polyfill/node_modules/regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5744,6 +5788,26 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", + "peer": true + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -5951,6 +6015,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "peer": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -8128,6 +8201,21 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "peer": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "peer": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8897,6 +8985,21 @@ } } }, + "node_modules/react-page-scroller": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-page-scroller/-/react-page-scroller-3.0.1.tgz", + "integrity": "sha512-1OTlUHOSFCG8wmYzy3wo4dUIugXORzgZZ/Pj4BbkxN3n+Nv5tynfo7kygUiLYgE0QhDJslzZ4lntpCONAlv/vA==", + "dependencies": { + "babel-polyfill": "^6.26.0" + }, + "peerDependencies": { + "babel-polyfill": "^6.26.0", + "prop-types": "^15.6.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^4.3.1" + } + }, "node_modules/react-responsive-carousel": { "version": "3.2.23", "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", @@ -8907,6 +9010,41 @@ "react-easy-swipe": "^0.0.21" } }, + "node_modules/react-router": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", + "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "peer": true, + "dependencies": { + "history": "^4.7.2", + "hoist-non-react-statics": "^2.5.0", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.1", + "warning": "^4.0.1" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", + "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "peer": true, + "dependencies": { + "history": "^4.7.2", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.1", + "react-router": "^4.3.1", + "warning": "^4.0.1" + }, + "peerDependencies": { + "react": ">=15" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9216,6 +9354,12 @@ "node": ">=4" } }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "peer": true + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -10097,6 +10241,18 @@ "real-require": "^0.2.0" } }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", + "peer": true + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "peer": true + }, "node_modules/tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -10584,6 +10740,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "peer": true + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -10594,6 +10756,15 @@ "pbf": "^3.2.1" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "peer": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -13092,6 +13263,49 @@ "dequal": "^2.0.3" } }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -14957,6 +15171,26 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "peer": true, + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", + "peer": true + }, "hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -15106,6 +15340,15 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "peer": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -16592,6 +16835,23 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "peer": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "peer": true + } + } + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -17055,6 +17315,14 @@ "@types/mapbox-gl": ">=1.0.0" } }, + "react-page-scroller": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-page-scroller/-/react-page-scroller-3.0.1.tgz", + "integrity": "sha512-1OTlUHOSFCG8wmYzy3wo4dUIugXORzgZZ/Pj4BbkxN3n+Nv5tynfo7kygUiLYgE0QhDJslzZ4lntpCONAlv/vA==", + "requires": { + "babel-polyfill": "^6.26.0" + } + }, "react-responsive-carousel": { "version": "3.2.23", "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", @@ -17065,6 +17333,35 @@ "react-easy-swipe": "^0.0.21" } }, + "react-router": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", + "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "peer": true, + "requires": { + "history": "^4.7.2", + "hoist-non-react-statics": "^2.5.0", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.1", + "warning": "^4.0.1" + } + }, + "react-router-dom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", + "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "peer": true, + "requires": { + "history": "^4.7.2", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.1", + "react-router": "^4.3.1", + "warning": "^4.0.1" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17306,6 +17603,12 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "peer": true + }, "resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -17969,6 +18272,18 @@ "real-require": "^0.2.0" } }, + "tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", + "peer": true + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "peer": true + }, "tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -18326,6 +18641,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "peer": true + }, "vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -18336,6 +18657,15 @@ "pbf": "^3.2.1" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "peer": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 15272cc..7fe47f4 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,13 @@ "lodash": "^4.17.21", "mapbox-gl": "^2.15.0", "multer": "^1.4.5-lts.1", + "next": "^14.0.3", "next-cloudinary": "^5.10.0", "next-connect": "^1.0.0-next.3", - "next": "^14.0.3", - "passport-local": "^1.0.0", "passport": "^0.6.0", + "passport-local": "^1.0.0", "pino": "^8.14.1", + "react": "^18.2.0", "react-daisyui": "^4.1.2", "react-dom": "^18.2.0", "react-email": "^1.9.5", @@ -50,8 +51,8 @@ "react-icons": "^4.10.1", "react-intersection-observer": "^9.5.2", "react-map-gl": "^7.1.2", + "react-page-scroller": "^3.0.1", "react-responsive-carousel": "^3.2.23", - "react": "^18.2.0", "swr": "^2.2.0", "theme-change": "^2.5.0", "zod": "^3.21.4" @@ -66,28 +67,27 @@ "@types/multer": "^1.4.7", "@types/node": "^20.4.2", "@types/passport-local": "^1.0.35", - "@types/react-dom": "^18.2.7", "@types/react": "^18.2.15", - "@types/sparkpost": "^2.1.5", + "@types/react-dom": "^18.2.7", "@vercel/fetch": "^7.0.0", "autoprefixer": "^10.4.14", "daisyui": "^3.9.2", "dotenv-cli": "^7.2.1", + "eslint": "^8.51.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-typescript": "17.1.0", "eslint-config-next": "^13.5.4", "eslint-config-prettier": "^9.0.0", "eslint-plugin-react": "^7.33.2", - "eslint": "^8.51.0", "generate-password": "^1.7.1", "onchange": "^7.1.0", "postcss": "^8.4.26", + "prettier": "^3.0.0", "prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-tailwindcss": "^0.5.7", - "prettier": "^3.0.0", "prisma": "^5.7.0", - "tailwindcss-animate": "^1.0.6", "tailwindcss": "^3.3.3", + "tailwindcss-animate": "^1.0.6", "ts-node": "^10.9.1", "typescript": "^5.3.2" }, diff --git a/src/components/BreweryPost/CreateBreweryPostForm.tsx b/src/components/BreweryPost/CreateBreweryPostForm.tsx index 7a7aaae..d61e444 100644 --- a/src/components/BreweryPost/CreateBreweryPostForm.tsx +++ b/src/components/BreweryPost/CreateBreweryPostForm.tsx @@ -1,5 +1,5 @@ import sendUploadBreweryImagesRequest from '@/requests/images/brewery-image/sendUploadBreweryImageRequest'; -import sendCreateBreweryPostRequest from '@/requests/posts/brewery-post/sendCreateBreweryPostRequest'; + import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; import UploadImageValidationSchema from '@/services/schema/ImageSchema/UploadImageValidationSchema'; import createErrorToast from '@/util/createErrorToast'; @@ -27,6 +27,7 @@ import FormSegment from '../ui/forms/FormSegment'; import FormTextArea from '../ui/forms/FormTextArea'; import FormTextInput from '../ui/forms/FormTextInput'; import Button from '../ui/forms/Button'; +import { sendCreateBreweryPostRequest } from '@/requests/posts/brewery-post'; const AddressAutofill = dynamic( // @ts-expect-error @@ -225,7 +226,7 @@ const CreateBreweryPostForm: FC<{ if (!(data.images instanceof FileList)) { return; } - const breweryPost = await sendCreateBreweryPostRequest(data); + const breweryPost = await sendCreateBreweryPostRequest({ body: data }); await sendUploadBreweryImagesRequest({ breweryPost, images: data.images }); await router.push(`/breweries/${breweryPost.id}`); toast.remove(loadingToast); diff --git a/src/components/EditBreweryPostForm.tsx b/src/components/EditBreweryPostForm.tsx new file mode 100644 index 0000000..4837be9 --- /dev/null +++ b/src/components/EditBreweryPostForm.tsx @@ -0,0 +1,106 @@ +import EditBreweryPostValidationSchema from '@/services/posts/brewery-post/schema/EditBreweryPostValidationSchema'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useRouter } from 'next/router'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { FC, useState } from 'react'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; +import { + sendDeleteBreweryPostRequest, + sendEditBreweryPostRequest, +} from '@/requests/posts/brewery-post'; +import FormError from './ui/forms/FormError'; +import FormInfo from './ui/forms/FormInfo'; +import FormLabel from './ui/forms/FormLabel'; +import FormSegment from './ui/forms/FormSegment'; +import FormTextArea from './ui/forms/FormTextArea'; +import FormTextInput from './ui/forms/FormTextInput'; + +interface EditBreweryPostFormProps { + breweryPost: z.infer; +} +const EditBreweryPostForm: FC = ({ breweryPost }) => { + const router = useRouter(); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm>({ + resolver: zodResolver(EditBreweryPostValidationSchema), + defaultValues: { + name: breweryPost.name, + description: breweryPost.description, + id: breweryPost.id, + dateEstablished: breweryPost.dateEstablished, + }, + }); + + const [isDeleting, setIsDeleting] = useState(false); + + const onSubmit = async (data: z.infer) => { + await sendEditBreweryPostRequest({ breweryPostId: breweryPost.id, body: data }); + await router.push(`/breweries/${breweryPost.id}`); + }; + + const handleDelete = async () => { + setIsDeleting(true); + await sendDeleteBreweryPostRequest({ breweryPostId: breweryPost.id }); + await router.push('/breweries'); + }; + + return ( + +
+ + Name + {errors.name?.message} + + + + + + + Description + {errors.description?.message} + + + + +
+ +
+ + +
+ + ); +}; + +export default EditBreweryPostForm; diff --git a/src/pages/breweries/[id]/edit.tsx b/src/pages/breweries/[id]/edit.tsx index 5f2c493..e6487c6 100644 --- a/src/pages/breweries/[id]/edit.tsx +++ b/src/pages/breweries/[id]/edit.tsx @@ -1,20 +1,11 @@ -import FormError from '@/components/ui/forms/FormError'; -import FormInfo from '@/components/ui/forms/FormInfo'; -import FormLabel from '@/components/ui/forms/FormLabel'; +import EditBreweryPostForm from '@/components/EditBreweryPostForm'; import FormPageLayout from '@/components/ui/forms/FormPageLayout'; -import FormSegment from '@/components/ui/forms/FormSegment'; -import FormTextArea from '@/components/ui/forms/FormTextArea'; -import FormTextInput from '@/components/ui/forms/FormTextInput'; import { getBreweryPostByIdService } from '@/services/posts/brewery-post'; import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; -import EditBreweryPostValidationSchema from '@/services/posts/brewery-post/schema/EditBreweryPostValidationSchema'; import withPageAuthRequired from '@/util/withPageAuthRequired'; -import { zodResolver } from '@hookform/resolvers/zod'; import { NextPage } from 'next'; import Head from 'next/head'; -import { useRouter } from 'next/router'; -import { useForm } from 'react-hook-form'; import { BiBeer } from 'react-icons/bi'; import { z } from 'zod'; @@ -25,49 +16,6 @@ interface EditPageProps { const EditBreweryPostPage: NextPage = ({ breweryPost }) => { const pageTitle = `Edit \u201c${breweryPost.name}\u201d`; - const router = useRouter(); - const { - register, - handleSubmit, - formState: { errors, isSubmitting }, - } = useForm>({ - resolver: zodResolver(EditBreweryPostValidationSchema), - defaultValues: { - name: breweryPost.name, - description: breweryPost.description, - id: breweryPost.id, - dateEstablished: breweryPost.dateEstablished, - }, - }); - - const onSubmit = async (data: z.infer) => { - const response = await fetch(`/api/breweries/${breweryPost.id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - throw new Error('Something went wrong'); - } - - router.push(`/breweries/${breweryPost.id}`); - }; - - const handleDelete = async () => { - const response = await fetch(`/api/breweries/${breweryPost.id}`, { - method: 'DELETE', - }); - - if (!response.ok) { - throw new Error('Something went wrong'); - } - - router.push('/breweries'); - }; - return ( <> @@ -81,59 +29,7 @@ const EditBreweryPostPage: NextPage = ({ breweryPost }) => { backLink={`/breweries/${breweryPost.id}`} backLinkText={`Back to "${breweryPost.name}"`} > - <> -
-
- - Name - {errors.name?.message} - - - - - - - Description - {errors.description?.message} - - - - -
- -
- - - -
-
- + ); diff --git a/src/requests/posts/brewery-post/index.ts b/src/requests/posts/brewery-post/index.ts new file mode 100644 index 0000000..4e5b4c6 --- /dev/null +++ b/src/requests/posts/brewery-post/index.ts @@ -0,0 +1,105 @@ +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; +import { + SendEditBreweryPostRequest, + SendDeleteBreweryPostRequest, + SendCreateBreweryPostRequest, +} from './types'; + +/** + * Sends an api request to edit a brewery post. + * + * @param args - The arguments for the request. + * @param args.body - The body of the request. + * @param args.body.name - The name of the brewery. + * @param args.body.description - The description of the brewery. + * @param args.body.dateEstablished - The date the brewery was established. + * @param args.breweryPostId - The id of the brewery post to edit. + */ +export const sendEditBreweryPostRequest: SendEditBreweryPostRequest = async ({ + body, + breweryPostId, +}) => { + const response = await fetch(`/api/breweries/${breweryPostId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error('Something went wrong'); + } + + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('Something went wrong'); + } + + return parsed.data; +}; + +/** + * Sends an api request to delete a brewery post. + * + * @param args - The arguments for the request. + * @param args.breweryPostId - The id of the brewery post to delete. + */ +export const sendDeleteBreweryPostRequest: SendDeleteBreweryPostRequest = async ({ + breweryPostId, +}) => { + const response = await fetch(`/api/breweries/${breweryPostId}`, { + method: 'DELETE', + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error(parsed.error.message); + } + + return parsed.data; +}; + +/** + * Sends an api request to create a brewery post. + * + * @param args - The arguments for the request. + * @param args.body - The body of the request. + * @param args.body.name - The name of the brewery. + * @param args.body.description - The description of the brewery. + * @param args.body.dateEstablished - The date the brewery was established. + */ +export const sendCreateBreweryPostRequest: SendCreateBreweryPostRequest = async ({ + body, +}) => { + const response = await fetch('/api/breweries/create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('API response parsing failed'); + } + + const parsedPayload = BreweryPostQueryResult.safeParse(parsed.data.payload); + if (!parsedPayload.success) { + throw new Error('API response payload parsing failed'); + } + + return parsedPayload.data; +}; diff --git a/src/requests/posts/brewery-post/sendCreateBreweryPostRequest.ts b/src/requests/posts/brewery-post/sendCreateBreweryPostRequest.ts deleted file mode 100644 index 5477035..0000000 --- a/src/requests/posts/brewery-post/sendCreateBreweryPostRequest.ts +++ /dev/null @@ -1,34 +0,0 @@ -import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; -import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -const sendCreateBreweryPostRequest = async ( - data: z.infer, -) => { - const response = await fetch('/api/breweries/create', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - if (!parsed.success) { - throw new Error('API response parsing failed'); - } - - const parsedPayload = BreweryPostQueryResult.safeParse(parsed.data.payload); - if (!parsedPayload.success) { - throw new Error('API response payload parsing failed'); - } - - return parsedPayload.data; -}; - -export default sendCreateBreweryPostRequest; diff --git a/src/requests/posts/brewery-post/types/index.ts b/src/requests/posts/brewery-post/types/index.ts new file mode 100644 index 0000000..4a3d375 --- /dev/null +++ b/src/requests/posts/brewery-post/types/index.ts @@ -0,0 +1,23 @@ +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; +import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +type APIResponse = z.infer; + +export type SendDeleteBreweryPostRequest = (args: { + breweryPostId: string; +}) => Promise; + +export type SendEditBreweryPostRequest = (args: { + body: { + name: string; + description: string; + dateEstablished: Date; + }; + breweryPostId: string; +}) => Promise; + +export type SendCreateBreweryPostRequest = (args: { + body: z.infer; +}) => Promise>; From 03088080e9626e5c978b5d9e2f62f2512aa793b4 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Fri, 16 Feb 2024 02:40:35 -0500 Subject: [PATCH 09/13] Update auth requests --- src/components/Account/AccountInfo.tsx | 6 +- src/components/Account/Security.tsx | 3 +- src/components/Login/LoginForm.tsx | 2 +- src/components/RegisterUserForm.tsx | 2 +- src/components/UserPage/UserFollowButton.tsx | 5 +- src/pages/users/forgot-password.tsx | 5 +- src/requests/users/auth/index.ts | 169 ++++++++++++++++++ .../users/auth/sendEditUserRequest.ts | 35 ---- .../users/auth/sendForgotPasswordRequest.ts | 24 --- .../users/auth/sendLoginUserRequest.ts | 31 ---- .../users/auth/sendRegisterUserRequest.ts | 33 ---- .../users/auth/sendUpdatePasswordRequest.ts | 26 --- .../users/auth/sendUserFollowRequest.ts | 16 -- src/requests/users/auth/types/index.ts | 41 +++++ .../users/auth/validateEmailRequest.ts | 25 --- .../schema/CreateUserValidationSchemas.ts | 4 +- 16 files changed, 225 insertions(+), 202 deletions(-) create mode 100644 src/requests/users/auth/index.ts delete mode 100644 src/requests/users/auth/sendEditUserRequest.ts delete mode 100644 src/requests/users/auth/sendForgotPasswordRequest.ts delete mode 100644 src/requests/users/auth/sendLoginUserRequest.ts delete mode 100644 src/requests/users/auth/sendRegisterUserRequest.ts delete mode 100644 src/requests/users/auth/sendUpdatePasswordRequest.ts delete mode 100644 src/requests/users/auth/sendUserFollowRequest.ts create mode 100644 src/requests/users/auth/types/index.ts delete mode 100644 src/requests/users/auth/validateEmailRequest.ts diff --git a/src/components/Account/AccountInfo.tsx b/src/components/Account/AccountInfo.tsx index 9eaf183..35b0a82 100644 --- a/src/components/Account/AccountInfo.tsx +++ b/src/components/Account/AccountInfo.tsx @@ -1,4 +1,3 @@ -import validateEmailRequest from '@/requests/users/auth/validateEmailRequest'; import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest'; import { BaseCreateUserSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { Switch } from '@headlessui/react'; @@ -7,7 +6,7 @@ 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/users/auth/sendEditUserRequest'; + import createErrorToast from '@/util/createErrorToast'; import { toast } from 'react-hot-toast'; import { AccountPageAction, AccountPageState } from '@/reducers/accountPageReducer'; @@ -15,6 +14,7 @@ import FormError from '../ui/forms/FormError'; import FormInfo from '../ui/forms/FormInfo'; import FormLabel from '../ui/forms/FormLabel'; import FormTextInput from '../ui/forms/FormTextInput'; +import { sendEditUserRequest, validateEmailRequest } from '@/requests/users/auth'; interface AccountInfoProps { pageState: AccountPageState; @@ -36,7 +36,7 @@ const AccountInfo: FC = ({ pageState, dispatch }) => { .refine( async (email) => { if (user!.email === email) return true; - return validateEmailRequest(email); + return validateEmailRequest({ email }); }, { message: 'Email is already taken.' }, ), diff --git a/src/components/Account/Security.tsx b/src/components/Account/Security.tsx index 7ed1bbf..e363595 100644 --- a/src/components/Account/Security.tsx +++ b/src/components/Account/Security.tsx @@ -4,10 +4,11 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; -import sendUpdatePasswordRequest from '@/requests/users/auth/sendUpdatePasswordRequest'; + import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer'; import toast from 'react-hot-toast'; import createErrorToast from '@/util/createErrorToast'; +import { sendUpdatePasswordRequest } from '@/requests/users/auth'; import FormError from '../ui/forms/FormError'; import FormInfo from '../ui/forms/FormInfo'; import FormLabel from '../ui/forms/FormLabel'; diff --git a/src/components/Login/LoginForm.tsx b/src/components/Login/LoginForm.tsx index f79bc0d..df2ecb9 100644 --- a/src/components/Login/LoginForm.tsx +++ b/src/components/Login/LoginForm.tsx @@ -1,4 +1,3 @@ -import sendLoginUserRequest from '@/requests/users/auth/sendLoginUserRequest'; import LoginValidationSchema from '@/services/users/auth/schema/LoginValidationSchema'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/router'; @@ -15,6 +14,7 @@ import FormLabel from '../ui/forms/FormLabel'; import FormSegment from '../ui/forms/FormSegment'; import FormTextInput from '../ui/forms/FormTextInput'; import Button from '../ui/forms/Button'; +import { sendLoginUserRequest } from '@/requests/users/auth'; type LoginT = z.infer; const LoginForm = () => { diff --git a/src/components/RegisterUserForm.tsx b/src/components/RegisterUserForm.tsx index 4970bbb..b126d45 100644 --- a/src/components/RegisterUserForm.tsx +++ b/src/components/RegisterUserForm.tsx @@ -1,4 +1,3 @@ -import sendRegisterUserRequest from '@/requests/users/auth/sendRegisterUserRequest'; import { CreateUserValidationSchemaWithUsernameAndEmailCheck } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/router'; @@ -9,6 +8,7 @@ import { z } from 'zod'; import createErrorToast from '@/util/createErrorToast'; import toast from 'react-hot-toast'; +import { sendRegisterUserRequest } from '@/requests/users/auth'; import Button from './ui/forms/Button'; import FormError from './ui/forms/FormError'; import FormInfo from './ui/forms/FormInfo'; diff --git a/src/components/UserPage/UserFollowButton.tsx b/src/components/UserPage/UserFollowButton.tsx index 0b0166c..f2eb28e 100644 --- a/src/components/UserPage/UserFollowButton.tsx +++ b/src/components/UserPage/UserFollowButton.tsx @@ -1,7 +1,8 @@ import useFollowStatus from '@/hooks/data-fetching/user-follows/useFollowStatus'; import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser'; import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; -import sendUserFollowRequest from '@/requests/users/auth/sendUserFollowRequest'; +import { sendUserFollowRequest } from '@/requests/users/auth'; + import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import { FC, useState } from 'react'; import { FaUserCheck, FaUserPlus } from 'react-icons/fa'; @@ -25,7 +26,7 @@ const UserFollowButton: FC = ({ const onClick = async () => { try { setIsLoading(true); - await sendUserFollowRequest(user.id); + await sendUserFollowRequest({ userId: user.id }); await Promise.all([ mutateFollowStatus(), mutateFollowerCount(), diff --git a/src/pages/users/forgot-password.tsx b/src/pages/users/forgot-password.tsx index d27ca0a..98a914c 100644 --- a/src/pages/users/forgot-password.tsx +++ b/src/pages/users/forgot-password.tsx @@ -12,8 +12,9 @@ import { NextPage } from 'next'; import { SubmitHandler, useForm } from 'react-hook-form'; import toast from 'react-hot-toast'; import { useRouter } from 'next/router'; -import sendForgotPasswordRequest from '@/requests/users/auth/sendForgotPasswordRequest'; + import { FaUserCircle } from 'react-icons/fa'; +import { sendForgotPasswordRequest } from '@/requests/users/auth'; interface ForgotPasswordPageProps {} @@ -29,7 +30,7 @@ const ForgotPasswordPage: NextPage = () => { const onSubmit: SubmitHandler<{ email: string }> = async (data) => { try { const loadingToast = toast.loading('Sending reset link...'); - await sendForgotPasswordRequest(data.email); + await sendForgotPasswordRequest({ email: data.email }); reset(); toast.dismiss(loadingToast); toast.success('Password reset link sent!'); diff --git a/src/requests/users/auth/index.ts b/src/requests/users/auth/index.ts new file mode 100644 index 0000000..7b8c76a --- /dev/null +++ b/src/requests/users/auth/index.ts @@ -0,0 +1,169 @@ +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { BasicUserInfoSchema } from '@/config/auth/types'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; +import type { + SendEditUserRequest, + SendForgotPasswordRequest, + SendLoginUserRequest, + SendRegisterUserRequest, + SendUpdatePasswordRequest, + SendUserFollowRequest, + ValidateEmailRequest, +} from './types'; +import { z } from 'zod'; + +export const sendEditUserRequest: SendEditUserRequest = async ({ user, data }) => { + const response = await fetch(`/api/users/${user!.id}`, { + body: JSON.stringify(data), + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + }); + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('API response validation failed.'); + } + + return parsed.data; +}; + +export const sendForgotPasswordRequest: SendForgotPasswordRequest = async ({ email }) => { + const response = await fetch('/api/users/forgot-password', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }), + }); + + if (!response.ok) { + throw new Error("Something went wrong and we couldn't send the reset link."); + } + + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error(parsed.error.message); + } + + return parsed.data; +}; + +export const sendLoginUserRequest: SendLoginUserRequest = async ({ + username, + password, +}) => { + const response = await fetch('/api/users/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + password, + }), + }); + + const json: unknown = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('API response validation failed'); + } + + if (!parsed.data.success) { + throw new Error(parsed.data.message); + } + const parsedPayload = BasicUserInfoSchema.safeParse(parsed.data.payload); + if (!parsedPayload.success) { + throw new Error('API response payload validation failed'); + } + + return parsedPayload.data; +}; + +export const sendRegisterUserRequest: SendRegisterUserRequest = async (data) => { + const response = await fetch('/api/users/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); + + const json = await response.json(); + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('API response validation failed.'); + } + + if (!parsed.data.success) { + throw new Error(parsed.data.message); + } + + const parsedPayload = GetUserSchema.safeParse(parsed.data.payload); + + if (!parsedPayload.success) { + throw new Error('API response payload validation failed.'); + } + + return parsedPayload.data; +}; + +export const sendUpdatePasswordRequest: SendUpdatePasswordRequest = async (data) => { + const response = await fetch('/api/users/edit-password', { + body: JSON.stringify(data), + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('API response validation failed.'); + } + + return parsed.data; +}; + +export const sendUserFollowRequest: SendUserFollowRequest = async ({ userId }) => { + const response = await fetch(`/api/users/${userId}/follow-user`, { method: 'POST' }); + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + throw new Error('Invalid API response.'); + } + + return parsed.data; +}; + +export const validateEmailRequest: ValidateEmailRequest = async ({ email }) => { + const response = await fetch(`/api/users/check-email?email=${email}`); + const json = await response.json(); + + const parsed = APIResponseValidationSchema.safeParse(json); + + if (!parsed.success) { + return false; + } + + const parsedPayload = z + .object({ emailIsTaken: z.boolean() }) + .safeParse(parsed.data.payload); + + if (!parsedPayload.success) { + return false; + } + + return !parsedPayload.data.emailIsTaken; +}; diff --git a/src/requests/users/auth/sendEditUserRequest.ts b/src/requests/users/auth/sendEditUserRequest.ts deleted file mode 100644 index 1ff9feb..0000000 --- a/src/requests/users/auth/sendEditUserRequest.ts +++ /dev/null @@ -1,35 +0,0 @@ -import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -interface SendEditUserRequestArgs { - user: z.infer; - data: { - username: string; - email: string; - firstName: string; - lastName: string; - }; -} - -const sendEditUserRequest = async ({ user, data }: SendEditUserRequestArgs) => { - const response = await fetch(`/api/users/${user!.id}`, { - body: JSON.stringify(data), - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - }); - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - if (!parsed.success) { - throw new Error('API response validation failed.'); - } - - return parsed; -}; - -export default sendEditUserRequest; diff --git a/src/requests/users/auth/sendForgotPasswordRequest.ts b/src/requests/users/auth/sendForgotPasswordRequest.ts deleted file mode 100644 index 8bbf962..0000000 --- a/src/requests/users/auth/sendForgotPasswordRequest.ts +++ /dev/null @@ -1,24 +0,0 @@ -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -const sendForgotPasswordRequest = async (email: string) => { - const response = await fetch('/api/users/forgot-password', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email }), - }); - - if (!response.ok) { - throw new Error("Something went wrong and we couldn't send the reset link."); - } - - const json = await response.json(); - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error(parsed.error.message); - } - - return parsed.data; -}; - -export default sendForgotPasswordRequest; diff --git a/src/requests/users/auth/sendLoginUserRequest.ts b/src/requests/users/auth/sendLoginUserRequest.ts deleted file mode 100644 index 7be9e64..0000000 --- a/src/requests/users/auth/sendLoginUserRequest.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BasicUserInfoSchema } from '@/config/auth/types'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -const sendLoginUserRequest = async (data: { username: string; password: string }) => { - const response = await fetch('/api/users/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - const json: unknown = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - if (!parsed.success) { - throw new Error('API response validation failed'); - } - - if (!parsed.data.success) { - throw new Error(parsed.data.message); - } - const parsedPayload = BasicUserInfoSchema.safeParse(parsed.data.payload); - if (!parsedPayload.success) { - throw new Error('API response payload validation failed'); - } - - return parsedPayload.data; -}; - -export default sendLoginUserRequest; diff --git a/src/requests/users/auth/sendRegisterUserRequest.ts b/src/requests/users/auth/sendRegisterUserRequest.ts deleted file mode 100644 index 3cb6c11..0000000 --- a/src/requests/users/auth/sendRegisterUserRequest.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CreateUserValidationSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; -import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -async function sendRegisterUserRequest(data: z.infer) { - const response = await fetch('/api/users/register', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }); - - const json = await response.json(); - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('API response validation failed.'); - } - - if (!parsed.data.success) { - throw new Error(parsed.data.message); - } - - const parsedPayload = GetUserSchema.safeParse(parsed.data.payload); - - if (!parsedPayload.success) { - throw new Error('API response payload validation failed.'); - } - - return parsedPayload.data; -} - -export default sendRegisterUserRequest; diff --git a/src/requests/users/auth/sendUpdatePasswordRequest.ts b/src/requests/users/auth/sendUpdatePasswordRequest.ts deleted file mode 100644 index d565c30..0000000 --- a/src/requests/users/auth/sendUpdatePasswordRequest.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -const sendUpdatePasswordRequest = async (data: z.infer) => { - const response = await fetch('/api/users/edit-password', { - body: JSON.stringify(data), - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - }); - - if (!response.ok) { - throw new Error(response.statusText); - } - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('API response validation failed.'); - } - - return parsed.data; -}; - -export default sendUpdatePasswordRequest; diff --git a/src/requests/users/auth/sendUserFollowRequest.ts b/src/requests/users/auth/sendUserFollowRequest.ts deleted file mode 100644 index 30f1347..0000000 --- a/src/requests/users/auth/sendUserFollowRequest.ts +++ /dev/null @@ -1,16 +0,0 @@ -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -const sendUserFollowRequest = async (userId: string) => { - const response = await fetch(`/api/users/${userId}/follow-user`, { method: 'POST' }); - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - throw new Error('Invalid API response.'); - } - - return parsed; -}; - -export default sendUserFollowRequest; diff --git a/src/requests/users/auth/types/index.ts b/src/requests/users/auth/types/index.ts new file mode 100644 index 0000000..c4d1f9c --- /dev/null +++ b/src/requests/users/auth/types/index.ts @@ -0,0 +1,41 @@ +import { BasicUserInfoSchema } from '@/config/auth/types'; +import { + CreateUserValidationSchema, + UpdatePasswordSchema, +} from '@/services/users/auth/schema/CreateUserValidationSchemas'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { z } from 'zod'; + +export type SendEditUserRequest = (args: { + user: z.infer; + data: { + username: string; + email: string; + firstName: string; + lastName: string; + }; +}) => Promise>; + +export type SendForgotPasswordRequest = (args: { + email: string; +}) => Promise>; + +export type SendLoginUserRequest = (args: { + username: string; + password: string; +}) => Promise>; + +export type SendRegisterUserRequest = ( + args: z.infer, +) => Promise>; + +export type SendUpdatePasswordRequest = ( + args: z.infer, +) => Promise>; + +export type SendUserFollowRequest = (args: { + userId: string; +}) => Promise>; + +export type ValidateEmailRequest = (args: { email: string }) => Promise; diff --git a/src/requests/users/auth/validateEmailRequest.ts b/src/requests/users/auth/validateEmailRequest.ts deleted file mode 100644 index 4e87291..0000000 --- a/src/requests/users/auth/validateEmailRequest.ts +++ /dev/null @@ -1,25 +0,0 @@ -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { z } from 'zod'; - -const validateEmailRequest = async (email: string) => { - const response = await fetch(`/api/users/check-email?email=${email}`); - const json = await response.json(); - - const parsed = APIResponseValidationSchema.safeParse(json); - - if (!parsed.success) { - return false; - } - - const parsedPayload = z - .object({ emailIsTaken: z.boolean() }) - .safeParse(parsed.data.payload); - - if (!parsedPayload.success) { - return false; - } - - return !parsedPayload.data.emailIsTaken; -}; - -export default validateEmailRequest; diff --git a/src/services/users/auth/schema/CreateUserValidationSchemas.ts b/src/services/users/auth/schema/CreateUserValidationSchemas.ts index 38aae9e..ab3c1bc 100644 --- a/src/services/users/auth/schema/CreateUserValidationSchemas.ts +++ b/src/services/users/auth/schema/CreateUserValidationSchemas.ts @@ -1,4 +1,4 @@ -import validateEmailRequest from '@/requests/users/auth/validateEmailRequest'; +import { validateEmailRequest } from '@/requests/users/auth'; import validateUsernameRequest from '@/requests/users/profile/validateUsernameRequest'; import sub from 'date-fns/sub'; import { z } from 'zod'; @@ -60,7 +60,7 @@ export const CreateUserValidationSchemaWithUsernameAndEmailCheck = email: z .string() .email({ message: 'Email must be a valid email address.' }) - .refine(async (email) => validateEmailRequest(email), { + .refine(async (email) => validateEmailRequest({ email }), { message: 'Email is already taken.', }), username: z From 34e960f0c620b375674dfb4871dd487a26cae3b1 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 19 Feb 2024 16:28:58 -0500 Subject: [PATCH 10/13] Update theme colors and layout styles, upgrade daisyui/tailwind --- package-lock.json | 198 +++++------------- package.json | 7 +- public/background.jpg | Bin 0 -> 207466 bytes src/components/Account/UpdateProfileLink.tsx | 2 +- src/components/Account/UserPosts.tsx | 8 +- .../BreweryPost/CreateBreweryPostForm.tsx | 10 +- src/components/ui/Layout.tsx | 7 +- src/components/ui/Navbar.tsx | 78 ++++--- src/pages/beers/[id]/index.tsx | 10 +- src/pages/beers/styles/[id]/index.tsx | 8 +- src/pages/breweries/[id]/index.tsx | 6 +- src/pages/index.tsx | 21 +- src/pages/login/index.tsx | 6 +- src/pages/users/[id].tsx | 2 +- src/pages/users/account/index.tsx | 16 +- src/pages/users/current.tsx | 2 +- tailwind.config.js | 6 +- 17 files changed, 155 insertions(+), 232 deletions(-) create mode 100644 public/background.jpg diff --git a/package-lock.json b/package-lock.json index 0bc327b..4696747 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@react-email/tailwind": "^0.0.12", "@vercel/analytics": "^1.1.0", "argon2": "^0.31.1", + "classnames": "^2.5.1", "cloudinary": "^1.41.0", "cookie": "^0.5.0", "date-fns": "^2.30.0", @@ -37,7 +38,7 @@ "passport-local": "^1.0.0", "pino": "^8.14.1", "react": "^18.2.0", - "react-daisyui": "^4.1.2", + "react-daisyui": "^5.0.0", "react-dom": "^18.2.0", "react-email": "^1.9.5", "react-hook-form": "^7.45.2", @@ -63,10 +64,9 @@ "@types/passport-local": "^1.0.35", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", - "@types/sparkpost": "^2.1.5", "@vercel/fetch": "^7.0.0", "autoprefixer": "^10.4.14", - "daisyui": "^3.9.2", + "daisyui": "^4.7.2", "dotenv-cli": "^7.2.1", "eslint": "^8.51.0", "eslint-config-airbnb-base": "15.0.0", @@ -81,7 +81,7 @@ "prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-tailwindcss": "^0.5.7", "prisma": "^5.7.0", - "tailwindcss": "^3.3.3", + "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.6", "ts-node": "^10.9.1", "typescript": "^5.3.2" @@ -2109,12 +2109,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/caseless": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.3.tgz", - "integrity": "sha512-ZD/NsIJYq/2RH+hY7lXmstfp/v9djGt9ah+xRQ3pcgR79qiKsG4pLl25AI7IcXxVO8dH9GiBE5rAknC0ePntlw==", - "dev": true - }, "node_modules/@types/connect": { "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", @@ -2376,32 +2370,6 @@ "@types/react": "*" } }, - "node_modules/@types/request": { - "version": "2.48.9", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.9.tgz", - "integrity": "sha512-4mi2hYsvPAhe8RXjk5DKB09sAUzbK68T2XjORehHdWyxFoX2zUnfi1VQ5wU4Md28H/5+uB4DkxY9BS4B87N/0A==", - "dev": true, - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - } - }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/@types/responselike": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.1.tgz", @@ -2449,22 +2417,6 @@ "@types/node": "*" } }, - "node_modules/@types/sparkpost": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/sparkpost/-/sparkpost-2.1.6.tgz", - "integrity": "sha512-lqimYaHi52iIJBge6XZBvLFGBjlZgSlsvUsARALgaYLpsUriPhayYKc8fyP5dgHmjJ6ClumP3BQHYT5Vbci6ew==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/request": "*" - } - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", - "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", - "dev": true - }, "node_modules/@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", @@ -3662,9 +3614,9 @@ } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/cli-cursor": { "version": "3.1.0", @@ -3758,11 +3710,6 @@ "color-support": "bin.js" } }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3926,16 +3873,23 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/culori": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz", + "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/daisyui": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.4.tgz", - "integrity": "sha512-fvi2RGH4YV617/6DntOVGcOugOPym9jTGWW2XySb5ZpvdWO4L7bEG77VHirrnbRUEWvIEVXkBpxUz2KFj0rVnA==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.7.2.tgz", + "integrity": "sha512-9UCss12Zmyk/22u+JbkVrHHxOzFOyY17HuqP5LeswI4hclbj6qbjJTovdj2zRy8cCH6/n6Wh0lTLjriGnyGh0g==", "dependencies": { - "colord": "^2.9", "css-selector-tokenizer": "^0.8", - "postcss": "^8", - "postcss-js": "^4", - "tailwindcss": "^3.1" + "culori": "^3", + "picocolors": "^1", + "postcss-js": "^4" }, "engines": { "node": ">=16.9.0" @@ -8782,11 +8736,11 @@ } }, "node_modules/react-daisyui": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-daisyui/-/react-daisyui-4.1.2.tgz", - "integrity": "sha512-Sx8ziaxKDe/59bw+UxTFOoDSJEuA8iGhgmMbzSAtnhaaZPP20kluHG+1/wY5mBSxfcAuk6oI8fqKcJRp55WzPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-daisyui/-/react-daisyui-5.0.0.tgz", + "integrity": "sha512-j1cugAxALmIbihycGBh7P9H3UXrfsvqu84qM6O55l7nOKST43IjUkZjmNav/7s0ZaUpa9Y+52mFchUb2zvon1A==", "peerDependencies": { - "daisyui": "^3.0.22", + "daisyui": "^4.4.6", "react": ">=16", "react-dom": ">=16", "tailwindcss": ">=3.2.7" @@ -10113,19 +10067,19 @@ "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" }, "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -12392,12 +12346,6 @@ "@types/responselike": "^1.0.0" } }, - "@types/caseless": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.3.tgz", - "integrity": "sha512-ZD/NsIJYq/2RH+hY7lXmstfp/v9djGt9ah+xRQ3pcgR79qiKsG4pLl25AI7IcXxVO8dH9GiBE5rAknC0ePntlw==", - "dev": true - }, "@types/connect": { "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", @@ -12658,31 +12606,6 @@ "@types/react": "*" } }, - "@types/request": { - "version": "2.48.9", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.9.tgz", - "integrity": "sha512-4mi2hYsvPAhe8RXjk5DKB09sAUzbK68T2XjORehHdWyxFoX2zUnfi1VQ5wU4Md28H/5+uB4DkxY9BS4B87N/0A==", - "dev": true, - "requires": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - }, - "dependencies": { - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, "@types/responselike": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.1.tgz", @@ -12730,22 +12653,6 @@ "@types/node": "*" } }, - "@types/sparkpost": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/sparkpost/-/sparkpost-2.1.6.tgz", - "integrity": "sha512-lqimYaHi52iIJBge6XZBvLFGBjlZgSlsvUsARALgaYLpsUriPhayYKc8fyP5dgHmjJ6ClumP3BQHYT5Vbci6ew==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/request": "*" - } - }, - "@types/tough-cookie": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", - "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", - "dev": true - }, "@types/unist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz", @@ -13582,9 +13489,9 @@ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "cli-cursor": { "version": "3.1.0", @@ -13652,11 +13559,6 @@ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -13787,16 +13689,20 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "culori": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz", + "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==" + }, "daisyui": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.4.tgz", - "integrity": "sha512-fvi2RGH4YV617/6DntOVGcOugOPym9jTGWW2XySb5ZpvdWO4L7bEG77VHirrnbRUEWvIEVXkBpxUz2KFj0rVnA==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.7.2.tgz", + "integrity": "sha512-9UCss12Zmyk/22u+JbkVrHHxOzFOyY17HuqP5LeswI4hclbj6qbjJTovdj2zRy8cCH6/n6Wh0lTLjriGnyGh0g==", "requires": { - "colord": "^2.9", "css-selector-tokenizer": "^0.8", - "postcss": "^8", - "postcss-js": "^4", - "tailwindcss": "^3.1" + "culori": "^3", + "picocolors": "^1", + "postcss-js": "^4" } }, "damerau-levenshtein": { @@ -17178,9 +17084,9 @@ } }, "react-daisyui": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-daisyui/-/react-daisyui-4.1.2.tgz", - "integrity": "sha512-Sx8ziaxKDe/59bw+UxTFOoDSJEuA8iGhgmMbzSAtnhaaZPP20kluHG+1/wY5mBSxfcAuk6oI8fqKcJRp55WzPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-daisyui/-/react-daisyui-5.0.0.tgz", + "integrity": "sha512-j1cugAxALmIbihycGBh7P9H3UXrfsvqu84qM6O55l7nOKST43IjUkZjmNav/7s0ZaUpa9Y+52mFchUb2zvon1A==", "requires": {} }, "react-dom": { @@ -18164,19 +18070,19 @@ "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" }, "tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "requires": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", diff --git a/package.json b/package.json index 7fe47f4..e8bb9ac 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@react-email/tailwind": "^0.0.12", "@vercel/analytics": "^1.1.0", "argon2": "^0.31.1", + "classnames": "^2.5.1", "cloudinary": "^1.41.0", "cookie": "^0.5.0", "date-fns": "^2.30.0", @@ -43,7 +44,7 @@ "passport-local": "^1.0.0", "pino": "^8.14.1", "react": "^18.2.0", - "react-daisyui": "^4.1.2", + "react-daisyui": "^5.0.0", "react-dom": "^18.2.0", "react-email": "^1.9.5", "react-hook-form": "^7.45.2", @@ -71,7 +72,7 @@ "@types/react-dom": "^18.2.7", "@vercel/fetch": "^7.0.0", "autoprefixer": "^10.4.14", - "daisyui": "^3.9.2", + "daisyui": "^4.7.2", "dotenv-cli": "^7.2.1", "eslint": "^8.51.0", "eslint-config-airbnb-base": "15.0.0", @@ -86,7 +87,7 @@ "prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-tailwindcss": "^0.5.7", "prisma": "^5.7.0", - "tailwindcss": "^3.3.3", + "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.6", "ts-node": "^10.9.1", "typescript": "^5.3.2" diff --git a/public/background.jpg b/public/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27841d8261e0e817307ab3cf765c160cd526b7f1 GIT binary patch literal 207466 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 `9#6D#AN~gG~eVta^g5>VQu4nmZG{DRK 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

>E3sFD_} zknpH-te@r2&B^vs_pzrOv3mLDf_+w^%~Gk+L0vax`$_doxu6uekJ+yunb!xk!6Ch@ zYJ*+7zhapm$>AeuH;XNLin@)HKvWr!u`|$8dtLLwu+_9`h_-#h{LtY5qvt$}J5Oh6 z^K~@$?vQ5o%cznkd_tmnt&EF@WQD$SWu35NT7FMUnDQ=;Oe>jYd=b*UJKb*>T0Xn` z14@n<|NM3rdO3dK?XD4pJ@P-$#YwF3(DO!I`dQOOtslmF&m49WTF<~IV81X9JJd4v__9%8)w2s( zo$}k=W~sVEw4FzEB`B|VbE3g{yx${OH{+#ja@TkT*Wb_$hPR$BsOr$gZbajt`qlL$ zUMJ}+^fQZcoz*<@_0ir_U-N`Y_=7rb{?^7vZsDL&Zy=Ynws$R5-t(N0`O?nL_<~CW zRF%rC)_ZmqRwYe(?Nj9_;zPs+%enBnu@cj5r=l2+389g z-r63el$pJnjJQ3!@c)^SIUb2r-l9F zS182dvCbj9Ab>E1L~99=gA#kq!$x+gsT_g<5?8y3k;=2a&nFgP}> zCHV$@XjC4%pTex#bbA3g*w9wAq(|CA?*lah%zw|*(8*K}YH7xVNXA$CgMOw{glSw} zN@(9(jruLMkq@5kSj|r=0D6;3Luw9q;wPf;$p(SrRj~j_g(d$;CV7e3hEfW1Ex7{Z zU@XHF+1WyhO6IX<=@t_PDh%J(uH1N=qR^|{yx^m(;lY7kI6`v5-MCN?T~D^VXZci* z$RGxuJw7+t&FM-r{b`cqib90RE z6J;%Pi=rg>TE+1>gZJP^ylgyr&vfL^pL6;%yoXC;&UJhym-{QMaqE(%XhE)7;+Gy}z{yBaW}aVw?hMW!Zx0~7e8QB7Py$5IuL z0qgvXU_{IKeCnCofj&ss-D5ApjnE_hO5m-+br#Uo(~dV9qStLqz{yEjjxbq}fl$?h zAEwC$3<%;*C081ia$E`H3FCBY=q4OA%_a2%o%$#n9W}k~D77|Ue02Du5o~(GB8$o* z1;i@%^<%?L$fm=rqNMuwj)~1Vw@alWa|bomVSRcfud{{Q!YU-&!Ab5FTLJ6TThc`sr{ot)sYVKUeK=O*dK z^-?GOVHUJkpz6XM=aXccr#kKi7Nf39K}mABwiN8*6#Dm{lz%U>cE6-tuRf*R z5Bw96`a>>afOV;1^*M#0o520=jUeV;umT9wℑR8b-Ov>v_$_HjkQvH#UlHjymCz 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 QaZ_?|ms6bA7oQaW2eG*F`~Uy| literal 0 HcmV?d00001 diff --git a/src/components/Account/UpdateProfileLink.tsx b/src/components/Account/UpdateProfileLink.tsx index e8c941a..e354a5b 100644 --- a/src/components/Account/UpdateProfileLink.tsx +++ b/src/components/Account/UpdateProfileLink.tsx @@ -5,7 +5,7 @@ import { FaArrowRight } from 'react-icons/fa'; const UpdateProfileLink: React.FC = () => { return ( -

+
diff --git a/src/components/Account/UserPosts.tsx b/src/components/Account/UserPosts.tsx index 5149649..65b3661 100644 --- a/src/components/Account/UserPosts.tsx +++ b/src/components/Account/UserPosts.tsx @@ -8,11 +8,9 @@ const UserPosts: FC = () => {
- - Beers - - Breweries - + + Beers + Breweries diff --git a/src/components/BreweryPost/CreateBreweryPostForm.tsx b/src/components/BreweryPost/CreateBreweryPostForm.tsx index d61e444..cc473a1 100644 --- a/src/components/BreweryPost/CreateBreweryPostForm.tsx +++ b/src/components/BreweryPost/CreateBreweryPostForm.tsx @@ -250,13 +250,9 @@ const CreateBreweryPostForm: FC<{ autoComplete="off" > - - - Information - - - Location - + + Information + Location diff --git a/src/components/ui/Layout.tsx b/src/components/ui/Layout.tsx index 8358a7e..6e1a8b6 100644 --- a/src/components/ui/Layout.tsx +++ b/src/components/ui/Layout.tsx @@ -3,13 +3,10 @@ import Navbar from './Navbar'; const Layout: FC<{ children: ReactNode }> = ({ children }) => { return ( -
+
-
- {children} -
+ {children}
); }; - export default Layout; diff --git a/src/components/ui/Navbar.tsx b/src/components/ui/Navbar.tsx index 675a8ca..178a2e3 100644 --- a/src/components/ui/Navbar.tsx +++ b/src/components/ui/Navbar.tsx @@ -3,9 +3,11 @@ import useNavbar from '@/hooks/utilities/useNavbar'; import useTheme from '@/hooks/utilities/useTheme'; import Link from 'next/link'; -import { FC } from 'react'; +import { FC, useRef } from 'react'; import { MdDarkMode, MdLightMode } from 'react-icons/md'; -import { GiHamburgerMenu } from 'react-icons/gi'; + +import { FaBars } from 'react-icons/fa'; +import classNames from 'classnames'; const DesktopLinks: FC = () => { const { pages, currentURL } = useNavbar(); @@ -19,8 +21,8 @@ const DesktopLinks: FC = () => { {page.name} @@ -35,24 +37,44 @@ const DesktopLinks: FC = () => { const MobileLinks: FC = () => { const { pages } = useNavbar(); + + const drawerRef = useRef(null); return (
-
- -
    - {pages.map((page) => ( -
  • - - {page.name} - -
  • - ))} -
+
+ +
+ +
+
+
); @@ -62,13 +84,21 @@ const Navbar = () => { const isDesktopView = useMediaQuery('(min-width: 1024px)'); const { theme, setTheme } = useTheme(); + const { currentURL } = useNavbar(); return ( -
+
- - The Biergarten App - + {currentURL === '/' ? null : ( + + The Biergarten App + + )}
= ({ beerPost }) => {
) : ( - - - Comments - - - Other Beers - + + Comments + Other Beers diff --git a/src/pages/beers/styles/[id]/index.tsx b/src/pages/beers/styles/[id]/index.tsx index 58d96b4..7340161 100644 --- a/src/pages/beers/styles/[id]/index.tsx +++ b/src/pages/beers/styles/[id]/index.tsx @@ -41,11 +41,9 @@ const BeerStyleByIdPage: NextPage = ({ beerStyle }) => {
) : ( - - - Comments - - + + Comments + Beers in this Style diff --git a/src/pages/breweries/[id]/index.tsx b/src/pages/breweries/[id]/index.tsx index a081fe4..19f0faa 100644 --- a/src/pages/breweries/[id]/index.tsx +++ b/src/pages/breweries/[id]/index.tsx @@ -85,11 +85,11 @@ const BreweryByIdPage: NextPage = ({ breweryPost, mapboxToken token={mapboxToken} /> - - + + Comments - + Beers diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b9e9602..8db5cbf 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,6 @@ import { NextPage } from 'next'; import Head from 'next/head'; +import Image from 'next/image'; const keywords = [ 'beer', @@ -29,7 +30,7 @@ const keywords = [ 'beer recipes', ]; -const description = `The Biergarten App is an app for beer lovers to share their favourite brews and breweries with like-minded people online.`; +const description = `An app for beer lovers to share their favourite brews and breweries with like-minded people online.`; const Home: NextPage = () => { return ( @@ -39,13 +40,17 @@ const Home: NextPage = () => { - -
-
-

- The Biergarten App -

-

{description}

+
+ +
+

The Biergarten App

+

{description}

diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index f80db46..9d07323 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -18,8 +18,8 @@ const LoginPage: NextPage = () => { -
-
+
+
{ className="h-full w-full object-cover" />
-
+
diff --git a/src/pages/users/[id].tsx b/src/pages/users/[id].tsx index 170a8ff..0940db9 100644 --- a/src/pages/users/[id].tsx +++ b/src/pages/users/[id].tsx @@ -25,7 +25,7 @@ const UserInfoPage: FC = ({ user }) => { <> -
+
diff --git a/src/pages/users/account/index.tsx b/src/pages/users/account/index.tsx index 7c6445e..43527aa 100644 --- a/src/pages/users/account/index.tsx +++ b/src/pages/users/account/index.tsx @@ -33,7 +33,7 @@ const AccountPage: NextPage = () => { content="Your account page. Here you can view your account information, change your settings, and view your posts." /> -
+
@@ -48,22 +48,18 @@ const AccountPage: NextPage = () => {
- - - Account - - - Your Posts - + + Account + Your Posts - + - + diff --git a/src/pages/users/current.tsx b/src/pages/users/current.tsx index 2111bbc..2e1c6e1 100644 --- a/src/pages/users/current.tsx +++ b/src/pages/users/current.tsx @@ -22,7 +22,7 @@ const ProtectedPage: NextPage = () => { Hello! | The Biergarten App -
+
{isLoading && } {user && !isLoading && ( <> diff --git a/tailwind.config.js b/tailwind.config.js index bf6fc8b..5beff7c 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -27,9 +27,9 @@ const myThemes = { warning: 'hsl(40, 76%, 73%)', 'primary-content': 'hsl(0, 0%, 0%)', 'error-content': 'hsl(0, 0%, 0%)', - 'base-300': 'hsl(180, 10%, 88%)', - 'base-200': 'hsl(180, 10%, 92%)', - 'base-100': 'hsl(180, 10%, 95%)', + 'base-300': 'hsl(180, 10%, 70%)', + 'base-200': 'hsl(180, 10%, 75%)', + 'base-100': 'hsl(180, 10%, 80%)', }, }; From f5d9a52572e879d6bb2be4d44d021145d05a108b Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Fri, 23 Feb 2024 21:25:56 -0500 Subject: [PATCH 11/13] Remove react-page-scroller dependency and replace tailwind animation plugin --- package-lock.json | 350 +----------------- package.json | 3 +- .../BeerById/BeerRecommendations.tsx | 8 +- .../Comments/CommentContentBody.tsx | 11 +- .../Comments/CommentLoadingCardBody.tsx | 2 +- src/components/Comments/EditCommentBody.tsx | 12 +- src/pages/404.tsx | 2 +- tailwind.config.js | 3 +- 8 files changed, 39 insertions(+), 352 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4696747..c4dd6f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,6 @@ "react-icons": "^4.10.1", "react-intersection-observer": "^9.5.2", "react-map-gl": "^7.1.2", - "react-page-scroller": "^3.0.1", "react-responsive-carousel": "^3.2.23", "swr": "^2.2.0", "theme-change": "^2.5.0", @@ -82,7 +81,7 @@ "prettier-plugin-tailwindcss": "^0.5.7", "prisma": "^5.7.0", "tailwindcss": "^3.4.1", - "tailwindcss-animate": "^1.0.6", + "tailwindcss-animated": "^1.0.1", "ts-node": "^10.9.1", "typescript": "^5.3.2" } @@ -3158,49 +3157,6 @@ "dequal": "^2.0.3" } }, - "node_modules/babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", - "dependencies": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - } - }, - "node_modules/babel-polyfill/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/babel-polyfill/node_modules/regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5742,26 +5698,6 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", - "peer": true - }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -5969,15 +5905,6 @@ "node": ">= 0.10" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -8155,21 +8082,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "peer": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-to-regexp/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "peer": true - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8939,21 +8851,6 @@ } } }, - "node_modules/react-page-scroller": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-page-scroller/-/react-page-scroller-3.0.1.tgz", - "integrity": "sha512-1OTlUHOSFCG8wmYzy3wo4dUIugXORzgZZ/Pj4BbkxN3n+Nv5tynfo7kygUiLYgE0QhDJslzZ4lntpCONAlv/vA==", - "dependencies": { - "babel-polyfill": "^6.26.0" - }, - "peerDependencies": { - "babel-polyfill": "^6.26.0", - "prop-types": "^15.6.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^4.3.1" - } - }, "node_modules/react-responsive-carousel": { "version": "3.2.23", "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", @@ -8964,41 +8861,6 @@ "react-easy-swipe": "^0.0.21" } }, - "node_modules/react-router": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", - "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", - "peer": true, - "dependencies": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-dom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", - "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", - "peer": true, - "dependencies": { - "history": "^4.7.2", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.1", - "react-router": "^4.3.1", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": ">=15" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9308,12 +9170,6 @@ "node": ">=4" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "peer": true - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -10102,13 +9958,13 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "node_modules/tailwindcss-animated": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tailwindcss-animated/-/tailwindcss-animated-1.0.1.tgz", + "integrity": "sha512-u5wusj89ZwP8I+s8WZlaAd7aZTWBN/XEG6QgMKpkIKmAf3xP1A6WYf7oYIKmGaB10UAQaSqWopi/i1ozzZEs8Q==", "dev": true, "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" + "tailwindcss": ">=3.1.0" } }, "node_modules/tailwindcss/node_modules/arg": { @@ -10195,18 +10051,6 @@ "real-require": "^0.2.0" } }, - "node_modules/tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", - "peer": true - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "peer": true - }, "node_modules/tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -10694,12 +10538,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "peer": true - }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -10710,15 +10548,6 @@ "pbf": "^3.2.1" } }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -13170,49 +12999,6 @@ "dequal": "^2.0.3" } }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -15077,26 +14863,6 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "peer": true, - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", - "peer": true - }, "hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -15246,15 +15012,6 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "peer": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -16741,23 +16498,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "peer": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "peer": true - } - } - }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -17221,14 +16961,6 @@ "@types/mapbox-gl": ">=1.0.0" } }, - "react-page-scroller": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-page-scroller/-/react-page-scroller-3.0.1.tgz", - "integrity": "sha512-1OTlUHOSFCG8wmYzy3wo4dUIugXORzgZZ/Pj4BbkxN3n+Nv5tynfo7kygUiLYgE0QhDJslzZ4lntpCONAlv/vA==", - "requires": { - "babel-polyfill": "^6.26.0" - } - }, "react-responsive-carousel": { "version": "3.2.23", "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", @@ -17239,35 +16971,6 @@ "react-easy-swipe": "^0.0.21" } }, - "react-router": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", - "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", - "peer": true, - "requires": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" - } - }, - "react-router-dom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", - "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", - "peer": true, - "requires": { - "history": "^4.7.2", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.1", - "react-router": "^4.3.1", - "warning": "^4.0.1" - } - }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17509,12 +17212,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "peer": true - }, "resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -18105,10 +17802,10 @@ } } }, - "tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "tailwindcss-animated": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tailwindcss-animated/-/tailwindcss-animated-1.0.1.tgz", + "integrity": "sha512-u5wusj89ZwP8I+s8WZlaAd7aZTWBN/XEG6QgMKpkIKmAf3xP1A6WYf7oYIKmGaB10UAQaSqWopi/i1ozzZEs8Q==", "dev": true, "requires": {} }, @@ -18178,18 +17875,6 @@ "real-require": "^0.2.0" } }, - "tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", - "peer": true - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "peer": true - }, "tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -18547,12 +18232,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "peer": true - }, "vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", @@ -18563,15 +18242,6 @@ "pbf": "^3.2.1" } }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "peer": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index e8bb9ac..433f928 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "react-icons": "^4.10.1", "react-intersection-observer": "^9.5.2", "react-map-gl": "^7.1.2", - "react-page-scroller": "^3.0.1", "react-responsive-carousel": "^3.2.23", "swr": "^2.2.0", "theme-change": "^2.5.0", @@ -88,7 +87,7 @@ "prettier-plugin-tailwindcss": "^0.5.7", "prisma": "^5.7.0", "tailwindcss": "^3.4.1", - "tailwindcss-animate": "^1.0.6", + "tailwindcss-animated": "^1.0.1", "ts-node": "^10.9.1", "typescript": "^5.3.2" }, diff --git a/src/components/BeerById/BeerRecommendations.tsx b/src/components/BeerById/BeerRecommendations.tsx index 96d0887..e6c48ad 100644 --- a/src/components/BeerById/BeerRecommendations.tsx +++ b/src/components/BeerById/BeerRecommendations.tsx @@ -1,10 +1,11 @@ import Link from 'next/link'; -import { FC, MutableRefObject, useRef } from 'react'; +import { FC } from 'react'; import { useInView } from 'react-intersection-observer'; import { z } from 'zod'; import useBeerRecommendations from '@/hooks/data-fetching/beer-posts/useBeerRecommendations'; import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import debounce from 'lodash/debounce'; + import BeerRecommendationLoadingComponent from './BeerRecommendationLoadingComponent'; const BeerRecommendationsSection: FC<{ @@ -28,10 +29,8 @@ const BeerRecommendationsSection: FC<{ }, }); - const beerRecommendationsRef: MutableRefObject = useRef(null); - return ( -
+
<>
@@ -54,6 +53,7 @@ const BeerRecommendationsSection: FC<{
diff --git a/src/components/Comments/CommentContentBody.tsx b/src/components/Comments/CommentContentBody.tsx index adb35c4..b1d6f42 100644 --- a/src/components/Comments/CommentContentBody.tsx +++ b/src/components/Comments/CommentContentBody.tsx @@ -7,6 +7,8 @@ import Link from 'next/link'; import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; import { z } from 'zod'; +import { useInView } from 'react-intersection-observer'; +import classNames from 'classnames'; import CommentCardDropdown from './CommentCardDropdown'; interface CommentContentBodyProps { @@ -17,9 +19,16 @@ interface CommentContentBodyProps { const CommentContentBody: FC = ({ comment, setInEditMode }) => { const { user } = useContext(UserContext); const timeDistance = useTimeDistance(new Date(comment.createdAt)); + const [ref, inView] = useInView({ triggerOnce: true }); return ( -
+
diff --git a/src/components/Comments/CommentLoadingCardBody.tsx b/src/components/Comments/CommentLoadingCardBody.tsx index b662594..ca5800d 100644 --- a/src/components/Comments/CommentLoadingCardBody.tsx +++ b/src/components/Comments/CommentLoadingCardBody.tsx @@ -1,6 +1,6 @@ const CommentLoadingCardBody = () => { return ( -
+
diff --git a/src/components/Comments/EditCommentBody.tsx b/src/components/Comments/EditCommentBody.tsx index cb5d985..6c278dd 100644 --- a/src/components/Comments/EditCommentBody.tsx +++ b/src/components/Comments/EditCommentBody.tsx @@ -15,6 +15,8 @@ import FormLabel from '../ui/forms/FormLabel'; import FormSegment from '../ui/forms/FormSegment'; import FormTextArea from '../ui/forms/FormTextArea'; import { HandleDeleteCommentRequest, HandleEditCommentRequest } from './types'; +import { useInView } from 'react-intersection-observer'; +import classNames from 'classnames'; interface EditCommentBodyProps { comment: z.infer; @@ -80,8 +82,16 @@ const EditCommentBody: FC = ({ const disableForm = isSubmitting || isDeleting; + const [ref, inView] = useInView({ triggerOnce: true }); + return ( -
+
diff --git a/src/pages/404.tsx b/src/pages/404.tsx index a83cc5c..4869202 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -8,7 +8,7 @@ const NotFound: NextPage = () => { 404 Page Not Found -
+

404: Not Found

Sorry, the page you are looking for does not exist. diff --git a/tailwind.config.js b/tailwind.config.js index 5beff7c..e2f4ba6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -46,10 +46,9 @@ module.exports = { plugins: [ require('@headlessui/tailwindcss'), require('daisyui'), - require('tailwindcss-animate'), + require('tailwindcss-animated'), require('autoprefixer'), ], - daisyui: { logs: false, themes: [myThemes], From ac89833a5dd9c0ca855b6590641bae26b55f5340 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 25 Mar 2024 01:59:18 -0400 Subject: [PATCH 12/13] style: redo login page design --- src/components/Login/LoginForm.tsx | 2 +- src/components/ui/Navbar.tsx | 21 +++++++----- src/pages/index.tsx | 14 ++++---- src/pages/login/index.tsx | 52 ++++++++++++++---------------- tailwind.config.js | 24 +++----------- 5 files changed, 51 insertions(+), 62 deletions(-) diff --git a/src/components/Login/LoginForm.tsx b/src/components/Login/LoginForm.tsx index df2ecb9..282cea0 100644 --- a/src/components/Login/LoginForm.tsx +++ b/src/components/Login/LoginForm.tsx @@ -47,7 +47,7 @@ const LoginForm = () => { }; return ( - +
username diff --git a/src/components/ui/Navbar.tsx b/src/components/ui/Navbar.tsx index 178a2e3..f1287fd 100644 --- a/src/components/ui/Navbar.tsx +++ b/src/components/ui/Navbar.tsx @@ -1,10 +1,10 @@ import useMediaQuery from '@/hooks/utilities/useMediaQuery'; import useNavbar from '@/hooks/utilities/useNavbar'; -import useTheme from '@/hooks/utilities/useTheme'; +// import useTheme from '@/hooks/utilities/useTheme'; import Link from 'next/link'; import { FC, useRef } from 'react'; -import { MdDarkMode, MdLightMode } from 'react-icons/md'; +// import { MdDarkMode, MdLightMode } from 'react-icons/md'; import { FaBars } from 'react-icons/fa'; import classNames from 'classnames'; @@ -83,25 +83,30 @@ const MobileLinks: FC = () => { const Navbar = () => { const isDesktopView = useMediaQuery('(min-width: 1024px)'); - const { theme, setTheme } = useTheme(); const { currentURL } = useNavbar(); + const backgroundIsTransparent = + currentURL === '/' || currentURL === '/login' || currentURL === '/register'; + + const isOnHomePage = currentURL === '/'; + + // const { theme, setTheme } = useTheme(); return (
- {currentURL === '/' ? null : ( + {isOnHomePage ? null : ( The Biergarten App )}
-
@@ -126,7 +131,7 @@ const Navbar = () => { )}
-
+
*/}
{isDesktopView ? : }

); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8db5cbf..efee99a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,6 @@ import { NextPage } from 'next'; +import { CldImage } from 'next-cloudinary'; import Head from 'next/head'; -import Image from 'next/image'; const keywords = [ 'beer', @@ -40,12 +40,12 @@ const Home: NextPage = () => { -
- +
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 9d07323..2d38184 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -18,34 +18,32 @@ const LoginPage: NextPage = () => { -
-
- -
-
-
-
- -

Login

-
+
+ + +
+
+ +

Login

+
+
-
- - Don't have an account? - - - Forgot password? - -
+
+ +
+ + Don't have an account? + + + + Forgot your password?{' '} +
diff --git a/tailwind.config.js b/tailwind.config.js index e2f4ba6..c6a3588 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,7 @@ const myThemes = { dark: { - primary: 'hsl(227, 25%, 25%)', + primary: 'hsl(227, 10%, 25%)', secondary: 'hsl(255, 9%, 69%)', error: 'hsl(9, 52%, 57%)', accent: 'hsl(316, 96%, 60%)', @@ -12,24 +12,10 @@ const myThemes = { warning: 'hsl(50, 98%, 50%)', 'primary-content': 'hsl(0, 0%, 98%)', 'error-content': 'hsl(0, 0%, 98%)', - 'base-100': 'hsl(227, 20%, 20%)', - 'base-200': 'hsl(227, 20%, 13%)', - 'base-300': 'hsl(227, 20%, 10%)', - }, - light: { - primary: 'hsl(180, 20%, 70%)', - secondary: 'hsl(120, 10%, 70%)', - error: 'hsl(4, 87%, 74%)', - accent: 'hsl(93, 27%, 73%)', - neutral: 'hsl(38, 31%, 91%)', - info: 'hsl(163, 40%, 79%)', - success: 'hsl(93, 27%, 73%)', - warning: 'hsl(40, 76%, 73%)', - 'primary-content': 'hsl(0, 0%, 0%)', - 'error-content': 'hsl(0, 0%, 0%)', - 'base-300': 'hsl(180, 10%, 70%)', - 'base-200': 'hsl(180, 10%, 75%)', - 'base-100': 'hsl(180, 10%, 80%)', + 'base-content': 'hsl(227, 0%, 60%)', + 'base-100': 'hsl(227, 10%, 20%)', + 'base-200': 'hsl(227, 10%, 10%)', + 'base-300': 'hsl(227, 10%, 8%)', }, }; From b299ed946b833f265800075587eae8f6eb6a7fc4 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Sun, 7 Apr 2024 15:51:25 -0400 Subject: [PATCH 13/13] Refactor login and register page designs --- src/components/RegisterUserForm.tsx | 60 ++++++++++++++--------------- src/pages/index.tsx | 4 +- src/pages/login/index.tsx | 2 +- src/pages/register/index.tsx | 33 +++++++++++----- 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/src/components/RegisterUserForm.tsx b/src/components/RegisterUserForm.tsx index b126d45..835f677 100644 --- a/src/components/RegisterUserForm.tsx +++ b/src/components/RegisterUserForm.tsx @@ -44,13 +44,9 @@ const RegisterUserForm: FC = () => { } }; return ( - -
-
+ +
+
First name @@ -63,7 +59,7 @@ const RegisterUserForm: FC = () => { type="text" formValidationSchema={register('firstName')} error={!!errors.firstName} - placeholder="first name" + placeholder="John" />
@@ -80,13 +76,13 @@ const RegisterUserForm: FC = () => { type="text" formValidationSchema={register('lastName')} error={!!errors.lastName} - placeholder="last name" + placeholder="Doe" />
-
+
email @@ -99,7 +95,7 @@ const RegisterUserForm: FC = () => { type="email" formValidationSchema={register('email')} error={!!errors.email} - placeholder="email" + placeholder="john.doe@example.com" />
@@ -115,13 +111,13 @@ const RegisterUserForm: FC = () => { type="text" formValidationSchema={register('username')} error={!!errors.username} - placeholder="username" + placeholder="johndoe" />
-
+
password @@ -155,26 +151,28 @@ const RegisterUserForm: FC = () => {
- - Date of birth - {errors.dateOfBirth?.message} - - - - -
- +
+ + Date of birth + {errors.dateOfBirth?.message} + + + +
+
+ +
); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index efee99a..cb58ba6 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -49,8 +49,8 @@ const Home: NextPage = () => { className="pointer-events-none absolute h-full w-full object-cover mix-blend-overlay" />
-

The Biergarten App

-

{description}

+

The Biergarten App

+

{description}

diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 2d38184..f254883 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -28,7 +28,7 @@ const LoginPage: NextPage = () => { />
-
+

Login

diff --git a/src/pages/register/index.tsx b/src/pages/register/index.tsx index d67e25b..4fcde09 100644 --- a/src/pages/register/index.tsx +++ b/src/pages/register/index.tsx @@ -1,10 +1,11 @@ import RegisterUserForm from '@/components/RegisterUserForm'; -import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import useRedirectWhenLoggedIn from '@/hooks/auth/useRedirectIfLoggedIn'; import { NextPage } from 'next'; +import { CldImage } from 'next-cloudinary'; import Head from 'next/head'; -import { BiUser } from 'react-icons/bi'; + +import { FaUserCircle } from 'react-icons/fa'; const RegisterUserPage: NextPage = () => { useRedirectWhenLoggedIn(); @@ -15,14 +16,26 @@ const RegisterUserPage: NextPage = () => { Register User - - - + +
+ + +
+
+ +

Register

+
+
+ +
+
+
); };