[resumes][feat] fetch comments from database (#320)
* [resumes][feat] Add resume-comments type * [resumes][feat] Add resume-comments type * [resumes][feat] Filter comments * [resumes][feat] Add comments render * [resumes][refactor] rename variables * [resumes][refactor] update invalidateQueries * [resumes][refactor] Use resumeId in [resumeId].tsx * [resumes][fix] fix invalidateQuery Co-authored-by: Terence Ho <>pull/326/head
parent
b37aae2154
commit
d9880dbff1
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import type { ResumesSection } from '@prisma/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returned by `resumeReviewsRouter` (query for 'resumes.reviews.list') and received as prop by `Comment` in `CommentsList`
|
||||||
|
* frontend-friendly representation of the query
|
||||||
|
*/
|
||||||
|
export type ResumeComment = {
|
||||||
|
createdAt: Date;
|
||||||
|
description: string;
|
||||||
|
hasVoted: boolean;
|
||||||
|
id: string;
|
||||||
|
numVotes: number;
|
||||||
|
resumeId: string;
|
||||||
|
section: ResumesSection;
|
||||||
|
updatedAt: Date;
|
||||||
|
user: {
|
||||||
|
image: string?;
|
||||||
|
name: string?;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in new issue