From d10377e0f909bd0a1a263e8e1f05fc6fc1071cc2 Mon Sep 17 00:00:00 2001 From: Terence <45381509+Vielheim@users.noreply.github.com> Date: Fri, 21 Oct 2022 15:55:59 +0800 Subject: [PATCH] [resumes][feat] replying comments (#401) * [resumes][feat] add resume comment parent * [resumes][refactor] Abstract comment edit form and votes to their components * [resumes][feat] Add reply form * [resumes][feat] Render replies * [resumes][feat] add collapsible comments * [resumes][chore] remove comment Co-authored-by: Terence Ho <> --- .../migration.sql | 5 + apps/portal/prisma/schema.prisma | 3 + .../comments/ResumeCommentListItem.tsx | 305 +++++------------- .../comment/ResumeCommentEditForm.tsx | 106 ++++++ .../comment/ResumeCommentReplyForm.tsx | 107 ++++++ .../comment/ResumeCommentVoteButtons.tsx | 129 ++++++++ .../router/resumes/resumes-comments-router.ts | 35 +- .../resumes/resumes-comments-user-router.ts | 22 ++ apps/portal/src/types/resume-comments.d.ts | 2 + 9 files changed, 497 insertions(+), 217 deletions(-) create mode 100644 apps/portal/prisma/migrations/20221020101123_add_resume_comment_parent/migration.sql create mode 100644 apps/portal/src/components/resumes/comments/comment/ResumeCommentEditForm.tsx create mode 100644 apps/portal/src/components/resumes/comments/comment/ResumeCommentReplyForm.tsx create mode 100644 apps/portal/src/components/resumes/comments/comment/ResumeCommentVoteButtons.tsx diff --git a/apps/portal/prisma/migrations/20221020101123_add_resume_comment_parent/migration.sql b/apps/portal/prisma/migrations/20221020101123_add_resume_comment_parent/migration.sql new file mode 100644 index 00000000..018ad4cd --- /dev/null +++ b/apps/portal/prisma/migrations/20221020101123_add_resume_comment_parent/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "ResumesComment" ADD COLUMN "parentId" TEXT; + +-- AddForeignKey +ALTER TABLE "ResumesComment" ADD CONSTRAINT "ResumesComment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "ResumesComment"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index 48e068ca..7f968286 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -140,6 +140,7 @@ model ResumesComment { id String @id @default(cuid()) userId String resumeId String + parentId String? description String @db.Text section ResumesSection createdAt DateTime @default(now()) @@ -147,6 +148,8 @@ model ResumesComment { resume ResumesResume @relation(fields: [resumeId], references: [id], onDelete: Cascade) votes ResumesCommentVote[] user User @relation(fields: [userId], references: [id], onDelete: Cascade) + parent ResumesComment? @relation("parentComment", fields: [parentId], references: [id]) + children ResumesComment[] @relation("parentComment") } enum ResumesSection { diff --git a/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx b/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx index 833c53c2..6814fda9 100644 --- a/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx +++ b/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx @@ -1,18 +1,11 @@ import clsx from 'clsx'; -import type { Dispatch, SetStateAction } from 'react'; import { useState } from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { useForm } from 'react-hook-form'; -import { - ArrowDownCircleIcon, - ArrowUpCircleIcon, -} from '@heroicons/react/20/solid'; +import { ChevronUpIcon } from '@heroicons/react/20/solid'; import { FaceSmileIcon } from '@heroicons/react/24/outline'; -import { Vote } from '@prisma/client'; -import { Button, TextArea } from '@tih/ui'; - -import { trpc } from '~/utils/trpc'; +import ResumeCommentEditForm from './comment/ResumeCommentEditForm'; +import ResumeCommentReplyForm from './comment/ResumeCommentReplyForm'; +import ResumeCommentVoteButtons from './comment/ResumeCommentVoteButtons'; import ResumeUserBadges from '../badges/ResumeUserBadges'; import ResumeExpandableText from '../shared/ResumeExpandableText'; @@ -23,141 +16,55 @@ type ResumeCommentListItemProps = { userId: string | undefined; }; -type ICommentInput = { - description: string; -}; - export default function ResumeCommentListItem({ comment, userId, }: ResumeCommentListItemProps) { const isCommentOwner = userId === comment.user.userId; const [isEditingComment, setIsEditingComment] = useState(false); - - const [upvoteAnimation, setUpvoteAnimation] = useState(false); - const [downvoteAnimation, setDownvoteAnimation] = useState(false); - - const { - register, - handleSubmit, - setValue, - formState: { errors, isDirty }, - reset, - } = useForm({ - defaultValues: { - description: comment.description, - }, - }); - - const trpcContext = trpc.useContext(); - const commentUpdateMutation = trpc.useMutation( - 'resumes.comments.user.update', - { - onSuccess: () => { - // Comment updated, invalidate query to trigger refetch - trpcContext.invalidateQueries(['resumes.comments.list']); - }, - }, - ); - - // COMMENT VOTES - const commentVotesQuery = trpc.useQuery([ - 'resumes.comments.votes.list', - { commentId: comment.id }, - ]); - const commentVotesUpsertMutation = trpc.useMutation( - 'resumes.comments.votes.user.upsert', - { - onSuccess: () => { - // Comment updated, invalidate query to trigger refetch - trpcContext.invalidateQueries(['resumes.comments.votes.list']); - }, - }, - ); - const commentVotesDeleteMutation = trpc.useMutation( - 'resumes.comments.votes.user.delete', - { - onSuccess: () => { - // Comment updated, invalidate query to trigger refetch - trpcContext.invalidateQueries(['resumes.comments.votes.list']); - }, - }, - ); - - // FORM ACTIONS - const onCancel = () => { - reset({ description: comment.description }); - setIsEditingComment(false); - }; - - const onSubmit: SubmitHandler = async (data) => { - const { id } = comment; - return commentUpdateMutation.mutate( - { - id, - ...data, - }, - { - onSuccess: () => { - setIsEditingComment(false); - }, - }, - ); - }; - - const setFormValue = (value: string) => { - setValue('description', value.trim(), { shouldDirty: true }); - }; - - const onVote = async ( - value: Vote, - setAnimation: Dispatch>, - ) => { - setAnimation(true); - - if (commentVotesQuery.data?.userVote?.value === value) { - return commentVotesDeleteMutation.mutate( - { - commentId: comment.id, - }, - { - onSettled: async () => setAnimation(false), - }, - ); - } - return commentVotesUpsertMutation.mutate( - { - commentId: comment.id, - value, - }, - { - onSettled: async () => setAnimation(false), - }, - ); - }; + const [isReplyingComment, setIsReplyingComment] = useState(false); + const [showReplies, setShowReplies] = useState(true); return ( -
+
+ {/* Image Icon */} {comment.user.image ? ( {comment.user.name ) : ( - + )}
{/* Name and creation time */}
-

+

{comment.user.name ?? 'Reviewer ABC'}

-

+

{isCommentOwner ? '(Me)' : ''}

@@ -174,112 +81,78 @@ export default function ResumeCommentListItem({ {/* Description */} {isEditingComment ? ( -
-
-