mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 18:52:06 +00:00
Refactoring beer by id page, add delete comment
Refactored the comments ui into various new components, added the delete beer comment by id feature.
This commit is contained in:
@@ -49,7 +49,7 @@ const BeerCommentForm: FunctionComponent<BeerCommentFormProps> = ({ beerPost })
|
|||||||
beerPostId: beerPost.id,
|
beerPostId: beerPost.id,
|
||||||
});
|
});
|
||||||
reset();
|
reset();
|
||||||
router.replace(router.asPath, undefined, { scroll: false });
|
router.replace(`/beers/${beerPost.id}?comments_page=1`, undefined, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const { errors } = formState;
|
const { errors } = formState;
|
||||||
|
|||||||
@@ -56,13 +56,14 @@ const BeerInfoHeader: FC<{ beerPost: BeerPostQueryResult; initialLikeCount: numb
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{isPostOwner && (
|
{isPostOwner && (
|
||||||
|
<div className="tooltip tooltip-left" data-tip={`Edit '${beerPost.name}'`}>
|
||||||
<Link
|
<Link
|
||||||
className="btn-outline btn-sm btn gap-2 rounded-2xl"
|
|
||||||
href={`/beers/${beerPost.id}/edit`}
|
href={`/beers/${beerPost.id}/edit`}
|
||||||
|
className="btn btn-outline btn-sm"
|
||||||
>
|
>
|
||||||
<FaRegEdit className="text-xl" />
|
<FaRegEdit className="text-xl" />
|
||||||
Edit
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
50
components/BeerById/BeerPostCommentsPaginationBar.tsx
Normal file
50
components/BeerById/BeerPostCommentsPaginationBar.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
interface BeerCommentsPaginationBarProps {
|
||||||
|
commentsPageNum: number;
|
||||||
|
commentsPageCount: number;
|
||||||
|
beerPost: BeerPostQueryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BeerCommentsPaginationBar: FC<BeerCommentsPaginationBarProps> = ({
|
||||||
|
commentsPageNum,
|
||||||
|
commentsPageCount,
|
||||||
|
beerPost,
|
||||||
|
}) => (
|
||||||
|
<div className="flex items-center justify-center" id="comments-pagination">
|
||||||
|
<div className="btn-group grid w-6/12 grid-cols-2">
|
||||||
|
<Link
|
||||||
|
className={`btn-outline btn ${
|
||||||
|
commentsPageNum === 1
|
||||||
|
? 'btn-disabled pointer-events-none'
|
||||||
|
: 'pointer-events-auto'
|
||||||
|
}`}
|
||||||
|
href={{
|
||||||
|
pathname: `/beers/${beerPost.id}`,
|
||||||
|
query: { comments_page: commentsPageNum - 1 },
|
||||||
|
}}
|
||||||
|
scroll={false}
|
||||||
|
>
|
||||||
|
Next Comments
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className={`btn-outline btn ${
|
||||||
|
commentsPageNum === commentsPageCount
|
||||||
|
? 'btn-disabled pointer-events-none'
|
||||||
|
: 'pointer-events-auto'
|
||||||
|
}`}
|
||||||
|
href={{
|
||||||
|
pathname: `/beers/${beerPost.id}`,
|
||||||
|
query: { comments_page: commentsPageNum + 1 },
|
||||||
|
}}
|
||||||
|
scroll={false}
|
||||||
|
>
|
||||||
|
Previous Comments
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BeerCommentsPaginationBar;
|
||||||
64
components/BeerById/BeerPostCommentsSection.tsx
Normal file
64
components/BeerById/BeerPostCommentsSection.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import UserContext from '@/contexts/userContext';
|
||||||
|
import { BeerCommentQueryResultArrayT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||||
|
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { FC, useContext } from 'react';
|
||||||
|
import BeerCommentForm from './BeerCommentForm';
|
||||||
|
import BeerCommentsPaginationBar from './BeerPostCommentsPaginationBar';
|
||||||
|
import CommentCard from './CommentCard';
|
||||||
|
|
||||||
|
interface BeerPostCommentsSectionProps {
|
||||||
|
beerPost: BeerPostQueryResult;
|
||||||
|
setComments: React.Dispatch<React.SetStateAction<BeerCommentQueryResultArrayT>>;
|
||||||
|
comments: BeerCommentQueryResultArrayT;
|
||||||
|
commentsPageCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BeerPostCommentsSection: FC<BeerPostCommentsSectionProps> = ({
|
||||||
|
beerPost,
|
||||||
|
setComments,
|
||||||
|
comments,
|
||||||
|
commentsPageCount,
|
||||||
|
}) => {
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const commentsPageNum = parseInt(router.query.comments_page as string, 10) || 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full space-y-3 md:w-[60%]">
|
||||||
|
<div className="card h-96 bg-base-300">
|
||||||
|
<div className="card-body h-full">
|
||||||
|
{user ? (
|
||||||
|
<BeerCommentForm beerPost={beerPost} setComments={setComments} />
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
|
<span className="text-lg font-bold">Log in to leave a comment.</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{comments.length ? (
|
||||||
|
<div className="card bg-base-300 pb-6">
|
||||||
|
{comments.map((comment) => (
|
||||||
|
<CommentCard key={comment.id} comment={comment} beerPostId={beerPost.id} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<BeerCommentsPaginationBar
|
||||||
|
commentsPageNum={commentsPageNum}
|
||||||
|
commentsPageCount={commentsPageCount}
|
||||||
|
beerPost={beerPost}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="card items-center bg-base-300">
|
||||||
|
<div className="card-body">
|
||||||
|
<span className="text-lg font-bold">No comments yet.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BeerPostCommentsSection;
|
||||||
@@ -1,20 +1,69 @@
|
|||||||
|
import UserContext from '@/contexts/userContext';
|
||||||
import { BeerCommentQueryResultT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
import { BeerCommentQueryResultT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||||
import { format, formatDistanceStrict } from 'date-fns';
|
import { format, formatDistanceStrict } from 'date-fns';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { Rating } from 'react-daisyui';
|
import { Rating } from 'react-daisyui';
|
||||||
|
|
||||||
|
import { FaEllipsisH } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const CommentCardDropdown: React.FC<{
|
||||||
|
comment: BeerCommentQueryResultT;
|
||||||
|
beerPostId: string;
|
||||||
|
}> = ({ comment, beerPostId }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
|
||||||
|
const isCommentOwner = user?.id === comment.postedBy.id;
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const response = await fetch(`/api/beer-comments/${comment.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
router.replace(`/beers/${beerPostId}?comments_page=1`, undefined, { scroll: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dropdown">
|
||||||
|
<label tabIndex={0} className="btn btn-ghost btn-sm m-1">
|
||||||
|
<FaEllipsisH />
|
||||||
|
</label>
|
||||||
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className="dropdown-content menu rounded-box w-52 bg-base-100 p-2 shadow"
|
||||||
|
>
|
||||||
|
{isCommentOwner ? (
|
||||||
|
<li>
|
||||||
|
<button onClick={handleDelete}>Delete</button>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>
|
||||||
|
<button>Report</button>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const CommentCard: React.FC<{
|
const CommentCard: React.FC<{
|
||||||
comment: BeerCommentQueryResultT;
|
comment: BeerCommentQueryResultT;
|
||||||
}> = ({ comment }) => {
|
beerPostId: string;
|
||||||
|
}> = ({ comment, beerPostId }) => {
|
||||||
const [timeDistance, setTimeDistance] = useState('');
|
const [timeDistance, setTimeDistance] = useState('');
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeDistance(formatDistanceStrict(new Date(comment.createdAt), new Date()));
|
setTimeDistance(formatDistanceStrict(new Date(comment.createdAt), new Date()));
|
||||||
}, [comment.createdAt]);
|
}, [comment.createdAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card-body sm:h-64">
|
<div className="card-body">
|
||||||
<div className="flex flex-col justify-between sm:flex-row">
|
<div className="flex flex-col justify-between sm:flex-row">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold sm:text-2xl">
|
<h3 className="font-semibold sm:text-2xl">
|
||||||
@@ -33,7 +82,11 @@ const CommentCard: React.FC<{
|
|||||||
ago
|
ago
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
|
{user && <CommentCardDropdown comment={comment} beerPostId={beerPostId} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
<Rating value={comment.rating}>
|
<Rating value={comment.rating}>
|
||||||
{Array.from({ length: 5 }).map((val, index) => (
|
{Array.from({ length: 5 }).map((val, index) => (
|
||||||
<Rating.Item
|
<Rating.Item
|
||||||
@@ -45,10 +98,9 @@ const CommentCard: React.FC<{
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Rating>
|
</Rating>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>{comment.content}</p>
|
<p>{comment.content}</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import sendEditBeerPostRequest from '@/requests/sendEditBeerPostRequest';
|
import sendEditBeerPostRequest from '@/requests/sendEditBeerPostRequest';
|
||||||
import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema';
|
import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
@@ -52,7 +52,7 @@ const EditBeerPostForm: FC<EditBeerPostFormProps> = ({ previousValues }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="form-control" onSubmit={handleSubmit(onSubmit)}>
|
<form className="form-control" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="my-5">
|
<div className="mb-5">
|
||||||
{error && <ErrorAlert error={error} setError={setError} />}
|
{error && <ErrorAlert error={error} setError={setError} />}
|
||||||
</div>
|
</div>
|
||||||
<FormInfo>
|
<FormInfo>
|
||||||
@@ -116,24 +116,11 @@ const EditBeerPostForm: FC<EditBeerPostFormProps> = ({ previousValues }) => {
|
|||||||
/>
|
/>
|
||||||
</FormSegment>
|
</FormSegment>
|
||||||
|
|
||||||
<div className="mt-6 flex w-full space-x-6">
|
<div className="mt-2">
|
||||||
<div className="w-3/6">
|
|
||||||
<Link
|
|
||||||
className={`btn-primary btn w-full rounded-xl ${
|
|
||||||
isSubmitting ? 'loading' : ''
|
|
||||||
}`}
|
|
||||||
href={`/beers/${previousValues.id}`}
|
|
||||||
>
|
|
||||||
Discard Changes
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-3/6">
|
|
||||||
<Button type="submit" isSubmitting={isSubmitting}>
|
<Button type="submit" isSubmitting={isSubmitting}>
|
||||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
import { ReactNode, FC } from 'react';
|
import { ReactNode, FC } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
import { IconType } from 'react-icons';
|
import { IconType } from 'react-icons';
|
||||||
|
import { BiArrowBack } from 'react-icons/bi';
|
||||||
|
|
||||||
interface FormPageLayoutProps {
|
interface FormPageLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
headingText: string;
|
headingText: string;
|
||||||
headingIcon: IconType;
|
headingIcon: IconType;
|
||||||
|
backLink: string;
|
||||||
|
backLinkText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormPageLayout: FC<FormPageLayoutProps> = ({
|
const FormPageLayout: FC<FormPageLayoutProps> = ({
|
||||||
children: FormComponent,
|
children: FormComponent,
|
||||||
headingIcon,
|
headingIcon,
|
||||||
headingText,
|
headingText,
|
||||||
|
backLink,
|
||||||
|
backLinkText,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="align-center my-20 flex h-fit flex-col items-center justify-center">
|
<div className="align-center my-20 flex h-fit flex-col items-center justify-center">
|
||||||
<div className="w-8/12">
|
<div className="w-8/12">
|
||||||
<div className="my-4 flex flex-col items-center space-y-1">
|
<div className="tooltip tooltip-bottom absolute" data-tip={backLinkText}>
|
||||||
{headingIcon({ className: 'text-4xl' })}
|
<Link href={backLink} className="btn btn-ghost btn-sm">
|
||||||
|
<BiArrowBack className="text-xl" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center space-y-1">
|
||||||
|
{headingIcon({ className: 'text-4xl' })}{' '}
|
||||||
<h1 className="text-3xl font-bold">{headingText}</h1>
|
<h1 className="text-3xl font-bold">{headingText}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div>{FormComponent}</div>
|
<div>{FormComponent}</div>
|
||||||
@@ -42,6 +42,7 @@ export async function getLoginSession(req: SessionRequest) {
|
|||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
throw new ServerError('Session is invalid.', 401);
|
throw new ServerError('Session is invalid.', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { createdAt, maxAge } = parsed.data;
|
const { createdAt, maxAge } = parsed.data;
|
||||||
|
|
||||||
const expiresAt = createdAt + maxAge * 1000;
|
const expiresAt = createdAt + maxAge * 1000;
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import { GetServerSideProps, GetServerSidePropsContext, Redirect } from 'next';
|
import { GetServerSideProps, GetServerSidePropsContext, Redirect } from 'next';
|
||||||
import { getLoginSession } from '@/config/auth/session';
|
import { getLoginSession } from '@/config/auth/session';
|
||||||
|
import findUserById from '@/services/User/findUserById';
|
||||||
|
|
||||||
const redirectIfLoggedIn = (redirect: Redirect) => {
|
const redirectIfLoggedIn = (redirect: Redirect) => {
|
||||||
const fn: GetServerSideProps = async (context: GetServerSidePropsContext) => {
|
const fn: GetServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
try {
|
try {
|
||||||
const { req } = context;
|
const { req } = context;
|
||||||
await getLoginSession(req);
|
const session = await getLoginSession(req);
|
||||||
|
|
||||||
|
const { id } = session;
|
||||||
|
|
||||||
|
const user = await findUserById(id);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('Could not get user.');
|
||||||
|
}
|
||||||
|
|
||||||
return { redirect };
|
return { redirect };
|
||||||
} catch {
|
} catch {
|
||||||
return { props: {} };
|
return { props: {} };
|
||||||
|
|||||||
60
pages/api/beer-comments/[id].tsx
Normal file
60
pages/api/beer-comments/[id].tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||||
|
import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser';
|
||||||
|
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||||
|
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||||
|
import ServerError from '@/config/util/ServerError';
|
||||||
|
import DBClient from '@/prisma/DBClient';
|
||||||
|
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||||
|
import { NextApiResponse } from 'next';
|
||||||
|
import { createRouter } from 'next-connect';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
interface DeleteCommentRequest extends UserExtendedNextApiRequest {
|
||||||
|
query: { id: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteComment = async (
|
||||||
|
req: DeleteCommentRequest,
|
||||||
|
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||||
|
) => {
|
||||||
|
const { id } = req.query;
|
||||||
|
const user = req.user!;
|
||||||
|
|
||||||
|
const comment = await DBClient.instance.beerComment.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!comment) {
|
||||||
|
throw new ServerError('Comment not found', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment.postedById !== user.id) {
|
||||||
|
throw new ServerError('You are not authorized to delete this comment', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DBClient.instance.beerComment.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Comment deleted successfully',
|
||||||
|
statusCode: 200,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = createRouter<
|
||||||
|
DeleteCommentRequest,
|
||||||
|
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||||
|
>();
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
validateRequest({
|
||||||
|
querySchema: z.object({ id: z.string().uuid() }),
|
||||||
|
}),
|
||||||
|
getCurrentUser,
|
||||||
|
deleteComment,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handler = router.handler(NextConnectOptions);
|
||||||
|
export default handler;
|
||||||
@@ -32,9 +32,7 @@ const editBeerPost = async (
|
|||||||
throw new ServerError('You cannot edit that beer post.', 403);
|
throw new ServerError('You cannot edit that beer post.', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await editBeerPostById(id, body);
|
await editBeerPostById(id, body);
|
||||||
|
|
||||||
console.log(updated);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'Beer post updated successfully',
|
message: 'Beer post updated successfully',
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired';
|
|||||||
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
||||||
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||||
import EditBeerPostForm from '@/components/EditBeerPostForm';
|
import EditBeerPostForm from '@/components/EditBeerPostForm';
|
||||||
import FormPageLayout from '@/components/ui/forms/BeerPostFormPageLayout';
|
import FormPageLayout from '@/components/ui/forms/FormPageLayout';
|
||||||
import { BiBeer } from 'react-icons/bi';
|
import { BiBeer } from 'react-icons/bi';
|
||||||
|
|
||||||
interface EditPageProps {
|
interface EditPageProps {
|
||||||
@@ -24,7 +24,12 @@ const EditPage: NextPage<EditPageProps> = ({ beerPost }) => {
|
|||||||
<meta name="description" content={pageTitle} />
|
<meta name="description" content={pageTitle} />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<FormPageLayout headingText={pageTitle} headingIcon={BiBeer}>
|
<FormPageLayout
|
||||||
|
headingText={pageTitle}
|
||||||
|
headingIcon={BiBeer}
|
||||||
|
backLink={`/beers/${beerPost.id}`}
|
||||||
|
backLinkText={`Back to "${beerPost.name}"`}
|
||||||
|
>
|
||||||
<EditBeerPostForm
|
<EditBeerPostForm
|
||||||
previousValues={{
|
previousValues={{
|
||||||
name: beerPost.name,
|
name: beerPost.name,
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import BeerCommentForm from '@/components/BeerById/BeerCommentForm';
|
|
||||||
import BeerInfoHeader from '@/components/BeerById/BeerInfoHeader';
|
|
||||||
import BeerRecommendations from '@/components/BeerById/BeerRecommendations';
|
|
||||||
import CommentCard from '@/components/BeerById/CommentCard';
|
|
||||||
import Layout from '@/components/ui/Layout';
|
|
||||||
import UserContext from '@/contexts/userContext';
|
|
||||||
import DBClient from '@/prisma/DBClient';
|
|
||||||
import getAllBeerComments from '@/services/BeerComment/getAllBeerComments';
|
|
||||||
import { BeerCommentQueryResultArrayT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
|
||||||
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
|
||||||
import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations';
|
|
||||||
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
|
||||||
import { BeerPost } from '@prisma/client';
|
|
||||||
import { NextPage, GetServerSideProps } from 'next';
|
import { NextPage, GetServerSideProps } from 'next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useState, useEffect } from 'react';
|
||||||
import { useState, useEffect, useContext } from 'react';
|
|
||||||
|
import BeerInfoHeader from '@/components/BeerById/BeerInfoHeader';
|
||||||
|
import BeerPostCommentsSection from '@/components/BeerById/BeerPostCommentsSection';
|
||||||
|
import BeerRecommendations from '@/components/BeerById/BeerRecommendations';
|
||||||
|
import Layout from '@/components/ui/Layout';
|
||||||
|
|
||||||
|
import DBClient from '@/prisma/DBClient';
|
||||||
|
import getAllBeerComments from '@/services/BeerComment/getAllBeerComments';
|
||||||
|
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
||||||
|
import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations';
|
||||||
|
|
||||||
|
import { BeerCommentQueryResultArrayT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||||
|
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||||
|
import { BeerPost } from '@prisma/client';
|
||||||
|
|
||||||
interface BeerPageProps {
|
interface BeerPageProps {
|
||||||
beerPost: BeerPostQueryResult;
|
beerPost: BeerPostQueryResult;
|
||||||
@@ -36,15 +36,8 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
|||||||
commentsPageCount,
|
commentsPageCount,
|
||||||
likeCount,
|
likeCount,
|
||||||
}) => {
|
}) => {
|
||||||
const { user } = useContext(UserContext);
|
|
||||||
const [comments, setComments] = useState(beerComments);
|
const [comments, setComments] = useState(beerComments);
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const commentsPageNum = router.query.comments_page
|
|
||||||
? parseInt(router.query.comments_page as string, 10)
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setComments(beerComments);
|
setComments(beerComments);
|
||||||
}, [beerComments]);
|
}, [beerComments]);
|
||||||
@@ -67,63 +60,16 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="my-12 flex w-full items-center justify-center ">
|
<div className="my-12 flex w-full items-center justify-center ">
|
||||||
<div className="w-11/12 space-y-3 lg:w-9/12">
|
<div className="w-11/12 space-y-3 xl:w-9/12">
|
||||||
<BeerInfoHeader beerPost={beerPost} initialLikeCount={likeCount} />
|
<BeerInfoHeader beerPost={beerPost} initialLikeCount={likeCount} />
|
||||||
<div className="mt-4 flex flex-col space-y-3 sm:flex-row sm:space-y-0 sm:space-x-3">
|
<div className="mt-4 flex flex-col space-y-3 md:flex-row md:space-y-0 md:space-x-3">
|
||||||
<div className="w-full space-y-3 sm:w-[60%]">
|
<BeerPostCommentsSection
|
||||||
<div className="card h-96 bg-base-300">
|
beerPost={beerPost}
|
||||||
<div className="card-body h-full">
|
comments={comments}
|
||||||
{user ? (
|
setComments={setComments}
|
||||||
<BeerCommentForm beerPost={beerPost} setComments={setComments} />
|
commentsPageCount={commentsPageCount}
|
||||||
) : (
|
/>
|
||||||
<div className="flex h-full flex-col items-center justify-center">
|
<div className="md:w-[40%]">
|
||||||
<span className="text-lg font-bold">
|
|
||||||
Log in to leave a comment.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card bg-base-300 pb-6">
|
|
||||||
{comments.map((comment) => (
|
|
||||||
<CommentCard key={comment.id} comment={comment} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<div className="btn-group grid w-6/12 grid-cols-2">
|
|
||||||
<Link
|
|
||||||
className={`btn-outline btn ${
|
|
||||||
commentsPageNum === 1
|
|
||||||
? 'btn-disabled pointer-events-none'
|
|
||||||
: 'pointer-events-auto'
|
|
||||||
}`}
|
|
||||||
href={{
|
|
||||||
pathname: `/beers/${beerPost.id}`,
|
|
||||||
query: { comments_page: commentsPageNum - 1 },
|
|
||||||
}}
|
|
||||||
scroll={false}
|
|
||||||
>
|
|
||||||
Next Comments
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
className={`btn-outline btn ${
|
|
||||||
commentsPageNum === commentsPageCount
|
|
||||||
? 'btn-disabled pointer-events-none'
|
|
||||||
: 'pointer-events-auto'
|
|
||||||
}`}
|
|
||||||
href={{
|
|
||||||
pathname: `/beers/${beerPost.id}`,
|
|
||||||
query: { comments_page: commentsPageNum + 1 },
|
|
||||||
}}
|
|
||||||
scroll={false}
|
|
||||||
>
|
|
||||||
Previous Comments
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="sm:w-[40%]">
|
|
||||||
<BeerRecommendations beerRecommendations={beerRecommendations} />
|
<BeerRecommendations beerRecommendations={beerRecommendations} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import CreateBeerPostForm from '@/components/CreateBeerPostForm';
|
import CreateBeerPostForm from '@/components/CreateBeerPostForm';
|
||||||
import FormPageLayout from '@/components/ui/forms/BeerPostFormPageLayout';
|
import FormPageLayout from '@/components/ui/forms/FormPageLayout';
|
||||||
import Layout from '@/components/ui/Layout';
|
import Layout from '@/components/ui/Layout';
|
||||||
import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired';
|
import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired';
|
||||||
import DBClient from '@/prisma/DBClient';
|
import DBClient from '@/prisma/DBClient';
|
||||||
@@ -17,7 +17,12 @@ interface CreateBeerPageProps {
|
|||||||
const Create: NextPage<CreateBeerPageProps> = ({ breweries, types }) => {
|
const Create: NextPage<CreateBeerPageProps> = ({ breweries, types }) => {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<FormPageLayout headingText="Create a new beer" headingIcon={BiBeer}>
|
<FormPageLayout
|
||||||
|
headingText="Create a new beer"
|
||||||
|
headingIcon={BiBeer}
|
||||||
|
backLink="/beers"
|
||||||
|
backLinkText="Back to beers"
|
||||||
|
>
|
||||||
<CreateBeerPostForm breweries={breweries} types={types} />
|
<CreateBeerPostForm breweries={breweries} types={types} />
|
||||||
</FormPageLayout>
|
</FormPageLayout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import RegisterUserForm from '@/components/RegisterUserForm';
|
import RegisterUserForm from '@/components/RegisterUserForm';
|
||||||
import FormPageLayout from '@/components/ui/forms/BeerPostFormPageLayout';
|
import FormPageLayout from '@/components/ui/forms/FormPageLayout';
|
||||||
import Layout from '@/components/ui/Layout';
|
import Layout from '@/components/ui/Layout';
|
||||||
import redirectIfLoggedIn from '@/getServerSideProps/redirectIfLoggedIn';
|
import redirectIfLoggedIn from '@/getServerSideProps/redirectIfLoggedIn';
|
||||||
import { NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
@@ -13,7 +13,12 @@ const RegisterUserPage: NextPage = () => {
|
|||||||
<title>Register User</title>
|
<title>Register User</title>
|
||||||
<meta name="description" content="Register a new user" />
|
<meta name="description" content="Register a new user" />
|
||||||
</Head>
|
</Head>
|
||||||
<FormPageLayout headingText="Register User" headingIcon={BiUser}>
|
<FormPageLayout
|
||||||
|
headingText="Register User"
|
||||||
|
headingIcon={BiUser}
|
||||||
|
backLink="/"
|
||||||
|
backLinkText="Back to home"
|
||||||
|
>
|
||||||
<RegisterUserForm />
|
<RegisterUserForm />
|
||||||
</FormPageLayout>
|
</FormPageLayout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const createNewBeerPosts = async ({
|
|||||||
abv: Math.floor(Math.random() * (12 - 4) + 4),
|
abv: Math.floor(Math.random() * (12 - 4) + 4),
|
||||||
ibu: Math.floor(Math.random() * (60 - 10) + 10),
|
ibu: Math.floor(Math.random() * (60 - 10) + 10),
|
||||||
name: faker.commerce.productName(),
|
name: faker.commerce.productName(),
|
||||||
description: faker.lorem.lines(42).replace(/(\r\n|\n|\r)/gm, ' '),
|
description: faker.lorem.lines(12).replace(/(\r\n|\n|\r)/gm, ' '),
|
||||||
brewery: { connect: { id: breweryPost.id } },
|
brewery: { connect: { id: breweryPost.id } },
|
||||||
postedBy: { connect: { id: user.id } },
|
postedBy: { connect: { id: user.id } },
|
||||||
type: { connect: { id: beerType.id } },
|
type: { connect: { id: beerType.id } },
|
||||||
|
|||||||
Reference in New Issue
Block a user