untested pushhhhh
This commit is contained in:
@@ -1,116 +1,114 @@
|
||||
'use server'
|
||||
import { db, students, grades, internships as internshipsTable, resumes as resumesTable } from '@workspace/db';
|
||||
'use server';
|
||||
import {
|
||||
db,
|
||||
students,
|
||||
grades,
|
||||
internships as internshipsTable,
|
||||
resumes as resumesTable,
|
||||
} from '@workspace/db';
|
||||
import { eq } from '@workspace/db/drizzle';
|
||||
import { studentSignupSchema } from './schema';
|
||||
import { studentSignupSchema, StudentSignup } from './schema';
|
||||
import { auth } from '@/auth';
|
||||
|
||||
export async function signupAction(data: FormData) {
|
||||
export async function signupAction(data: StudentSignup) {
|
||||
try {
|
||||
const session = await auth();
|
||||
const studentId = session?.user?.studentId;
|
||||
if (!studentId) {
|
||||
return { error: 'Student ID not found in session.' };
|
||||
return { error: 'Student ID not found in session.' };
|
||||
}
|
||||
|
||||
const formData = Object.fromEntries(data.entries());
|
||||
// Parse arrays/objects from formData if sent as JSON strings
|
||||
if (typeof formData.skills === 'string') formData.skills = JSON.parse(formData.skills);
|
||||
if (typeof formData.sgpi === 'string') formData.sgpi = JSON.parse(formData.sgpi);
|
||||
if (typeof formData.internships === 'string') formData.internships = JSON.parse(formData.internships);
|
||||
if (typeof formData.resume === 'string') formData.resume = JSON.parse(formData.resume);
|
||||
|
||||
const parsedData = await studentSignupSchema.safeParseAsync(formData);
|
||||
// Validate data using schema
|
||||
const parsedData = await studentSignupSchema.safeParseAsync(data);
|
||||
|
||||
if (!parsedData.success) {
|
||||
return { error: parsedData.error.issues };
|
||||
return { error: parsedData.error.issues };
|
||||
}
|
||||
|
||||
const student = parsedData.data;
|
||||
|
||||
// Update student table
|
||||
await db.update(students).set({
|
||||
rollNumber: student.rollNumber,
|
||||
firstName: student.firstName,
|
||||
middleName: student.middleName,
|
||||
lastName: student.lastName,
|
||||
mothersName: student.mothersName,
|
||||
gender: student.gender,
|
||||
dob: student.dob,
|
||||
personalGmail: student.personalGmail,
|
||||
phoneNumber: student.phoneNumber,
|
||||
address: student.address,
|
||||
degree: student.degree,
|
||||
branch: student.branch,
|
||||
year: student.year,
|
||||
skills: student.skills, // store as array
|
||||
linkedin: student.linkedin,
|
||||
github: student.github,
|
||||
ssc: String(student.ssc),
|
||||
hsc: String(student.hsc),
|
||||
isDiploma: student.isDiploma,
|
||||
}).where(eq(students.id, studentId));
|
||||
// Use a transaction to ensure all operations succeed or fail together
|
||||
await db.transaction(async (tx) => {
|
||||
// Update student table
|
||||
await tx
|
||||
.update(students)
|
||||
.set({
|
||||
rollNumber: student.rollNumber,
|
||||
firstName: student.firstName,
|
||||
middleName: student.middleName,
|
||||
lastName: student.lastName,
|
||||
mothersName: student.mothersName,
|
||||
gender: student.gender,
|
||||
dob: student.dob,
|
||||
personalGmail: student.personalGmail,
|
||||
phoneNumber: student.phoneNumber,
|
||||
address: student.address,
|
||||
degree: student.degree,
|
||||
branch: student.branch,
|
||||
year: student.year,
|
||||
skills: student.skills, // store as array
|
||||
linkedin: student.linkedin,
|
||||
github: student.github,
|
||||
ssc: String(student.ssc),
|
||||
hsc: String(student.hsc),
|
||||
isDiploma: student.isDiploma,
|
||||
})
|
||||
.where(eq(students.id, studentId));
|
||||
|
||||
// Upsert grades (sgpi)
|
||||
if (Array.isArray(student.sgpi)) {
|
||||
// Clear existing grades for this student
|
||||
await tx.delete(grades).where(eq(grades.studentId, studentId));
|
||||
|
||||
// Insert grades (sgpi)
|
||||
if (Array.isArray(student.sgpi)) {
|
||||
for (const grade of student.sgpi) {
|
||||
await db.insert(grades).values({
|
||||
studentId: studentId,
|
||||
sem: grade.sem,
|
||||
sgpi: String(grade.sgpi),
|
||||
isKT: grade.kt,
|
||||
deadKT: grade.ktDead,
|
||||
}).onConflictDoUpdate({
|
||||
target: [grades.studentId, grades.sem],
|
||||
set: {
|
||||
sgpi: String(grade.sgpi),
|
||||
isKT: grade.kt,
|
||||
deadKT: grade.ktDead,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
await tx.insert(grades).values({
|
||||
studentId: studentId,
|
||||
sem: grade.sem,
|
||||
sgpi: String(grade.sgpi),
|
||||
isKT: grade.kt,
|
||||
deadKT: grade.ktDead,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert internships
|
||||
if (Array.isArray(student.internships)) {
|
||||
// Clear existing internships for this student
|
||||
await tx.delete(internshipsTable).where(eq(internshipsTable.studentId, studentId));
|
||||
|
||||
// Insert internships
|
||||
if (Array.isArray(student.internships)) {
|
||||
for (const internship of student.internships) {
|
||||
await db.insert(internshipsTable).values({
|
||||
studentId,
|
||||
title: internship.title,
|
||||
company: internship.company,
|
||||
description: internship.description,
|
||||
location: internship.location,
|
||||
startDate: internship.startDate,
|
||||
endDate: internship.endDate,
|
||||
}).onConflictDoUpdate({
|
||||
target: [internshipsTable.studentId, internshipsTable.title, internshipsTable.company],
|
||||
set: {
|
||||
description: internship.description,
|
||||
location: internship.location,
|
||||
startDate: internship.startDate,
|
||||
endDate: internship.endDate,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
await tx.insert(internshipsTable).values({
|
||||
studentId,
|
||||
title: internship.title,
|
||||
company: internship.company,
|
||||
description: internship.description,
|
||||
location: internship.location,
|
||||
startDate: internship.startDate,
|
||||
endDate: internship.endDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert resumes
|
||||
if (Array.isArray(student.resume)) {
|
||||
// Clear existing resumes for this student
|
||||
await tx.delete(resumesTable).where(eq(resumesTable.studentId, studentId));
|
||||
|
||||
// Insert resumes
|
||||
if (Array.isArray(student.resume)) {
|
||||
for (const resume of student.resume) {
|
||||
await db.insert(resumesTable).values({
|
||||
studentId,
|
||||
title: resume.title,
|
||||
link: resume.link,
|
||||
}).onConflictDoUpdate({
|
||||
target: [resumesTable.studentId, resumesTable.title],
|
||||
set: {
|
||||
link: resume.link,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
await tx.insert(resumesTable).values({
|
||||
studentId,
|
||||
title: resume.title,
|
||||
link: resume.link,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Signup action error:', error);
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'An unexpected error occurred during signup.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useTransition } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
@@ -13,6 +13,7 @@ import { Button } from '@workspace/ui/components/button';
|
||||
import { Progress } from '@workspace/ui/components/progress';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
||||
import { Form } from '@workspace/ui/components/form';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { studentSignupSchema, StudentSignup } from './schema';
|
||||
import PersonalDetailsStep from './steps/PersonalDetailsStep';
|
||||
@@ -22,9 +23,29 @@ import AdditionalDetailsStep from './steps/AdditionalDetailsStep';
|
||||
import InternshipStep from './steps/InternshipStep';
|
||||
import ResumeStep from './steps/ResumeStep';
|
||||
|
||||
import { signupAction } from './action';
|
||||
|
||||
const steps = [
|
||||
{ id: 1, title: 'Personal Details', fields: ['firstName', 'lastName', 'mothersName', 'rollNumber', 'phoneNumber', 'address', 'gender', 'dob', 'personalGmail'] },
|
||||
{ id: 2, title: 'Academic Details', fields: ['degree', 'year', 'branch', 'ssc', 'hsc', 'isDiploma'] },
|
||||
{
|
||||
id: 1,
|
||||
title: 'Personal Details',
|
||||
fields: [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'mothersName',
|
||||
'rollNumber',
|
||||
'phoneNumber',
|
||||
'address',
|
||||
'gender',
|
||||
'dob',
|
||||
'personalGmail',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Academic Details',
|
||||
fields: ['degree', 'year', 'branch', 'ssc', 'hsc', 'isDiploma'],
|
||||
},
|
||||
{ id: 3, title: 'Semester Grades', fields: ['sgpi'] },
|
||||
{ id: 4, title: 'Additional Details', fields: ['linkedin', 'github', 'skills'] },
|
||||
{ id: 5, title: 'Internships', fields: ['internships'] },
|
||||
@@ -34,6 +55,8 @@ const steps = [
|
||||
export default function StudentRegistrationForm() {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm<StudentSignup>({
|
||||
resolver: zodResolver(studentSignupSchema),
|
||||
@@ -57,7 +80,12 @@ export default function StudentRegistrationForm() {
|
||||
ssc: 0,
|
||||
hsc: 0,
|
||||
isDiploma: false,
|
||||
sgpi: Array.from({ length: 8 }, (_, i) => ({ sem: i + 1, sgpi: 0, kt: false, ktDead: false })),
|
||||
sgpi: Array.from({ length: 8 }, (_, i) => ({
|
||||
sem: i + 1,
|
||||
sgpi: 0,
|
||||
kt: false,
|
||||
ktDead: false,
|
||||
})),
|
||||
internships: [],
|
||||
resume: [],
|
||||
},
|
||||
@@ -68,13 +96,21 @@ export default function StudentRegistrationForm() {
|
||||
const validateCurrentStep = async () => {
|
||||
const current = steps.find((s) => s.id === currentStep);
|
||||
if (!current) return false;
|
||||
// Cast fields to the correct type for react-hook-form
|
||||
return await form.trigger(current.fields as Parameters<typeof form.trigger>[0]);
|
||||
|
||||
try {
|
||||
const result = await form.trigger(current.fields as (keyof StudentSignup)[]);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Validation error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const nextStep = async () => {
|
||||
const isValid = await validateCurrentStep();
|
||||
if (isValid && currentStep < steps.length) setCurrentStep((prev) => prev + 1);
|
||||
if (isValid && currentStep < steps.length) {
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const prevStep = () => {
|
||||
@@ -84,13 +120,24 @@ export default function StudentRegistrationForm() {
|
||||
const onSubmit = async (data: StudentSignup) => {
|
||||
// Only submit if on the last step
|
||||
if (currentStep !== steps.length) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await new Promise((res) => setTimeout(res, 2000));
|
||||
console.log('Form submitted:', data);
|
||||
alert('Form submitted successfully!');
|
||||
const result = await signupAction(data);
|
||||
if (result && result.success) {
|
||||
router.push('/');
|
||||
return;
|
||||
}
|
||||
if (result && result.error) {
|
||||
const errorMessage = Array.isArray(result.error)
|
||||
? result.error.map((e) => e.message || e).join(', ')
|
||||
: result.error;
|
||||
alert('Submission failed: ' + errorMessage);
|
||||
} else {
|
||||
alert('Submission failed. Try again.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error('Submission error:', err);
|
||||
alert('Submission failed. Try again.');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -128,7 +175,9 @@ export default function StudentRegistrationForm() {
|
||||
</CardTitle>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm text-muted-foreground">
|
||||
<span>Step {currentStep} of {steps.length}</span>
|
||||
<span>
|
||||
Step {currentStep} of {steps.length}
|
||||
</span>
|
||||
<span>{steps[currentStep - 1]?.title}</span>
|
||||
</div>
|
||||
<Progress value={progress} className="w-full" />
|
||||
@@ -139,7 +188,7 @@ export default function StudentRegistrationForm() {
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
onKeyDown={e => {
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === 'Enter' &&
|
||||
e.target instanceof HTMLElement &&
|
||||
@@ -151,16 +200,17 @@ export default function StudentRegistrationForm() {
|
||||
>
|
||||
{renderStep()}
|
||||
<div className="flex justify-between pt-6">
|
||||
<Button type="button" variant="outline" onClick={prevStep} disabled={currentStep === 1}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={prevStep}
|
||||
disabled={currentStep === 1}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
{currentStep === steps.length ? (
|
||||
<Button
|
||||
type="button"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => form.handleSubmit(onSubmit)()}
|
||||
>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
<Button type="submit" disabled={isSubmitting || isPending}>
|
||||
{isSubmitting || isPending ? 'Submitting...' : 'Submit'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button type="button" onClick={nextStep}>
|
||||
|
||||
@@ -7,43 +7,50 @@ export const sgpiSchema = z.object({
|
||||
ktDead: z.boolean(),
|
||||
});
|
||||
|
||||
export const internshipSchema = z.object({
|
||||
title: z.string(),
|
||||
company: z.string(),
|
||||
description: z.string(),
|
||||
location: z.string(),
|
||||
startDate: z.coerce.date(),
|
||||
endDate: z.coerce.date(),
|
||||
});
|
||||
export const internshipSchema = z
|
||||
.object({
|
||||
title: z.string().min(1, 'Title is required'),
|
||||
company: z.string().min(1, 'Company is required'),
|
||||
description: z.string().min(1, 'Description is required'),
|
||||
location: z.string().min(1, 'Location is required'),
|
||||
startDate: z.coerce.date(),
|
||||
endDate: z.coerce.date(),
|
||||
})
|
||||
.refine((data) => data.endDate >= data.startDate, {
|
||||
message: 'End date must be after start date',
|
||||
path: ['endDate'],
|
||||
});
|
||||
|
||||
export const resumeSchema = z.object({
|
||||
title: z.string(),
|
||||
link: z.string().url(),
|
||||
title: z.string().min(1, 'Title is required'),
|
||||
link: z.string().url('Must be a valid URL'),
|
||||
});
|
||||
|
||||
export const studentSignupSchema = z.object({
|
||||
rollNumber: z.string().max(12),
|
||||
firstName: z.string().max(255),
|
||||
middleName: z.string().max(255),
|
||||
lastName: z.string().max(255),
|
||||
mothersName: z.string().max(255),
|
||||
gender: z.string().max(10),
|
||||
dob: z.coerce.date(),
|
||||
personalGmail: z.string().email(),
|
||||
phoneNumber: z.string().max(10),
|
||||
address: z.string(),
|
||||
degree: z.string(),
|
||||
branch: z.string(),
|
||||
year: z.string(),
|
||||
rollNumber: z.string().min(1, 'Roll number is required').max(12),
|
||||
firstName: z.string().min(1, 'First name is required').max(255),
|
||||
middleName: z.string().max(255).optional(),
|
||||
lastName: z.string().min(1, 'Last name is required').max(255),
|
||||
mothersName: z.string().min(1, "Mother's name is required").max(255),
|
||||
gender: z.string().min(1, 'Gender is required').max(10),
|
||||
dob: z.coerce.date().refine((date) => date <= new Date(), {
|
||||
message: 'Date of birth cannot be in the future',
|
||||
}),
|
||||
personalGmail: z.string().email('Must be a valid email'),
|
||||
phoneNumber: z.string().min(10, 'Phone number must be at least 10 digits').max(10),
|
||||
address: z.string().min(1, 'Address is required'),
|
||||
degree: z.string().min(1, 'Degree is required'),
|
||||
branch: z.string().min(1, 'Branch is required'),
|
||||
year: z.string().min(1, 'Year is required'),
|
||||
skills: z.array(z.string()),
|
||||
linkedin: z.string(),
|
||||
github: z.string(),
|
||||
ssc: z.coerce.number(),
|
||||
hsc: z.coerce.number(),
|
||||
ssc: z.coerce.number().min(0).max(100),
|
||||
hsc: z.coerce.number().min(0).max(100),
|
||||
isDiploma: z.boolean(),
|
||||
sgpi: z.array(sgpiSchema),
|
||||
internships: z.array(internshipSchema).optional(),
|
||||
resume: z.array(resumeSchema).optional(),
|
||||
sgpi: z.array(sgpiSchema).length(8, 'Must provide grades for all 8 semesters'),
|
||||
internships: z.array(internshipSchema),
|
||||
resume: z.array(resumeSchema),
|
||||
});
|
||||
|
||||
export type StudentSignup = z.infer<typeof studentSignupSchema>;
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function AcademicDetailsStep({ form }: { form: any }) {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Degree *</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select your degree" />
|
||||
@@ -64,7 +64,7 @@ export default function AcademicDetailsStep({ form }: { form: any }) {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Year *</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Year of graduation" />
|
||||
@@ -89,7 +89,7 @@ export default function AcademicDetailsStep({ form }: { form: any }) {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Branch *</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select your branch" />
|
||||
@@ -119,7 +119,12 @@ export default function AcademicDetailsStep({ form }: { form: any }) {
|
||||
<FormItem>
|
||||
<FormLabel>SSC % *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="10th percentage" {...field} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="10th percentage"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -133,7 +138,12 @@ export default function AcademicDetailsStep({ form }: { form: any }) {
|
||||
<FormItem>
|
||||
<FormLabel>HSC % *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="12th percentage" {...field} />
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="12th percentage"
|
||||
{...field}
|
||||
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -146,7 +156,11 @@ export default function AcademicDetailsStep({ form }: { form: any }) {
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2">
|
||||
<FormControl>
|
||||
<input type="checkbox" checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="!m-0">Diploma Holder?</FormLabel>
|
||||
</FormItem>
|
||||
|
||||
@@ -23,10 +23,11 @@ export default function AdditionalDetailsStep({ form }: { form: any }) {
|
||||
name="linkedin"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>LinkedIn Profile *</FormLabel>
|
||||
<FormLabel>LinkedIn Profile</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="url" placeholder="https://linkedin.com/in/yourprofile" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Optional</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -37,10 +38,11 @@ export default function AdditionalDetailsStep({ form }: { form: any }) {
|
||||
name="github"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>GitHub Profile *</FormLabel>
|
||||
<FormLabel>GitHub Profile</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="url" placeholder="https://github.com/yourusername" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Optional</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -57,10 +59,20 @@ export default function AdditionalDetailsStep({ form }: { form: any }) {
|
||||
<Textarea
|
||||
placeholder="JavaScript, React, Node.js, Python"
|
||||
className="resize-none"
|
||||
value={field.value ? (Array.isArray(field.value) ? field.value.join(", ") : field.value) : ""}
|
||||
onChange={e => {
|
||||
value={
|
||||
field.value
|
||||
? Array.isArray(field.value)
|
||||
? field.value.join(', ')
|
||||
: field.value
|
||||
: ''
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
field.onChange(value.split(',').map(s => s.trim()).filter(Boolean));
|
||||
const skills = value
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
field.onChange(skills);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -12,6 +12,19 @@ import {
|
||||
import { Input } from '@workspace/ui/components/input';
|
||||
import { Textarea } from '@workspace/ui/components/textarea';
|
||||
import { Separator } from '@workspace/ui/components/separator';
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Calendar } from '@workspace/ui/components/calendar';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@workspace/ui/components/popover';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
|
||||
export default function PersonalDetailsStep({ form }: { form: any }) {
|
||||
return (
|
||||
@@ -76,6 +89,66 @@ export default function PersonalDetailsStep({ form }: { form: any }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="gender"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Gender *</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select your gender" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="male">Male</SelectItem>
|
||||
<SelectItem value="female">Female</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dob"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Date of Birth *</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'w-full pl-3 text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{field.value ? format(field.value, 'PPP') : <span>Pick a date</span>}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
disabled={(date) => date > new Date() || date < new Date('1900-01-01')}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
Reference in New Issue
Block a user