diff --git a/apps/portal/src/components/resumes/comments/CommentsForm.tsx b/apps/portal/src/components/resumes/comments/CommentsForm.tsx index 98ff8d82..dd669d39 100644 --- a/apps/portal/src/components/resumes/comments/CommentsForm.tsx +++ b/apps/portal/src/components/resumes/comments/CommentsForm.tsx @@ -40,13 +40,22 @@ export default function CommentsForm({ }, }); const reviewCreateMutation = trpc.useMutation('resumes.reviews.user.create'); + const trpcContext = trpc.useContext(); // TODO: Give a feedback to the user if the action succeeds/fails const onSubmit: SubmitHandler = async (data) => { - await reviewCreateMutation.mutate({ - resumeId, - ...data, - }); + await reviewCreateMutation.mutate( + { + resumeId, + ...data, + }, + { + onSuccess: () => { + // New review added, invalidate query to trigger refetch + trpcContext.invalidateQueries(['resumes.reviews.list']); + }, + }, + ); // Redirect back to comments section setShowCommentsForm(false); diff --git a/apps/portal/src/components/resumes/comments/CommentsList.tsx b/apps/portal/src/components/resumes/comments/CommentsList.tsx index 63aafc2c..81f41394 100644 --- a/apps/portal/src/components/resumes/comments/CommentsList.tsx +++ b/apps/portal/src/components/resumes/comments/CommentsList.tsx @@ -1,8 +1,10 @@ +import { useSession } from 'next-auth/react'; import { useState } from 'react'; import { Tabs } from '@tih/ui'; import { trpc } from '~/utils/trpc'; +import Comment from './comment/Comment'; import CommentsListButton from './CommentsListButton'; import { COMMENTS_SECTIONS } from './constants'; @@ -19,17 +21,11 @@ export default function CommentsList({ }: CommentsListProps) { const [tab, setTab] = useState(COMMENTS_SECTIONS[0].value); const [comments, setComments] = useState>([]); + const { data: session } = useSession(); - const onFetchComments = (data: Array) => { - const filteredComments = data.filter((comment) => { - return comment.section === tab; - }); - - setComments(filteredComments); - }; - - trpc.useQuery(['resumes.reviews.list', { resumeId }], { - onSuccess: onFetchComments, + // Fetch the most updated comments to render + trpc.useQuery(['resumes.reviews.list', { resumeId, tab }], { + onSuccess: setComments, }); return ( @@ -41,7 +37,18 @@ export default function CommentsList({ value={tab} onChange={(value) => setTab(value)} /> - {/* TODO: Add comments lists */} + +
+ {comments.map((comment) => { + return ( + + ); + })} +
); } diff --git a/apps/portal/src/components/resumes/comments/comment/Comment.tsx b/apps/portal/src/components/resumes/comments/comment/Comment.tsx new file mode 100644 index 00000000..cb08480b --- /dev/null +++ b/apps/portal/src/components/resumes/comments/comment/Comment.tsx @@ -0,0 +1,18 @@ +import CommentBody from './CommentBody'; +import CommentCard from './CommentCard'; + +import type { ResumeComment } from '~/types/resume-comments'; + +type CommentProps = { + comment: ResumeComment; + userId?: string; +}; + +export default function Comment({ comment, userId }: CommentProps) { + const isCommentOwner = userId === comment.user.userId; + return ( + + + + ); +} diff --git a/apps/portal/src/components/resumes/comments/comment/CommentBody.tsx b/apps/portal/src/components/resumes/comments/comment/CommentBody.tsx new file mode 100644 index 00000000..69da7418 --- /dev/null +++ b/apps/portal/src/components/resumes/comments/comment/CommentBody.tsx @@ -0,0 +1,64 @@ +import { + ArrowDownCircleIcon, + ArrowUpCircleIcon, +} from '@heroicons/react/20/solid'; +import { FaceSmileIcon } from '@heroicons/react/24/outline'; + +import type { ResumeComment } from '~/types/resume-comments'; + +type CommentBodyProps = { + comment: ResumeComment; + isCommentOwner?: boolean; +}; + +export default function CommentBody({ + comment, + isCommentOwner, +}: CommentBodyProps) { + return ( +
+ {comment.user.image ? ( + {comment.user.name + ) : ( + + )} + +
+ {/* Name and creation time */} +
+
+ {comment.user.name ?? 'Reviewer ABC'} +
+
+ {comment.createdAt.toLocaleString('en-US', { + dateStyle: 'medium', + timeStyle: 'short', + })} +
+
+ + {/* Description */} +
{comment.description}
+ + {/* Upvote and edit */} +
+ {/* TODO: Implement upvote */} + +
{comment.numVotes}
+ + + {/* TODO: Implement edit */} + {isCommentOwner ? ( +
+ Edit +
+ ) : null} +
+
+
+ ); +} diff --git a/apps/portal/src/components/resumes/comments/comment/CommentCard.tsx b/apps/portal/src/components/resumes/comments/comment/CommentCard.tsx new file mode 100644 index 00000000..bbe0f840 --- /dev/null +++ b/apps/portal/src/components/resumes/comments/comment/CommentCard.tsx @@ -0,0 +1,22 @@ +import type { ReactNode } from 'react'; + +type CommentCardProps = { + children: ReactNode; + isCommentOwner?: boolean; +}; + +export default function CommentCard({ + isCommentOwner, + children, +}: CommentCardProps) { + // Used two different
to allow customisation of owner comments + return isCommentOwner ? ( +
+ {children} +
+ ) : ( +
+ {children} +
+ ); +} diff --git a/apps/portal/src/server/router/resumes-reviews-router.ts b/apps/portal/src/server/router/resumes-reviews-router.ts index 24c7c830..01bb1fd0 100644 --- a/apps/portal/src/server/router/resumes-reviews-router.ts +++ b/apps/portal/src/server/router/resumes-reviews-router.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { ResumesSection } from '@prisma/client'; import { createRouter } from './context'; @@ -7,10 +8,11 @@ import type { ResumeComment } from '~/types/resume-comments'; export const resumeReviewsRouter = createRouter().query('list', { input: z.object({ resumeId: z.string(), + tab: z.nativeEnum(ResumesSection), }), async resolve({ ctx, input }) { const userId = ctx.session?.user?.id; - const { resumeId } = input; + const { resumeId, tab } = input; // For this resume, we retrieve every comment's information, along with: // The user's name and image to render @@ -40,6 +42,7 @@ export const resumeReviewsRouter = createRouter().query('list', { }, where: { resumeId, + section: tab, }, }); @@ -54,13 +57,12 @@ export const resumeReviewsRouter = createRouter().query('list', { id: data.id, numVotes, resumeId: data.resumeId, - resumesProfileId: data.resumesProfileId, section: data.section, updatedAt: data.updatedAt, user: { - image: data.resumesProfile.user.image, - name: data.resumesProfile.user.name, - userId: data.resumesProfile.userId, + image: data.user.image, + name: data.user.name, + userId: data.userId, }, }; diff --git a/apps/portal/src/types/resume-comments.d.ts b/apps/portal/src/types/resume-comments.d.ts index 7f8397a3..5a6dfff8 100644 --- a/apps/portal/src/types/resume-comments.d.ts +++ b/apps/portal/src/types/resume-comments.d.ts @@ -11,7 +11,6 @@ export type ResumeComment = { id: string; numVotes: number; resumeId: string; - resumesProfileId: string; section: ResumesSection; updatedAt: Date; user: {