gohorsejobs/frontend/src/app/cadastro/candidato/page.tsx
2025-12-22 14:44:12 -03:00

598 lines
26 KiB
TypeScript

"use client";
import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import {
User,
Mail,
Lock,
Eye,
EyeOff,
Phone,
MapPin,
Calendar,
GraduationCap,
Briefcase,
ArrowLeft,
} from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { motion } from "framer-motion";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useTranslation } from "@/lib/i18n";
const createCandidateSchema = (t: (key: string, params?: Record<string, string | number>) => string) =>
z.object({
fullName: z.string().min(2, t("register.candidate.validation.fullName")),
email: z.string().email(t("register.candidate.validation.email")),
password: z.string().min(6, t("register.candidate.validation.password")),
confirmPassword: z.string(),
phone: z.string().min(10, t("register.candidate.validation.phone")),
birthDate: z.string().min(1, t("register.candidate.validation.birthDate")),
address: z.string().min(5, t("register.candidate.validation.address")),
city: z.string().min(2, t("register.candidate.validation.city")),
state: z.string().min(2, t("register.candidate.validation.state")),
zipCode: z.string().min(8, t("register.candidate.validation.zipCode")),
education: z.string().min(1, t("register.candidate.validation.education")),
experience: z.string().min(1, t("register.candidate.validation.experience")),
skills: z.string().optional(),
objective: z.string().optional(),
acceptTerms: z
.boolean()
.refine(val => val === true, t("register.candidate.validation.acceptTerms")),
acceptNewsletter: z.boolean().optional(),
}).refine(data => data.password === data.confirmPassword, {
message: t("register.candidate.validation.passwordMismatch"),
path: ["confirmPassword"],
});
type CandidateFormData = z.infer<ReturnType<typeof createCandidateSchema>>;
export default function CandidateRegisterPage() {
const router = useRouter();
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [currentStep, setCurrentStep] = useState(1);
const candidateSchema = useMemo(() => createCandidateSchema(t), [t]);
const {
register,
handleSubmit,
formState: { errors },
setValue,
watch,
} = useForm<CandidateFormData>({
resolver: zodResolver(candidateSchema),
});
const acceptTerms = watch("acceptTerms");
const acceptNewsletter = watch("acceptNewsletter");
const onSubmit = async (data: CandidateFormData) => {
setLoading(true);
try {
// Simular cadastro
await new Promise(resolve => setTimeout(resolve, 2000));
console.log("Dados do candidato:", data);
// Redirecionar para login após cadastro
router.push(`/login?message=${encodeURIComponent(t("register.candidate.success"))}`);
} catch (error) {
console.error("Erro no cadastro:", error);
} finally {
setLoading(false);
}
};
const nextStep = () => {
if (currentStep < 3) setCurrentStep(currentStep + 1);
};
const prevStep = () => {
if (currentStep > 1) setCurrentStep(currentStep - 1);
};
const stepVariants = {
hidden: { opacity: 0, x: 20 },
visible: { opacity: 1, x: 0 },
exit: { opacity: 0, x: -20 }
};
return (
<div className="min-h-screen bg-gradient-to-br from-background to-muted/20 flex">
{/* Left Panel - Informações */}
<div className="hidden lg:flex lg:flex-1 bg-gradient-to-br from-primary to-primary/80 p-8 flex-col justify-center items-center text-primary-foreground">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md text-center"
>
<div className="flex items-center justify-center gap-3 mb-8">
<Image src="/logohorse.png" alt="GoHorse Jobs" width={80} height={80} className="rounded-lg" />
</div>
<h1 className="text-4xl font-bold mb-4">
{t("register.candidate.hero.title")}
</h1>
<p className="text-lg opacity-90 leading-relaxed mb-6">
{t("register.candidate.hero.subtitle")}
</p>
<div className="space-y-4 text-left">
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>{t("register.candidate.hero.bullets.jobs")}</span>
</div>
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>{t("register.candidate.hero.bullets.fastApplications")}</span>
</div>
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>{t("register.candidate.hero.bullets.profile")}</span>
</div>
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>{t("register.candidate.hero.bullets.notifications")}</span>
</div>
</div>
</motion.div>
</div>
{/* Right Panel - Formulário */}
<div className="flex-1 p-8 flex flex-col justify-center">
<div className="w-full max-w-md mx-auto">
{/* Header */}
<div className="mb-6">
<Link
href="/login"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground mb-4 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
{t("register.candidate.actions.backToLogin")}
</Link>
<h2 className="text-2xl font-bold text-foreground mb-2">
{t("register.candidate.title")}
</h2>
<p className="text-muted-foreground">
{t("register.candidate.subtitle")}
</p>
</div>
{/* Progress Indicator */}
<div className="mb-8">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">
{t("register.candidate.progress.step", { current: currentStep, total: 3 })}
</span>
<span className="text-sm text-muted-foreground">
{currentStep === 1 && t("register.candidate.steps.personal")}
{currentStep === 2 && t("register.candidate.steps.address")}
{currentStep === 3 && t("register.candidate.steps.professional")}
</span>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-primary h-2 rounded-full transition-all duration-300"
style={{ width: `${(currentStep / 3) * 100}%` }}
/>
</div>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{/* Step 1: Dados Pessoais */}
{currentStep === 1 && (
<motion.div
key="step1"
variants={stepVariants}
initial="hidden"
animate="visible"
exit="exit"
className="space-y-4"
>
<div className="space-y-2">
<Label htmlFor="fullName">{t("register.candidate.fields.fullName")}</Label>
<div className="relative">
<User className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="fullName"
type="text"
placeholder={t("register.candidate.placeholders.fullName")}
className="pl-10"
{...register("fullName")}
/>
</div>
{errors.fullName && (
<span className="text-sm text-destructive">{errors.fullName.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">{t("register.candidate.fields.email")}</Label>
<div className="relative">
<Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="email"
type="email"
placeholder={t("register.candidate.placeholders.email")}
className="pl-10"
{...register("email")}
/>
</div>
{errors.email && (
<span className="text-sm text-destructive">{errors.email.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">{t("register.candidate.fields.password")}</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder={t("register.candidate.placeholders.password")}
className="pl-10 pr-10"
{...register("password")}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
{errors.password && (
<span className="text-sm text-destructive">{errors.password.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">{t("register.candidate.fields.confirmPassword")}</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="confirmPassword"
type={showConfirmPassword ? "text" : "password"}
placeholder={t("register.candidate.placeholders.confirmPassword")}
className="pl-10 pr-10"
{...register("confirmPassword")}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
>
{showConfirmPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
{errors.confirmPassword && (
<span className="text-sm text-destructive">{errors.confirmPassword.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="birthDate">{t("register.candidate.fields.birthDate")}</Label>
<div className="relative">
<Calendar className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="birthDate"
type="date"
className="pl-10"
{...register("birthDate")}
/>
</div>
{errors.birthDate && (
<span className="text-sm text-destructive">{errors.birthDate.message}</span>
)}
</div>
<Button type="button" onClick={nextStep} className="w-full">
{t("register.candidate.actions.next")}
</Button>
</motion.div>
)}
{/* Step 2: Endereço e Contato */}
{currentStep === 2 && (
<motion.div
key="step2"
variants={stepVariants}
initial="hidden"
animate="visible"
exit="exit"
className="space-y-4"
>
<div className="space-y-2">
<Label htmlFor="phone">{t("register.candidate.fields.phone")}</Label>
<div className="relative">
<Phone className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="phone"
type="tel"
placeholder={t("register.candidate.placeholders.phone")}
className="pl-10"
{...register("phone")}
/>
</div>
{errors.phone && (
<span className="text-sm text-destructive">{errors.phone.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="address">{t("register.candidate.fields.address")}</Label>
<div className="relative">
<MapPin className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="address"
type="text"
placeholder={t("register.candidate.placeholders.address")}
className="pl-10"
{...register("address")}
/>
</div>
{errors.address && (
<span className="text-sm text-destructive">{errors.address.message}</span>
)}
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="city">{t("register.candidate.fields.city")}</Label>
<Input
id="city"
type="text"
placeholder={t("register.candidate.placeholders.city")}
{...register("city")}
/>
{errors.city && (
<span className="text-sm text-destructive">{errors.city.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="state">{t("register.candidate.fields.state")}</Label>
<Select onValueChange={(value) => setValue("state", value)}>
<SelectTrigger>
<SelectValue placeholder={t("register.candidate.placeholders.state")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="AC">Acre</SelectItem>
<SelectItem value="AL">Alagoas</SelectItem>
<SelectItem value="AP">Amapá</SelectItem>
<SelectItem value="AM">Amazonas</SelectItem>
<SelectItem value="BA">Bahia</SelectItem>
<SelectItem value="CE">Ceará</SelectItem>
<SelectItem value="DF">Distrito Federal</SelectItem>
<SelectItem value="ES">Espírito Santo</SelectItem>
<SelectItem value="GO">Goiás</SelectItem>
<SelectItem value="MA">Maranhão</SelectItem>
<SelectItem value="MT">Mato Grosso</SelectItem>
<SelectItem value="MS">Mato Grosso do Sul</SelectItem>
<SelectItem value="MG">Minas Gerais</SelectItem>
<SelectItem value="PA">Pará</SelectItem>
<SelectItem value="PB">Paraíba</SelectItem>
<SelectItem value="PR">Paraná</SelectItem>
<SelectItem value="PE">Pernambuco</SelectItem>
<SelectItem value="PI">Piauí</SelectItem>
<SelectItem value="RJ">Rio de Janeiro</SelectItem>
<SelectItem value="RN">Rio Grande do Norte</SelectItem>
<SelectItem value="RS">Rio Grande do Sul</SelectItem>
<SelectItem value="RO">Rondônia</SelectItem>
<SelectItem value="RR">Roraima</SelectItem>
<SelectItem value="SC">Santa Catarina</SelectItem>
<SelectItem value="SP">São Paulo</SelectItem>
<SelectItem value="SE">Sergipe</SelectItem>
<SelectItem value="TO">Tocantins</SelectItem>
</SelectContent>
</Select>
{errors.state && (
<span className="text-sm text-destructive">{errors.state.message}</span>
)}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="zipCode">{t("register.candidate.fields.zipCode")}</Label>
<Input
id="zipCode"
type="text"
placeholder={t("register.candidate.placeholders.zipCode")}
{...register("zipCode")}
/>
{errors.zipCode && (
<span className="text-sm text-destructive">{errors.zipCode.message}</span>
)}
</div>
<div className="flex gap-4">
<Button type="button" variant="outline" onClick={prevStep} className="flex-1">
{t("register.candidate.actions.back")}
</Button>
<Button type="button" onClick={nextStep} className="flex-1">
{t("register.candidate.actions.next")}
</Button>
</div>
</motion.div>
)}
{/* Step 3: Perfil Profissional */}
{currentStep === 3 && (
<motion.div
key="step3"
variants={stepVariants}
initial="hidden"
animate="visible"
exit="exit"
className="space-y-4"
>
<div className="space-y-2">
<Label htmlFor="education">{t("register.candidate.fields.education")}</Label>
<Select onValueChange={(value) => setValue("education", value)}>
<SelectTrigger>
<SelectValue placeholder={t("register.candidate.placeholders.education")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="fundamental">{t("register.candidate.education.fundamental")}</SelectItem>
<SelectItem value="medio">{t("register.candidate.education.highSchool")}</SelectItem>
<SelectItem value="tecnico">{t("register.candidate.education.technical")}</SelectItem>
<SelectItem value="superior">{t("register.candidate.education.college")}</SelectItem>
<SelectItem value="pos">{t("register.candidate.education.postgrad")}</SelectItem>
<SelectItem value="mestrado">{t("register.candidate.education.masters")}</SelectItem>
<SelectItem value="doutorado">{t("register.candidate.education.phd")}</SelectItem>
</SelectContent>
</Select>
{errors.education && (
<span className="text-sm text-destructive">{errors.education.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="experience">{t("register.candidate.fields.experience")}</Label>
<Select onValueChange={(value) => setValue("experience", value)}>
<SelectTrigger>
<SelectValue placeholder={t("register.candidate.placeholders.experience")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="sem-experiencia">{t("register.candidate.experience.none")}</SelectItem>
<SelectItem value="ate-1-ano">{t("register.candidate.experience.upToOne")}</SelectItem>
<SelectItem value="1-2-anos">{t("register.candidate.experience.oneToTwo")}</SelectItem>
<SelectItem value="2-5-anos">{t("register.candidate.experience.twoToFive")}</SelectItem>
<SelectItem value="5-10-anos">{t("register.candidate.experience.fiveToTen")}</SelectItem>
<SelectItem value="mais-10-anos">{t("register.candidate.experience.moreThanTen")}</SelectItem>
</SelectContent>
</Select>
{errors.experience && (
<span className="text-sm text-destructive">{errors.experience.message}</span>
)}
</div>
<div className="space-y-2">
<Label htmlFor="skills">{t("register.candidate.fields.skills")}</Label>
<Textarea
id="skills"
placeholder={t("register.candidate.placeholders.skills")}
className="min-h-[80px]"
{...register("skills")}
/>
</div>
<div className="space-y-2">
<Label htmlFor="objective">{t("register.candidate.fields.objective")}</Label>
<Textarea
id="objective"
placeholder={t("register.candidate.placeholders.objective")}
className="min-h-[80px]"
{...register("objective")}
/>
</div>
<div className="space-y-4">
<div className="flex items-start space-x-2">
<Checkbox
id="acceptTerms"
checked={acceptTerms}
onCheckedChange={(checked) => setValue("acceptTerms", checked as boolean)}
/>
<div className="grid gap-1.5 leading-none">
<label
htmlFor="acceptTerms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{t("register.candidate.acceptTerms.prefix")}{" "}
<Link href="/termos" className="text-primary hover:underline">
{t("register.candidate.acceptTerms.terms")}
</Link>{" "}
{t("register.candidate.acceptTerms.and")}{" "}
<Link href="/privacidade" className="text-primary hover:underline">
{t("register.candidate.acceptTerms.privacy")}
</Link>
</label>
</div>
</div>
{errors.acceptTerms && (
<span className="text-sm text-destructive">{errors.acceptTerms.message}</span>
)}
<div className="flex items-start space-x-2">
<Checkbox
id="acceptNewsletter"
checked={acceptNewsletter}
onCheckedChange={(checked) => setValue("acceptNewsletter", checked as boolean)}
/>
<div className="grid gap-1.5 leading-none">
<label
htmlFor="acceptNewsletter"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{t("register.candidate.acceptNewsletter")}
</label>
</div>
</div>
</div>
<div className="flex gap-4">
<Button type="button" variant="outline" onClick={prevStep} className="flex-1">
{t("register.candidate.actions.back")}
</Button>
<Button type="submit" disabled={loading} className="flex-1">
{loading ? t("register.candidate.actions.creating") : t("register.candidate.actions.submit")}
</Button>
</div>
</motion.div>
)}
</form>
<div className="mt-6 text-center">
<p className="text-sm text-muted-foreground">
{t("register.candidate.footer.prompt")}{" "}
<Link href="/login" className="text-primary hover:underline font-medium">
{t("register.candidate.footer.login")}
</Link>
</p>
</div>
</div>
</div>
</div>
);
}