[resumes][feat] delete comment (#537)

* [resumes][feat] add delete form

* [resumes][feat] add delete comment

Co-authored-by: Terence Ho <>
pull/540/head
Terence 2 years ago committed by GitHub
parent ad6d2f27e2
commit 710e67063b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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;

@ -187,7 +187,7 @@ model ResumesComment {
resume ResumesResume @relation(fields: [resumeId], references: [id], onDelete: Cascade) resume ResumesResume @relation(fields: [resumeId], references: [id], onDelete: Cascade)
votes ResumesCommentVote[] votes ResumesCommentVote[]
user User @relation(fields: [userId], references: [id], onDelete: Cascade) 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") children ResumesComment[] @relation("parentComment")
} }

@ -2,6 +2,7 @@ import clsx from 'clsx';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { useState } from 'react'; import { useState } from 'react';
import ResumeCommentDeleteForm from './comment/ResumeCommentDeleteForm';
import ResumeCommentEditForm from './comment/ResumeCommentEditForm'; import ResumeCommentEditForm from './comment/ResumeCommentEditForm';
import ResumeCommentReplyForm from './comment/ResumeCommentReplyForm'; import ResumeCommentReplyForm from './comment/ResumeCommentReplyForm';
import ResumeCommentVoteButtons from './comment/ResumeCommentVoteButtons'; import ResumeCommentVoteButtons from './comment/ResumeCommentVoteButtons';
@ -22,6 +23,7 @@ export default function ResumeCommentListItem({
const isCommentOwner = userId === comment.user.userId; const isCommentOwner = userId === comment.user.userId;
const [isEditingComment, setIsEditingComment] = useState(false); const [isEditingComment, setIsEditingComment] = useState(false);
const [isReplyingComment, setIsReplyingComment] = useState(false); const [isReplyingComment, setIsReplyingComment] = useState(false);
const [isDeletingComment, setIsDeletingComment] = useState(false);
const [showReplies, setShowReplies] = useState(true); const [showReplies, setShowReplies] = useState(true);
return ( return (
@ -73,7 +75,7 @@ export default function ResumeCommentListItem({
</div> </div>
)} )}
{/* Upvote and edit */} {/* Upvote and actions (edit, reply, delete) */}
<div className="mt-1 flex h-6 items-center"> <div className="mt-1 flex h-6 items-center">
<ResumeCommentVoteButtons commentId={comment.id} userId={userId} /> <ResumeCommentVoteButtons commentId={comment.id} userId={userId} />
{/* Action buttons; only present for authenticated user when not editing/replying */} {/* Action buttons; only present for authenticated user when not editing/replying */}
@ -84,6 +86,7 @@ export default function ResumeCommentListItem({
onClick={() => { onClick={() => {
setIsReplyingComment(!isReplyingComment); setIsReplyingComment(!isReplyingComment);
setIsEditingComment(false); setIsEditingComment(false);
setIsDeletingComment(false);
}}> }}>
Reply Reply
</button> </button>
@ -111,10 +114,33 @@ export default function ResumeCommentListItem({
onClick={() => { onClick={() => {
setIsEditingComment(!isEditingComment); setIsEditingComment(!isEditingComment);
setIsReplyingComment(false); setIsReplyingComment(false);
setIsDeletingComment(false);
}}> }}>
Edit Edit
</button> </button>
)} )}
{isCommentOwner && (
<button
className="-my-1 rounded-md px-2 py-1 text-xs font-medium text-slate-500 hover:bg-slate-100 hover:text-red-600"
type="button"
onClick={() => {
setIsDeletingComment(!isDeletingComment);
setIsEditingComment(false);
setIsReplyingComment(false);
}}>
Delete
</button>
)}
{/* Delete comment form */}
{isDeletingComment && (
<ResumeCommentDeleteForm
id={comment.id}
isDeletingComment={isDeletingComment}
setIsDeletingComment={setIsDeletingComment}
/>
)}
</div> </div>
{/* Reply Form */} {/* Reply Form */}

@ -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 (
<Dialog
isShown={isDeletingComment}
primaryButton={
<Button
disabled={commentDeleteMutation.isLoading}
display="block"
isLoading={commentDeleteMutation.isLoading}
label="Delete"
variant="danger"
onClick={onDelete}
/>
}
secondaryButton={
<Button
disabled={commentDeleteMutation.isLoading}
display="block"
label="Cancel"
variant="tertiary"
onClick={onCancel}
/>
}
title="Are you sure?"
onClose={() => setIsDeletingComment(false)}>
<div>
Note that deleting this comment will delete all its replies as well.
This action is also irreversible! Please check before confirming!
</div>
</Dialog>
);
}

@ -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,
},
});
},
}); });

Loading…
Cancel
Save