From a1445e9de8e8ac90926b300c5325295c9f506633 Mon Sep 17 00:00:00 2001
From: Terence Ho <>
Date: Fri, 21 Oct 2022 10:00:50 +0800
Subject: [PATCH] [resumes][feat] Add reply form
---
.../comments/ResumeCommentListItem.tsx | 35 ++++--
.../comment/ResumeCommentReplyForm.tsx | 107 ++++++++++++++++++
.../resumes/resumes-comments-user-router.ts | 22 ++++
3 files changed, 157 insertions(+), 7 deletions(-)
create mode 100644 apps/portal/src/components/resumes/comments/comment/ResumeCommentReplyForm.tsx
diff --git a/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx b/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx
index 9a871070..1be17cf7 100644
--- a/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx
+++ b/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { FaceSmileIcon } from '@heroicons/react/24/outline';
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';
@@ -19,6 +20,7 @@ export default function ResumeCommentListItem({
}: ResumeCommentListItemProps) {
const isCommentOwner = userId === comment.user.userId;
const [isEditingComment, setIsEditingComment] = useState(false);
+ const [isReplyingComment, setIsReplyingComment] = useState(false);
return (
@@ -70,15 +72,34 @@ export default function ResumeCommentListItem({
- {isCommentOwner && !isEditingComment && (
-
+ {isCommentOwner && !isEditingComment && !isReplyingComment && (
+ <>
+
+
+
+ >
)}
+
+ {/* Replies */}
+ {isReplyingComment && (
+
+ )}
diff --git a/apps/portal/src/components/resumes/comments/comment/ResumeCommentReplyForm.tsx b/apps/portal/src/components/resumes/comments/comment/ResumeCommentReplyForm.tsx
new file mode 100644
index 00000000..0f2f89e6
--- /dev/null
+++ b/apps/portal/src/components/resumes/comments/comment/ResumeCommentReplyForm.tsx
@@ -0,0 +1,107 @@
+import type { SubmitHandler } from 'react-hook-form';
+import { useForm } from 'react-hook-form';
+import type { ResumesSection } from '@prisma/client';
+import { Button, TextArea } from '@tih/ui';
+
+import { trpc } from '~/utils/trpc';
+
+type ResumeCommentEditFormProps = {
+ parentId: string;
+ resumeId: string;
+ section: ResumesSection;
+ setIsReplyingComment: (value: boolean) => void;
+};
+
+type IReplyInput = {
+ description: string;
+};
+
+export default function ResumeCommentReplyForm({
+ parentId,
+ setIsReplyingComment,
+ resumeId,
+ section,
+}: ResumeCommentEditFormProps) {
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ formState: { errors, isDirty },
+ reset,
+ } = useForm({
+ defaultValues: {
+ description: '',
+ },
+ });
+
+ const trpcContext = trpc.useContext();
+ const commentReplyMutation = trpc.useMutation('resumes.comments.user.reply', {
+ onSuccess: () => {
+ // Comment updated, invalidate query to trigger refetch
+ trpcContext.invalidateQueries(['resumes.comments.list']);
+ },
+ });
+
+ const onCancel = () => {
+ reset({ description: '' });
+ setIsReplyingComment(false);
+ };
+
+ const onSubmit: SubmitHandler = async (data) => {
+ return commentReplyMutation.mutate(
+ {
+ parentId,
+ resumeId,
+ section,
+ ...data,
+ },
+ {
+ onSuccess: () => {
+ setIsReplyingComment(false);
+ },
+ },
+ );
+ };
+
+ const setFormValue = (value: string) => {
+ setValue('description', value.trim(), { shouldDirty: true });
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/portal/src/server/router/resumes/resumes-comments-user-router.ts b/apps/portal/src/server/router/resumes/resumes-comments-user-router.ts
index 94c375f7..d061a4c2 100644
--- a/apps/portal/src/server/router/resumes/resumes-comments-user-router.ts
+++ b/apps/portal/src/server/router/resumes/resumes-comments-user-router.ts
@@ -67,4 +67,26 @@ export const resumesCommentsUserRouter = createProtectedRouter()
},
});
},
+ })
+ .mutation('reply', {
+ input: z.object({
+ description: z.string(),
+ parentId: z.string(),
+ resumeId: z.string(),
+ section: z.nativeEnum(ResumesSection),
+ }),
+ async resolve({ ctx, input }) {
+ const userId = ctx.session.user.id;
+ const { description, parentId, resumeId, section } = input;
+
+ return await ctx.prisma.resumesComment.create({
+ data: {
+ description,
+ parentId,
+ resumeId,
+ section,
+ userId,
+ },
+ });
+ },
});