mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 20:52:00 +08:00
[resumes][feat] submit resume mutation (#310)
This commit is contained in:
@ -6,8 +6,11 @@ CREATE TABLE "ResumesResume" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"additionalInfo" TEXT NOT NULL,
|
||||
"role" TEXT NOT NULL,
|
||||
"experience" TEXT NOT NULL,
|
||||
"location" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"additionalInfo" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
@ -94,9 +94,12 @@ model ResumesResume {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
title String @db.Text
|
||||
additionalInfo String @db.Text
|
||||
// TODO: Add role, experience, location from Enums
|
||||
// TODO: Update role, experience, location to use Enums
|
||||
role String @db.Text
|
||||
experience String @db.Text
|
||||
location String @db.Text
|
||||
url String
|
||||
additionalInfo String? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
@ -1,10 +1,13 @@
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { PaperClipIcon } from '@heroicons/react/24/outline';
|
||||
import { Button, Select, TextInput } from '@tih/ui';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
const TITLE_PLACEHOLDER =
|
||||
'e.g. Applying for Company XYZ, please help me to review!';
|
||||
const ADDITIONAL_INFO_PLACEHOLDER = `e.g. I’m applying for company XYZ. I have been resume-rejected by N companies that I have applied for. Please help me to review so company XYZ gives me an interview!`;
|
||||
@ -13,7 +16,7 @@ const FILE_UPLOAD_ERROR = 'Please upload a PDF file that is less than 10MB.';
|
||||
const MAX_FILE_SIZE_LIMIT = 10485760;
|
||||
|
||||
type IFormInput = {
|
||||
additionalInformation?: string;
|
||||
additionalInfo?: string;
|
||||
experience: string;
|
||||
file: File;
|
||||
location: string;
|
||||
@ -68,6 +71,9 @@ export default function SubmitResumeForm() {
|
||||
},
|
||||
];
|
||||
|
||||
const resumeCreateMutation = trpc.useMutation('resumes.resume.user.create');
|
||||
const router = useRouter();
|
||||
|
||||
const [resumeFile, setResumeFile] = useState<File | null>();
|
||||
const [invalidFileUploadError, setInvalidFileUploadError] = useState<
|
||||
string | null
|
||||
@ -81,10 +87,11 @@ export default function SubmitResumeForm() {
|
||||
formState: { errors },
|
||||
} = useForm<IFormInput>();
|
||||
|
||||
// TODO: Add Create resume mutation
|
||||
const onSubmit: SubmitHandler<IFormInput> = (data) => {
|
||||
alert(JSON.stringify(data));
|
||||
onClickReset();
|
||||
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
|
||||
await resumeCreateMutation.mutate({
|
||||
...data,
|
||||
});
|
||||
router.push('/resumes');
|
||||
};
|
||||
|
||||
const onUploadFile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -196,10 +203,10 @@ export default function SubmitResumeForm() {
|
||||
<div className="mb-4">
|
||||
{/* TODO: Use TextInputArea instead */}
|
||||
<TextInput
|
||||
{...register('additionalInformation')}
|
||||
{...register('additionalInfo')}
|
||||
label="Additional Information"
|
||||
placeholder={ADDITIONAL_INFO_PLACEHOLDER}
|
||||
onChange={(val) => setValue('additionalInformation', val)}
|
||||
onChange={(val) => setValue('additionalInfo', val)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end gap-4">
|
||||
|
@ -2,6 +2,7 @@ import superjson from 'superjson';
|
||||
|
||||
import { createRouter } from './context';
|
||||
import { protectedExampleRouter } from './protected-example-router';
|
||||
import { resumesResumeUserRouter } from './resumes-resume-user-router';
|
||||
import { todosRouter } from './todos';
|
||||
import { todosUserRouter } from './todos-user-router';
|
||||
|
||||
@ -12,7 +13,8 @@ export const appRouter = createRouter()
|
||||
// Example routers. Learn more about tRPC routers: https://trpc.io/docs/v9/router
|
||||
.merge('auth.', protectedExampleRouter)
|
||||
.merge('todos.', todosRouter)
|
||||
.merge('todos.user.', todosUserRouter);
|
||||
.merge('todos.user.', todosUserRouter)
|
||||
.merge('resumes.resume.user.', resumesResumeUserRouter);
|
||||
|
||||
// Export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
30
apps/portal/src/server/router/resumes-resume-user-router.ts
Normal file
30
apps/portal/src/server/router/resumes-resume-user-router.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createProtectedRouter } from './context';
|
||||
|
||||
export const resumesResumeUserRouter = createProtectedRouter().mutation(
|
||||
'create',
|
||||
{
|
||||
// TODO: Use enums for experience, location, role
|
||||
input: z.object({
|
||||
additionalInfo: z.string().optional(),
|
||||
experience: z.string(),
|
||||
location: z.string(),
|
||||
role: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
async resolve({ ctx, input }) {
|
||||
const userId = ctx.session?.user.id;
|
||||
|
||||
// TODO: Store file in file storage and retrieve URL
|
||||
|
||||
return await ctx.prisma.resumesResume.create({
|
||||
data: {
|
||||
...input,
|
||||
url: '',
|
||||
userId,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
Reference in New Issue
Block a user