mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +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,
|
||||
});
|
||||
reset();
|
||||
router.replace(router.asPath, undefined, { scroll: false });
|
||||
router.replace(`/beers/${beerPost.id}?comments_page=1`, undefined, { scroll: false });
|
||||
};
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
@@ -56,13 +56,14 @@ const BeerInfoHeader: FC<{ beerPost: BeerPostQueryResult; initialLikeCount: numb
|
||||
</div>
|
||||
<div>
|
||||
{isPostOwner && (
|
||||
<Link
|
||||
className="btn-outline btn-sm btn gap-2 rounded-2xl"
|
||||
href={`/beers/${beerPost.id}/edit`}
|
||||
>
|
||||
<FaRegEdit className="text-xl" />
|
||||
Edit
|
||||
</Link>
|
||||
<div className="tooltip tooltip-left" data-tip={`Edit '${beerPost.name}'`}>
|
||||
<Link
|
||||
href={`/beers/${beerPost.id}/edit`}
|
||||
className="btn btn-outline btn-sm"
|
||||
>
|
||||
<FaRegEdit className="text-xl" />
|
||||
</Link>
|
||||
</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 { format, formatDistanceStrict } from 'date-fns';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { Rating } from 'react-daisyui';
|
||||
|
||||
import { FaEllipsisH } from 'react-icons/fa';
|
||||
|
||||
const CommentCardDropdown: React.FC<{
|
||||
comment: BeerCommentQueryResultT;
|
||||
beerPostId: string;
|
||||
}> = ({ comment, beerPostId }) => {
|
||||
const router = useRouter();
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
const isCommentOwner = user?.id === comment.postedBy.id;
|
||||
|
||||
const handleDelete = async () => {
|
||||
const response = await fetch(`/api/beer-comments/${comment.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete comment');
|
||||
}
|
||||
|
||||
router.replace(`/beers/${beerPostId}?comments_page=1`, undefined, { scroll: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<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<{
|
||||
comment: BeerCommentQueryResultT;
|
||||
}> = ({ comment }) => {
|
||||
beerPostId: string;
|
||||
}> = ({ comment, beerPostId }) => {
|
||||
const [timeDistance, setTimeDistance] = useState('');
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeDistance(formatDistanceStrict(new Date(comment.createdAt), new Date()));
|
||||
}, [comment.createdAt]);
|
||||
|
||||
return (
|
||||
<div className="card-body sm:h-64">
|
||||
<div className="card-body">
|
||||
<div className="flex flex-col justify-between sm:flex-row">
|
||||
<div>
|
||||
<h3 className="font-semibold sm:text-2xl">
|
||||
@@ -33,21 +82,24 @@ const CommentCard: React.FC<{
|
||||
ago
|
||||
</h4>
|
||||
</div>
|
||||
<div>
|
||||
<Rating value={comment.rating}>
|
||||
{Array.from({ length: 5 }).map((val, index) => (
|
||||
<Rating.Item
|
||||
name="rating-1"
|
||||
className="mask mask-star cursor-default"
|
||||
disabled
|
||||
aria-disabled
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</Rating>
|
||||
</div>
|
||||
|
||||
{user && <CommentCardDropdown comment={comment} beerPostId={beerPostId} />}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Rating value={comment.rating}>
|
||||
{Array.from({ length: 5 }).map((val, index) => (
|
||||
<Rating.Item
|
||||
name="rating-1"
|
||||
className="mask mask-star cursor-default"
|
||||
disabled
|
||||
aria-disabled
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</Rating>
|
||||
<p>{comment.content}</p>
|
||||
</div>
|
||||
<p>{comment.content}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sendEditBeerPostRequest from '@/requests/sendEditBeerPostRequest';
|
||||
import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
import { FC, useState } from 'react';
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
@@ -52,7 +52,7 @@ const EditBeerPostForm: FC<EditBeerPostFormProps> = ({ previousValues }) => {
|
||||
|
||||
return (
|
||||
<form className="form-control" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="my-5">
|
||||
<div className="mb-5">
|
||||
{error && <ErrorAlert error={error} setError={setError} />}
|
||||
</div>
|
||||
<FormInfo>
|
||||
@@ -116,23 +116,10 @@ const EditBeerPostForm: FC<EditBeerPostFormProps> = ({ previousValues }) => {
|
||||
/>
|
||||
</FormSegment>
|
||||
|
||||
<div className="mt-6 flex w-full space-x-6">
|
||||
<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}>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<Button type="submit" isSubmitting={isSubmitting}>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { ReactNode, FC } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { IconType } from 'react-icons';
|
||||
import { BiArrowBack } from 'react-icons/bi';
|
||||
|
||||
interface FormPageLayoutProps {
|
||||
children: ReactNode;
|
||||
headingText: string;
|
||||
headingIcon: IconType;
|
||||
backLink: string;
|
||||
backLinkText: string;
|
||||
}
|
||||
|
||||
const FormPageLayout: FC<FormPageLayoutProps> = ({
|
||||
children: FormComponent,
|
||||
headingIcon,
|
||||
headingText,
|
||||
backLink,
|
||||
backLinkText,
|
||||
}) => {
|
||||
return (
|
||||
<div className="align-center my-20 flex h-fit flex-col items-center justify-center">
|
||||
<div className="w-8/12">
|
||||
<div className="my-4 flex flex-col items-center space-y-1">
|
||||
{headingIcon({ className: 'text-4xl' })}
|
||||
<div className="tooltip tooltip-bottom absolute" data-tip={backLinkText}>
|
||||
<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>
|
||||
</div>
|
||||
<div>{FormComponent}</div>
|
||||
Reference in New Issue
Block a user