[resumes][feat] Add API to submit & query for resume reviews (#313)
* [resumes][feat] Add route to submit resume reviews * [resumes][feat] Add router to query for comments * [resumes][refactor] Change limit of upvotes query * [resumes][chore] revert changes * [resumes][chore] remove comment * [resumes][chore] Use ResumesSection enum instead of hard-coded string * [resumes][refactor] Add check for user session in comments * [resumes][fix] fix linting issues Co-authored-by: Terence Ho <>pull/317/head
parent
641a565e5c
commit
0933cce7b5
@ -0,0 +1,48 @@
|
||||
import { signIn, useSession } from 'next-auth/react';
|
||||
import { Button } from '@tih/ui';
|
||||
|
||||
type CommentsListButtonProps = {
|
||||
setShowCommentsForm: (show: boolean) => void;
|
||||
};
|
||||
|
||||
export default function CommentsListButton({
|
||||
setShowCommentsForm,
|
||||
}: CommentsListButtonProps) {
|
||||
const { data: session, status } = useSession();
|
||||
const isSessionLoading = status === 'loading';
|
||||
|
||||
// Don't render anything
|
||||
if (isSessionLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Not signed in
|
||||
if (session == null) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<p>
|
||||
<a
|
||||
className="text-primary-800 hover:text-primary-500"
|
||||
href="/api/auth/signin"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
signIn();
|
||||
}}>
|
||||
Sign in
|
||||
</a>{' '}
|
||||
to join discussion
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Signed in. Return Add review button
|
||||
return (
|
||||
<Button
|
||||
display="block"
|
||||
label="Add your review"
|
||||
variant="tertiary"
|
||||
onClick={() => setShowCommentsForm(true)}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,23 +1,24 @@
|
||||
// TODO: Move to a general enums/constants file? For resumes
|
||||
import { ResumesSection } from '@prisma/client';
|
||||
|
||||
export const COMMENTS_SECTIONS = [
|
||||
{
|
||||
label: 'General',
|
||||
value: 'general',
|
||||
value: ResumesSection.GENERAL,
|
||||
},
|
||||
{
|
||||
label: 'Education',
|
||||
value: 'education',
|
||||
value: ResumesSection.EDUCATION,
|
||||
},
|
||||
{
|
||||
label: 'Experience',
|
||||
value: 'experience',
|
||||
value: ResumesSection.EXPERIENCE,
|
||||
},
|
||||
{
|
||||
label: 'Projects',
|
||||
value: 'projects',
|
||||
value: ResumesSection.PROJECTS,
|
||||
},
|
||||
{
|
||||
label: 'Skills',
|
||||
value: 'skills',
|
||||
value: ResumesSection.SKILLS,
|
||||
},
|
||||
];
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createRouter } from './context';
|
||||
|
||||
export const resumeReviewsRouter = createRouter().query('list', {
|
||||
input: z.object({
|
||||
resumeId: z.string(),
|
||||
}),
|
||||
async resolve({ ctx, input }) {
|
||||
const userId = ctx.session?.user?.id;
|
||||
const { resumeId } = input;
|
||||
|
||||
// For this resume, we retrieve every comment's information, along with:
|
||||
// The user's name and image to render
|
||||
// Number of votes, and whether the user (if-any) has voted
|
||||
return await ctx.prisma.resumesComment.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
votes: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
image: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
take: 1,
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
where: {
|
||||
resumeId,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
import { z } from 'zod';
|
||||
import { ResumesSection } from '@prisma/client';
|
||||
|
||||
import { createProtectedRouter } from './context';
|
||||
|
||||
type IResumeCommentInput = Readonly<{
|
||||
description: string;
|
||||
resumeId: string;
|
||||
section: ResumesSection;
|
||||
userId: string;
|
||||
}>;
|
||||
|
||||
export const resumesReviewsUserRouter = createProtectedRouter().mutation(
|
||||
'create',
|
||||
{
|
||||
input: z.object({
|
||||
education: z.string(),
|
||||
experience: z.string(),
|
||||
general: z.string(),
|
||||
projects: z.string(),
|
||||
resumeId: z.string(),
|
||||
skills: z.string(),
|
||||
}),
|
||||
async resolve({ ctx, input }) {
|
||||
const userId = ctx.session?.user.id;
|
||||
const { resumeId, education, experience, general, projects, skills } =
|
||||
input;
|
||||
|
||||
// For each section, convert them into ResumesComment model if provided
|
||||
const comments: Array<IResumeCommentInput> = [
|
||||
{ description: education, section: ResumesSection.EDUCATION },
|
||||
{ description: experience, section: ResumesSection.EXPERIENCE },
|
||||
{ description: general, section: ResumesSection.GENERAL },
|
||||
{ description: projects, section: ResumesSection.PROJECTS },
|
||||
{ description: skills, section: ResumesSection.SKILLS },
|
||||
]
|
||||
.filter(({ description }) => {
|
||||
return description.trim().length > 0;
|
||||
})
|
||||
.map(({ description, section }) => {
|
||||
return {
|
||||
description,
|
||||
resumeId,
|
||||
section,
|
||||
userId,
|
||||
};
|
||||
});
|
||||
|
||||
return await ctx.prisma.resumesComment.createMany({
|
||||
data: comments,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
Loading…
Reference in new issue