[resumes][feat] Toggle resume star button

pull/322/head
Wu Peirong 3 years ago
parent 902f07601e
commit e6f397416e

@ -0,0 +1,73 @@
/*
Warnings:
- You are about to drop the column `resumesProfileId` on the `ResumesComment` table. All the data in the column will be lost.
- You are about to drop the column `resumesProfileId` on the `ResumesCommentVote` table. All the data in the column will be lost.
- You are about to drop the column `resumesProfileId` on the `ResumesResume` table. All the data in the column will be lost.
- You are about to drop the column `resumesProfileId` on the `ResumesStar` table. All the data in the column will be lost.
- You are about to drop the `ResumesProfile` table. If the table is not empty, all the data it contains will be lost.
- A unique constraint covering the columns `[userId,commentId]` on the table `ResumesCommentVote` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[userId,resumeId]` on the table `ResumesStar` will be added. If there are existing duplicate values, this will fail.
- Added the required column `userId` to the `ResumesComment` table without a default value. This is not possible if the table is not empty.
- Added the required column `userId` to the `ResumesCommentVote` table without a default value. This is not possible if the table is not empty.
- Added the required column `userId` to the `ResumesResume` table without a default value. This is not possible if the table is not empty.
- Added the required column `userId` to the `ResumesStar` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "ResumesComment" DROP CONSTRAINT "ResumesComment_resumesProfileId_fkey";
-- DropForeignKey
ALTER TABLE "ResumesCommentVote" DROP CONSTRAINT "ResumesCommentVote_resumesProfileId_fkey";
-- DropForeignKey
ALTER TABLE "ResumesProfile" DROP CONSTRAINT "ResumesProfile_userId_fkey";
-- DropForeignKey
ALTER TABLE "ResumesResume" DROP CONSTRAINT "ResumesResume_resumesProfileId_fkey";
-- DropForeignKey
ALTER TABLE "ResumesStar" DROP CONSTRAINT "ResumesStar_resumesProfileId_fkey";
-- DropIndex
DROP INDEX "ResumesCommentVote_commentId_resumesProfileId_key";
-- DropIndex
DROP INDEX "ResumesStar_resumeId_resumesProfileId_key";
-- AlterTable
ALTER TABLE "ResumesComment" DROP COLUMN "resumesProfileId",
ADD COLUMN "userId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "ResumesCommentVote" DROP COLUMN "resumesProfileId",
ADD COLUMN "userId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "ResumesResume" DROP COLUMN "resumesProfileId",
ADD COLUMN "userId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "ResumesStar" DROP COLUMN "resumesProfileId",
ADD COLUMN "userId" TEXT NOT NULL;
-- DropTable
DROP TABLE "ResumesProfile";
-- CreateIndex
CREATE UNIQUE INDEX "ResumesCommentVote_userId_commentId_key" ON "ResumesCommentVote"("userId", "commentId");
-- CreateIndex
CREATE UNIQUE INDEX "ResumesStar_userId_resumeId_key" ON "ResumesStar"("userId", "resumeId");
-- AddForeignKey
ALTER TABLE "ResumesResume" ADD CONSTRAINT "ResumesResume_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ResumesStar" ADD CONSTRAINT "ResumesStar_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ResumesComment" ADD CONSTRAINT "ResumesComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ResumesCommentVote" ADD CONSTRAINT "ResumesCommentVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

@ -37,15 +37,18 @@ model Session {
} }
model User { model User {
id String @id @default(cuid()) id String @id @default(cuid())
name String? name String?
email String? @unique email String? @unique
emailVerified DateTime? emailVerified DateTime?
image String? image String?
accounts Account[] accounts Account[]
sessions Session[] sessions Session[]
todos Todo[] todos Todo[]
resumesProfile ResumesProfile? resumesResumes ResumesResume[]
resumesStars ResumesStar[]
resumesComments ResumesComment[]
resumesCommentVotes ResumesCommentVote[]
} }
model VerificationToken { model VerificationToken {
@ -85,56 +88,45 @@ model Company {
// Add Resumes project models here, prefix all models with "Resumes", // Add Resumes project models here, prefix all models with "Resumes",
// use camelCase for field names, and try to name them consistently // use camelCase for field names, and try to name them consistently
// across all models in this file. // across all models in this file.
model ResumesProfile {
id String @id @default(cuid())
userId String @unique
resumesResumes ResumesResume[]
resumesStars ResumesStar[]
resumesComments ResumesComment[]
resumesCommentVotes ResumesCommentVote[]
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model ResumesResume { model ResumesResume {
id String @id @default(cuid()) id String @id @default(cuid())
resumesProfileId String userId String
title String @db.Text title String @db.Text
// TODO: Update role, experience, location to use Enums // TODO: Update role, experience, location to use Enums
role String @db.Text role String @db.Text
experience String @db.Text experience String @db.Text
location String @db.Text location String @db.Text
url String url String
additionalInfo String? @db.Text additionalInfo String? @db.Text
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
resumesProfile ResumesProfile @relation(fields: [resumesProfileId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
stars ResumesStar[] stars ResumesStar[]
comments ResumesComment[] comments ResumesComment[]
} }
model ResumesStar { model ResumesStar {
id String @id @default(cuid()) id String @id @default(cuid())
resumesProfileId String userId String
resumeId String resumeId String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
resumesProfile ResumesProfile @relation(fields: [resumesProfileId], references: [id], onDelete: Cascade) resume ResumesResume @relation(fields: [resumeId], references: [id], onDelete: Cascade)
resume ResumesResume @relation(fields: [resumeId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([resumeId, resumesProfileId]) @@unique([userId, resumeId])
} }
model ResumesComment { model ResumesComment {
id String @id @default(cuid()) id String @id @default(cuid())
resumesProfileId String userId String
resumeId String resumeId String
description String @db.Text description String @db.Text
section ResumesSection section ResumesSection
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
resumesProfile ResumesProfile @relation(fields: [resumesProfileId], references: [id], onDelete: Cascade) 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)
} }
enum ResumesSection { enum ResumesSection {
@ -146,16 +138,16 @@ enum ResumesSection {
} }
model ResumesCommentVote { model ResumesCommentVote {
id String @id @default(cuid()) id String @id @default(cuid())
resumesProfileId String userId String
commentId String commentId String
value Int value Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
resumesProfile ResumesProfile @relation(fields: [resumesProfileId], references: [id], onDelete: Cascade) comment ResumesComment @relation(fields: [commentId], references: [id], onDelete: Cascade)
comment ResumesComment @relation(fields: [commentId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([commentId, resumesProfileId]) @@unique([userId, commentId])
} }
// End of Resumes project models. // End of Resumes project models.

@ -1,6 +1,9 @@
import clsx from 'clsx';
import formatDistanceToNow from 'date-fns/formatDistanceToNow'; import formatDistanceToNow from 'date-fns/formatDistanceToNow';
import Error from 'next/error'; import Error from 'next/error';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useEffect } from 'react';
import { import {
AcademicCapIcon, AcademicCapIcon,
BriefcaseIcon, BriefcaseIcon,
@ -20,13 +23,42 @@ export default function ResumeReviewPage() {
const ErrorPage = ( const ErrorPage = (
<Error statusCode={404} title="Requested resume does not exist." /> <Error statusCode={404} title="Requested resume does not exist." />
); );
const { data: session } = useSession();
const router = useRouter(); const router = useRouter();
const { resumeId } = router.query; const { resumeId } = router.query;
const utils = trpc.useContext();
// Safe to assert resumeId type as string because query is only sent if so // Safe to assert resumeId type as string because query is only sent if so
const detailsQuery = trpc.useQuery( const detailsQuery = trpc.useQuery(
['resumes.details.find', { resumeId: resumeId as string }], [
{ enabled: typeof resumeId === 'string' }, 'resumes.details.find',
{ resumeId: resumeId as string, userId: session?.user?.id },
],
{
enabled: typeof resumeId === 'string' && session?.user?.id !== undefined,
},
); );
const starMutation = trpc.useMutation('resumes.details.update_star', {
onSuccess() {
utils.invalidateQueries();
},
});
useEffect(() => {
if (detailsQuery.data?.stars.length) {
document.getElementById('star-button')?.focus();
} else {
document.getElementById('star-button')?.blur();
}
}, [detailsQuery.data?.stars]);
const onStarButtonClick = () => {
// Star button only rendered if resume exists
// Star button only clickable if user exists
starMutation.mutate({
resumeId: resumeId as string,
userId: session!.user!.id!,
});
};
return ( return (
<> <>
@ -40,11 +72,20 @@ export default function ResumeReviewPage() {
</h1> </h1>
<button <button
className="isolate inline-flex max-h-10 items-center space-x-4 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" className="isolate inline-flex max-h-10 items-center space-x-4 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
type="button"> disabled={session?.user === null}
id="star-button"
type="button"
onClick={onStarButtonClick}>
<span className="relative inline-flex"> <span className="relative inline-flex">
<StarIcon <StarIcon
aria-hidden="true" aria-hidden="true"
className="-ml-1 mr-2 h-5 w-5 text-gray-400" className={clsx(
detailsQuery.data?.stars.length
? 'text-orange-400'
: 'text-gray-400',
'-ml-1 mr-2 h-5 w-5',
)}
id="star-icon"
/> />
Star Star
</span> </span>
@ -83,7 +124,7 @@ export default function ResumeReviewPage() {
{`Uploaded ${formatDistanceToNow( {`Uploaded ${formatDistanceToNow(
new Date(detailsQuery.data.createdAt), new Date(detailsQuery.data.createdAt),
{ addSuffix: true }, { addSuffix: true },
)} by ${detailsQuery.data.resumesProfile.user.name}`} )} by ${detailsQuery.data.user.name}`}
</div> </div>
</div> </div>
{detailsQuery.data.additionalInfo && ( {detailsQuery.data.additionalInfo && (

@ -2,35 +2,77 @@ import { z } from 'zod';
import { createRouter } from './context'; import { createRouter } from './context';
export const resumesDetailsRouter = createRouter().query('find', { export const resumesDetailsRouter = createRouter()
input: z.object({ .query('find', {
resumeId: z.string(), input: z.object({
}), resumeId: z.string(),
async resolve({ ctx, input }) { userId: z.string().optional(),
const { resumeId } = input; }),
async resolve({ ctx, input }) {
const { resumeId, userId } = input;
// Use the resumeId to query all related information of a single resume // Use the resumeId to query all related information of a single resume
// from Resumesresume: // from Resumesresume:
return await ctx.prisma.resumesResume.findUnique({ return await ctx.prisma.resumesResume.findUnique({
include: { include: {
_count: { _count: {
select: { select: {
stars: true, stars: true,
},
}, },
}, stars: {
resumesProfile: { where: {
select: { userId,
user: { },
select: { },
name: true, user: {
}, select: {
name: true,
}, },
}, },
}, },
}, where: {
where: { id: resumeId,
id: resumeId, },
}, });
}); },
}, })
}); .mutation('update_star', {
input: z.object({
resumeId: z.string(),
userId: z.string(),
}),
async resolve({ ctx, input }) {
const { resumeId, userId } = input;
// Use the resumeId and resumeProfileId to check if star exists
const resumesStar = await ctx.prisma.resumesStar.findUnique({
select: {
id: true,
},
where: {
userId_resumeId: {
resumeId,
userId,
},
},
});
if (resumesStar === null) {
return await ctx.prisma.resumesStar.create({
data: {
resumeId,
userId,
},
});
}
return await ctx.prisma.resumesStar.delete({
where: {
userId_resumeId: {
resumeId,
userId,
},
},
});
},
});

Loading…
Cancel
Save