diff --git a/apps/portal/prisma/migrations/20221108061935_add_comment_deletion/migration.sql b/apps/portal/prisma/migrations/20221108061935_add_comment_deletion/migration.sql
new file mode 100644
index 00000000..bb64e690
--- /dev/null
+++ b/apps/portal/prisma/migrations/20221108061935_add_comment_deletion/migration.sql
@@ -0,0 +1,5 @@
+-- DropForeignKey
+ALTER TABLE "ResumesComment" DROP CONSTRAINT "ResumesComment_parentId_fkey";
+
+-- AddForeignKey
+ALTER TABLE "ResumesComment" ADD CONSTRAINT "ResumesComment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "ResumesComment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma
index 04493fa9..4a530ac4 100644
--- a/apps/portal/prisma/schema.prisma
+++ b/apps/portal/prisma/schema.prisma
@@ -187,7 +187,7 @@ 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])
+ parent ResumesComment? @relation("parentComment", fields: [parentId], references: [id], onDelete: Cascade)
children ResumesComment[] @relation("parentComment")
}
diff --git a/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx b/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx
index 1a19230b..b75a6160 100644
--- a/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx
+++ b/apps/portal/src/components/resumes/comments/ResumeCommentListItem.tsx
@@ -2,6 +2,7 @@ import clsx from 'clsx';
import { formatDistanceToNow } from 'date-fns';
import { useState } from 'react';
+import ResumeCommentDeleteForm from './comment/ResumeCommentDeleteForm';
import ResumeCommentEditForm from './comment/ResumeCommentEditForm';
import ResumeCommentReplyForm from './comment/ResumeCommentReplyForm';
import ResumeCommentVoteButtons from './comment/ResumeCommentVoteButtons';
@@ -22,6 +23,7 @@ export default function ResumeCommentListItem({
const isCommentOwner = userId === comment.user.userId;
const [isEditingComment, setIsEditingComment] = useState(false);
const [isReplyingComment, setIsReplyingComment] = useState(false);
+ const [isDeletingComment, setIsDeletingComment] = useState(false);
const [showReplies, setShowReplies] = useState(true);
return (
@@ -73,7 +75,7 @@ export default function ResumeCommentListItem({
)}
- {/* Upvote and edit */}
+ {/* Upvote and actions (edit, reply, delete) */}
{/* Action buttons; only present for authenticated user when not editing/replying */}
@@ -84,6 +86,7 @@ export default function ResumeCommentListItem({
onClick={() => {
setIsReplyingComment(!isReplyingComment);
setIsEditingComment(false);
+ setIsDeletingComment(false);
}}>
Reply
@@ -111,10 +114,33 @@ export default function ResumeCommentListItem({
onClick={() => {
setIsEditingComment(!isEditingComment);
setIsReplyingComment(false);
+ setIsDeletingComment(false);
}}>
Edit
)}
+
+ {isCommentOwner && (
+
+ )}
+
+ {/* Delete comment form */}
+ {isDeletingComment && (
+
+ )}
{/* Reply Form */}
diff --git a/apps/portal/src/components/resumes/comments/comment/ResumeCommentDeleteForm.tsx b/apps/portal/src/components/resumes/comments/comment/ResumeCommentDeleteForm.tsx
new file mode 100644
index 00000000..25f6444e
--- /dev/null
+++ b/apps/portal/src/components/resumes/comments/comment/ResumeCommentDeleteForm.tsx
@@ -0,0 +1,83 @@
+import { Button, Dialog } from '@tih/ui';
+
+import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
+
+import { trpc } from '~/utils/trpc';
+type ResumeCommentDeleteFormProps = Readonly<{
+ id: string;
+ isDeletingComment: boolean;
+ setIsDeletingComment: (value: boolean) => void;
+}>;
+
+export default function ResumeCommentDeleteForm({
+ id,
+ isDeletingComment,
+ setIsDeletingComment,
+}: ResumeCommentDeleteFormProps) {
+ const { event: gaEvent } = useGoogleAnalytics();
+
+ const trpcContext = trpc.useContext();
+ const commentDeleteMutation = trpc.useMutation(
+ 'resumes.comments.user.delete',
+ {
+ onSuccess: () => {
+ // Comments updated, invalidate query to trigger refetch
+ trpcContext.invalidateQueries(['resumes.comments.list']);
+ },
+ },
+ );
+
+ const onDelete = async () => {
+ return commentDeleteMutation.mutate(
+ {
+ id,
+ },
+ {
+ onSuccess: () => {
+ setIsDeletingComment(false);
+
+ gaEvent({
+ action: 'resumes.comment_delete',
+ category: 'engagement',
+ label: 'Delete comment',
+ });
+ },
+ },
+ );
+ };
+
+ const onCancel = () => {
+ setIsDeletingComment(false);
+ };
+
+ return (
+
+ }
+ secondaryButton={
+
+ }
+ title="Are you sure?"
+ onClose={() => setIsDeletingComment(false)}>
+
+ Note that deleting this comment will delete all its replies as well.
+ This action is also irreversible! Please check before confirming!
+
+
+ );
+}
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 e33a399b..10036b74 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
@@ -99,4 +99,16 @@ export const resumesCommentsUserRouter = createProtectedRouter()
},
});
},
+ })
+ .mutation('delete', {
+ input: z.object({ id: z.string() }),
+ async resolve({ ctx, input }) {
+ const { id } = input;
+
+ return await ctx.prisma.resumesComment.delete({
+ where: {
+ id,
+ },
+ });
+ },
});