[resumes][feat] Add comments render

pull/320/head
Terence Ho 3 years ago
parent 84af917ef3
commit 3d72bc7b23

@ -40,13 +40,22 @@ export default function CommentsForm({
}, },
}); });
const reviewCreateMutation = trpc.useMutation('resumes.reviews.user.create'); const reviewCreateMutation = trpc.useMutation('resumes.reviews.user.create');
const trpcContext = trpc.useContext();
// TODO: Give a feedback to the user if the action succeeds/fails // TODO: Give a feedback to the user if the action succeeds/fails
const onSubmit: SubmitHandler<IFormInput> = async (data) => { const onSubmit: SubmitHandler<IFormInput> = async (data) => {
await reviewCreateMutation.mutate({ await reviewCreateMutation.mutate(
{
resumeId, resumeId,
...data, ...data,
}); },
{
onSuccess: () => {
// New review added, invalidate query to trigger refetch
trpcContext.invalidateQueries(['resumes.reviews.list']);
},
},
);
// Redirect back to comments section // Redirect back to comments section
setShowCommentsForm(false); setShowCommentsForm(false);

@ -1,8 +1,10 @@
import { useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { Tabs } from '@tih/ui'; import { Tabs } from '@tih/ui';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
import Comment from './comment/Comment';
import CommentsListButton from './CommentsListButton'; import CommentsListButton from './CommentsListButton';
import { COMMENTS_SECTIONS } from './constants'; import { COMMENTS_SECTIONS } from './constants';
@ -19,17 +21,11 @@ export default function CommentsList({
}: CommentsListProps) { }: CommentsListProps) {
const [tab, setTab] = useState(COMMENTS_SECTIONS[0].value); const [tab, setTab] = useState(COMMENTS_SECTIONS[0].value);
const [comments, setComments] = useState<Array<ResumeComment>>([]); const [comments, setComments] = useState<Array<ResumeComment>>([]);
const { data: session } = useSession();
const onFetchComments = (data: Array<ResumeComment>) => { // Fetch the most updated comments to render
const filteredComments = data.filter((comment) => { trpc.useQuery(['resumes.reviews.list', { resumeId, tab }], {
return comment.section === tab; onSuccess: setComments,
});
setComments(filteredComments);
};
trpc.useQuery(['resumes.reviews.list', { resumeId }], {
onSuccess: onFetchComments,
}); });
return ( return (
@ -41,7 +37,18 @@ export default function CommentsList({
value={tab} value={tab}
onChange={(value) => setTab(value)} onChange={(value) => setTab(value)}
/> />
{/* TODO: Add comments lists */}
<div className="m-2 flow-root h-[calc(100vh-20rem)] w-full flex-col space-y-3 overflow-y-scroll">
{comments.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
userId={session?.user?.id}
/>
);
})}
</div>
</div> </div>
); );
} }

@ -0,0 +1,18 @@
import CommentBody from './CommentBody';
import CommentCard from './CommentCard';
import type { ResumeComment } from '~/types/resume-comments';
type CommentProps = {
comment: ResumeComment;
userId?: string;
};
export default function Comment({ comment, userId }: CommentProps) {
const isCommentOwner = userId === comment.user.userId;
return (
<CommentCard isCommentOwner={isCommentOwner}>
<CommentBody comment={comment} isCommentOwner={isCommentOwner} />
</CommentCard>
);
}

