diff --git a/package.json b/package.json index ce284728..0daab8d6 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,12 @@ "dependencies": { "@aws-sdk/client-s3": "^3.645.0", "@aws-sdk/s3-request-presigner": "^3.645.0", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", "@faker-js/faker": "^9.0.0", + "@fontsource/roboto": "^5.1.0", "@hookform/resolvers": "^3.9.0", + "@mui/material": "^6.1.3", "@prisma/client": "5.18.0", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-avatar": "^1.1.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8aa98268..502eebba 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,11 +20,16 @@ model User { email String @unique emailVerified DateTime? + skills String[] + experience Experience[] + project Project[] + resume String? + oauthProvider OauthProvider? // Tracks OAuth provider (e.g., 'google') oauthId String? blockedByAdmin DateTime? - + onBoard Boolean @default(false) } enum OauthProvider { @@ -75,6 +80,31 @@ model Job { user User @relation(fields: [userId], references: [id]) } +model Experience { + id Int @id @default(autoincrement()) + companyName String + designation String + EmploymentType EmployementType + address String + workMode WorkMode + currentWorkStatus Boolean + startDate DateTime + endDate DateTime? + description String + userId String + user User @relation(fields: [userId] ,references: [id]) +} + +model Project { + id Int @id @default(autoincrement()) + projectName String + projectSummary String + projectLiveLink String? + projectGithub String + userId String + user User @relation(fields: [userId] , references: [id]) +} + enum Currency { INR USD diff --git a/prisma/seed.ts b/prisma/seed.ts index 7a16f7f6..90e81712 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -13,7 +13,7 @@ const prisma = new PrismaClient(); const users = [ { id: '1', name: 'Jack', email: 'user@gmail.com' }, - { id: '2', name: 'Admin', email: 'admin@gmail.com', role: Role.ADMIN }, + { id: '2', name: 'Admin', email: 'admin@gmail.com', role: Role.ADMIN , onBoard : true }, { id: '3', name: 'Hr', email: 'hr@gmail.com', role: Role.HR }, ]; diff --git a/src/actions/user.profile.actions.ts b/src/actions/user.profile.actions.ts index 9ba48b9f..511db05a 100644 --- a/src/actions/user.profile.actions.ts +++ b/src/actions/user.profile.actions.ts @@ -1,7 +1,18 @@ 'use server'; import prisma from '@/config/prisma.config'; -import { UserProfileSchemaType } from '@/lib/validators/user.profile.validator'; +import { + addSkillsSchemaType, + expFormSchemaType, + projectSchemaType, + UserProfileSchemaType, +} from '@/lib/validators/user.profile.validator'; import bcryptjs from 'bcryptjs'; +import { authOptions } from '@/lib/authOptions'; +import { getServerSession } from 'next-auth'; +import { ErrorHandler } from '@/lib/error'; +import { withServerActionAsyncCatcher } from '@/lib/async-catch'; +import { ServerActionReturnType } from '@/types/api.types'; +import { SuccessResponse } from '@/lib/success'; export const updateUser = async ( email: string, @@ -125,3 +136,98 @@ export const deleteUser = async (email: string) => { return { error: error }; } }; + +export const addUserSkills = withServerActionAsyncCatcher< + addSkillsSchemaType, + ServerActionReturnType +>(async (data) => { + const auth = await getServerSession(authOptions); + + if (!auth || !auth?.user?.id) + throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED'); + + try { + await prisma.user.update({ + where: { + id: auth.user.id, + }, + data: { + skills: data.skills, + }, + }); + return new SuccessResponse('Skills updated successfully', 200).serialize(); + } catch (_error) { + return new ErrorHandler('Internal server error', 'DATABASE_ERROR'); + } +}); + +export const addUserExperience = withServerActionAsyncCatcher< + expFormSchemaType, + ServerActionReturnType +>(async (data) => { + const auth = await getServerSession(authOptions); + + if (!auth || !auth?.user?.id) + throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED'); + + try { + await prisma.experience.create({ + data: { + ...data, + userId: auth.user.id, + }, + }); + return new SuccessResponse( + 'Experience updated successfully', + 200 + ).serialize(); + } catch (_error) { + return new ErrorHandler('Internal server error', 'DATABASE_ERROR'); + } +}); + +export const addUserProjects = withServerActionAsyncCatcher< + projectSchemaType, + ServerActionReturnType +>(async (data) => { + const auth = await getServerSession(authOptions); + + if (!auth || !auth?.user?.id) + throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED'); + + try { + await prisma.project.create({ + data: { + ...data, + userId: auth.user.id, + }, + }); + return new SuccessResponse('Project updated successfully', 200).serialize(); + } catch (_error) { + return new ErrorHandler('Internal server error', 'DATABASE_ERROR'); + } +}); + +export const validateUserBoarding = async () => { + const auth = await getServerSession(authOptions); + + if (!auth || !auth?.user?.id) + throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED'); + + try { + await prisma.user.update({ + where: { + id: auth.user.id, + }, + data: { + onBoard: true, + }, + }); + return new SuccessResponse( + 'Welcome!! On Boarding successfully', + 200 + ).serialize(); + } catch (_error) { + return new ErrorHandler('Internal server error', 'DATABASE_ERROR'); + } +}; diff --git a/src/app/create-profile/page.tsx b/src/app/create-profile/page.tsx new file mode 100644 index 00000000..0975521e --- /dev/null +++ b/src/app/create-profile/page.tsx @@ -0,0 +1,18 @@ +import VerticalLinearStepper from '@/components/user-multistep-form/user-multistep-form'; +import { authOptions } from '@/lib/authOptions'; +import { getServerSession } from 'next-auth'; +import { redirect } from 'next/navigation'; + +export default async function Home() { + const session = await getServerSession(authOptions); + if (!session || session.user.role !== 'USER') redirect('/'); + if (session.user.onBoard === true) redirect('/profile'); + return ( +
+

+ Hey {session?.user.name} let's get you set up and started! +

+ +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index bd78e16e..a0669a33 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,8 +2,18 @@ import Faqs from '@/components/Faqs'; import HeroSection from '@/components/hero-section'; import { JobLanding } from '@/components/job-landing'; import Testimonials from '@/components/Testimonials'; +import { authOptions } from '@/lib/authOptions'; +import { getServerSession } from 'next-auth'; +import { redirect } from 'next/navigation'; const HomePage = async () => { + const session = await getServerSession(authOptions); + { + session && + session.user.role === 'USER' && + !session.user.onBoard && + redirect('/create-profile'); + } return (
diff --git a/src/components/auth/signin.tsx b/src/components/auth/signin.tsx index 34a61b0b..76d6f7f1 100644 --- a/src/components/auth/signin.tsx +++ b/src/components/auth/signin.tsx @@ -52,6 +52,7 @@ export const Signin = () => { const searchParams = new URLSearchParams(window.location.search); const redirect = searchParams.get('next') || APP_PATHS.HOME; router.push(redirect); + router.refresh(); } catch (_error) { return toast({ title: 'Internal server error', diff --git a/src/components/skills-combobox.tsx b/src/components/skills-combobox.tsx index 870ad44d..3f724d5c 100644 --- a/src/components/skills-combobox.tsx +++ b/src/components/skills-combobox.tsx @@ -9,6 +9,7 @@ import { UseFormReturn } from 'react-hook-form'; import { JobPostSchemaType } from '@/lib/validators/jobs.validator'; import _ from 'lodash'; import { X } from 'lucide-react'; +import { addSkillsSchemaType } from '@/lib/validators/user.profile.validator'; export function SkillsCombobox({ form, @@ -16,7 +17,7 @@ export function SkillsCombobox({ setComboBoxSelectedValues, }: { comboBoxSelectedValues: string[]; - form: UseFormReturn; + form: UseFormReturn | UseFormReturn; setComboBoxSelectedValues: React.Dispatch>; }) { const [comboBoxInputValue, setComboBoxInputValue] = useState(''); diff --git a/src/components/user-multistep-form/add-project-form.tsx b/src/components/user-multistep-form/add-project-form.tsx new file mode 100644 index 00000000..fab2ba4d --- /dev/null +++ b/src/components/user-multistep-form/add-project-form.tsx @@ -0,0 +1,117 @@ +import { + projectSchema, + projectSchemaType, +} from '@/lib/validators/user.profile.validator'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { + Form, + FormItem, + FormLabel, + FormControl, + FormMessage, + FormField, +} from '../ui/form'; +import { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { Textarea } from '../ui/textarea'; +import { useToast } from '../ui/use-toast'; +import { addUserProjects } from '@/actions/user.profile.actions'; + +export const AddProject = () => { + const form = useForm({ + resolver: zodResolver(projectSchema), + defaultValues: { + projectName: '', + projectSummary: '', + projectGithub: '', + projectLiveLink: '', + }, + }); + + const { toast } = useToast(); + + const onSubmit = async (data: projectSchemaType) => { + try { + const response = await addUserProjects(data); + + if (!response.status) { + return toast({ + title: response.message || 'Error', + variant: 'destructive', + }); + } + toast({ + title: response.message, + variant: 'success', + }); + form.reset(form.formState.defaultValues); + } catch (_error) { + toast({ + title: 'Something went wrong while Adding Projects', + description: 'Internal server error', + variant: 'destructive', + }); + } + }; + + return ( +
+ + ( + + Project Name + + + + + )} + /> + ( + + Project Github Link + + + + + )} + /> + ( + + Project Live Link + + + + + + )} + /> + ( + + Project Summary + +