[resumes][feat] add comment edit (#386)

* [resumes][feat] add comment edit

* [resumes][fix] use react-hook-form validation

Co-authored-by: Terence Ho <>
This commit is contained in:
Terence
2022-10-18 18:45:22 +08:00
committed by GitHub
parent c9f7b59d52
commit 25039b52de
2 changed files with 125 additions and 11 deletions

View File

@ -1,8 +1,14 @@
import { useState } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { import {
ArrowDownCircleIcon, ArrowDownCircleIcon,
ArrowUpCircleIcon, ArrowUpCircleIcon,
} from '@heroicons/react/20/solid'; } from '@heroicons/react/20/solid';
import { FaceSmileIcon } from '@heroicons/react/24/outline'; import { FaceSmileIcon } from '@heroicons/react/24/outline';
import { Button, TextArea } from '@tih/ui';
import { trpc } from '~/utils/trpc';
import ResumeExpandableText from '../shared/ResumeExpandableText'; import ResumeExpandableText from '../shared/ResumeExpandableText';
@ -13,11 +19,63 @@ type ResumeCommentListItemProps = {
userId?: string; userId?: string;
}; };
type ICommentInput = {
description: string;
};
export default function ResumeCommentListItem({ export default function ResumeCommentListItem({
comment, comment,
userId, userId,
}: ResumeCommentListItemProps) { }: ResumeCommentListItemProps) {
const isCommentOwner = userId === comment.user.userId; const isCommentOwner = userId === comment.user.userId;
const [isEditingComment, setIsEditingComment] = useState(false);
const {
register,
handleSubmit,
setValue,
formState: { errors, isDirty },
reset,
} = useForm<ICommentInput>({
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']);
},
},
);
const onCancel = () => {
reset({ description: comment.description });
setIsEditingComment(false);
};
const onSubmit: SubmitHandler<ICommentInput> = async (data) => {
const { id } = comment;
return await commentUpdateMutation.mutate(
{
id,
...data,
},
{
onSuccess: () => {
setIsEditingComment(false);
},
},
);
};
const setFormValue = (value: string) => {
setValue('description', value.trim(), { shouldDirty: true });
};
return ( return (
<div className="border-primary-300 w-3/4 rounded-md border-2 bg-white p-2 drop-shadow-md"> <div className="border-primary-300 w-3/4 rounded-md border-2 bg-white p-2 drop-shadow-md">
@ -54,7 +112,45 @@ export default function ResumeCommentListItem({
</div> </div>
{/* Description */} {/* Description */}
<ResumeExpandableText text={comment.description} /> {isEditingComment ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex-column mt-1 space-y-2">
<TextArea
{...(register('description', {
required: 'Comments cannot be empty!',
}),
{})}
defaultValue={comment.description}
disabled={commentUpdateMutation.isLoading}
errorMessage={errors.description?.message}
label="Edit comment"
placeholder="Leave your comment here"
onChange={setFormValue}
/>
<div className="flex-row space-x-2">
<Button
disabled={commentUpdateMutation.isLoading}
label="Cancel"
size="sm"
variant="tertiary"
onClick={onCancel}
/>
<Button
disabled={!isDirty || commentUpdateMutation.isLoading}
isLoading={commentUpdateMutation.isLoading}
label="Confirm"
size="sm"
type="submit"
variant="primary"
/>
</div>
</div>
</form>
) : (
<ResumeExpandableText text={comment.description} />
)}
{/* Upvote and edit */} {/* Upvote and edit */}
<div className="flex flex-row space-x-1 pt-1 align-middle"> <div className="flex flex-row space-x-1 pt-1 align-middle">
@ -63,12 +159,14 @@ export default function ResumeCommentListItem({
<div className="text-xs">{comment.numVotes}</div> <div className="text-xs">{comment.numVotes}</div>
<ArrowDownCircleIcon className="h-4 w-4 fill-gray-400" /> <ArrowDownCircleIcon className="h-4 w-4 fill-gray-400" />
{/* TODO: Implement edit */} {isCommentOwner && !isEditingComment && (
{isCommentOwner ? ( <a
<div className="text-primary-800 hover:text-primary-400 px-1 text-xs"> className="text-primary-800 hover:text-primary-400 px-1 text-xs"
href="#"
onClick={() => setIsEditingComment(true)}>
Edit Edit
</div> </a>
) : null} )}
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,9 +10,8 @@ type ResumeCommentInput = Readonly<{
userId: string; userId: string;
}>; }>;
export const resumesCommentsUserRouter = createProtectedRouter().mutation( export const resumesCommentsUserRouter = createProtectedRouter()
'create', .mutation('create', {
{
input: z.object({ input: z.object({
education: z.string(), education: z.string(),
experience: z.string(), experience: z.string(),
@ -50,5 +49,22 @@ export const resumesCommentsUserRouter = createProtectedRouter().mutation(
data: comments, data: comments,
}); });
}, },
}, })
); .mutation('update', {
input: z.object({
description: z.string(),
id: z.string(),
}),
async resolve({ ctx, input }) {
const { id, description } = input;
return await ctx.prisma.resumesComment.update({
data: {
description,
},
where: {
id,
},
});
},
});