@ -0,0 +1,64 @@
import {
ArrowDownCircleIcon,
ArrowUpCircleIcon,
} from '@heroicons/react/20/solid';
import { FaceSmileIcon } from '@heroicons/react/24/outline';
import type { ResumeComment } from '~/types/resume-comments';
type CommentBodyProps = {
comment: ResumeComment;
isCommentOwner?: boolean;
};
export default function CommentBody({
comment,
isCommentOwner,
}: CommentBodyProps) {
return (
<div className="flex w-full flex-row space-x-2 p-1 align-top">
{comment.user.image ? (
<img
alt={comment.user.name ?? 'Reviewer'}
className="mt-1 h-8 w-8 rounded-full"
src={comment.user.image!}
/>
) : (
<FaceSmileIcon className="h-8 w-8 rounded-full" />
)}
<div className="flex w-full flex-col space-y-1">
{/* Name and creation time */}
<div className="flex flex-row justify-between">
<div className="font-medium">
{comment.user.name ?? 'Reviewer ABC'}
</div>
<div className="text-xs text-gray-600">
{comment.createdAt.toLocaleString('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
})}
</div>
</div>
{/* Description */}
<div className="text-sm">{comment.description}</div>
{/* Upvote and edit */}
<div className="flex flex-row space-x-1 pt-1 align-middle">
{/* TODO: Implement upvote */}
<ArrowUpCircleIcon className="h-4 w-4 fill-gray-400" />
<div className="text-xs">{comment.numVotes}</div>
<ArrowDownCircleIcon className="h-4 w-4 fill-gray-400" />
{/* TODO: Implement edit */}
{isCommentOwner ? (
<div className="text-primary-800 hover:text-primary-400 px-1 text-xs">
Edit
</div>
) : null}
</div>
</div>
</div>
);
}

@ -0,0 +1,22 @@
import type { ReactNode } from 'react';
type CommentCardProps = {
children: ReactNode;
isCommentOwner?: boolean;
};
export default function CommentCard({
isCommentOwner,
children,
}: CommentCardProps) {
// Used two different <div> to allow customisation of owner comments
return isCommentOwner ? (
<div className="border-primary-300 float-right w-3/4 rounded-md border-2 bg-white p-2 drop-shadow-md">
{children}
</div>
) : (
<div className="border-primary-300 float-left w-3/4 rounded-md border-2 bg-white p-2 drop-shadow-md">
{children}
</div>
);
}

@ -1,4 +1,5 @@
import { z } from 'zod'; import { z } from 'zod';
import { ResumesSection } from '@prisma/client';
import { createRouter } from './context'; import { createRouter } from './context';
@ -7,10 +8,11 @@ import type { ResumeComment } from '~/types/resume-comments';
export const resumeReviewsRouter = createRouter().query('list', { export const resumeReviewsRouter = createRouter().query('list', {
input: z.object({ input: z.object({
resumeId: z.string(), resumeId: z.string(),
tab: z.nativeEnum(ResumesSection),
}), }),
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
const userId = ctx.session?.user?.id; const userId = ctx.session?.user?.id;
const { resumeId } = input; const { resumeId, tab } = input;
// For this resume, we retrieve every comment's information, along with: // For this resume, we retrieve every comment's information, along with:
// The user's name and image to render // The user's name and image to render
@ -40,6 +42,7 @@ export const resumeReviewsRouter = createRouter().query('list', {
}, },
where: { where: {
resumeId, resumeId,
section: tab,
}, },
}); });
@ -54,13 +57,12 @@ export const resumeReviewsRouter = createRouter().query('list', {
id: data.id, id: data.id,
numVotes, numVotes,
resumeId: data.resumeId, resumeId: data.resumeId,
resumesProfileId: data.resumesProfileId,
section: data.section, section: data.section,
updatedAt: data.updatedAt, updatedAt: data.updatedAt,
user: { user: {
image: data.resumesProfile.user.image, image: data.user.image,
name: data.resumesProfile.user.name, name: data.user.name,
userId: data.resumesProfile.userId, userId: data.userId,
}, },
}; };

@ -11,7 +11,6 @@ export type ResumeComment = {
id: string; id: string;
numVotes: number; numVotes: number;
resumeId: string; resumeId: string;
resumesProfileId: string;
section: ResumesSection; section: ResumesSection;
updatedAt: Date; updatedAt: Date;
user: { user: {

Loading…
Cancel
Save