gohorsejobs/frontend/src/app/dashboard/candidato/perfil/page.tsx
2026-02-09 14:11:05 +00:00

395 lines
18 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useForm, useFieldArray } from "react-hook-form";
import {
Loader2,
MapPin,
Plus,
Save,
Trash2,
Upload,
User as UserIcon,
Briefcase,
GraduationCap
} from "lucide-react";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
CardFooter
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Navbar } from "@/components/navbar";
import { Footer } from "@/components/footer";
import { useToast } from "@/hooks/use-toast";
// We'll update api.ts to include usersApi.updateMe later
// Mocking for now or assuming it exists
import { storageApi, usersApi } from "@/lib/api";
type Experience = {
company: string;
position: string;
description: string;
startDate: string;
endDate: string;
};
type Education = {
institution: string;
degree: string;
field: string;
startDate: string;
endDate: string;
};
type ProfileFormValues = {
fullName: string;
email: string;
phone: string;
whatsapp: string;
bio: string;
skills: string; // Comma separated for input
experience: Experience[];
education: Education[];
};
export default function ProfilePage() {
const { toast } = useToast();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [user, setUser] = useState<any>(null);
const [profilePic, setProfilePic] = useState<string | null>(null);
const { register, control, handleSubmit, reset, setValue, watch } = useForm<ProfileFormValues>({
defaultValues: {
experience: [],
education: []
}
});
const { fields: expFields, append: appendExp, remove: removeExp } = useFieldArray({
control,
name: "experience"
});
const { fields: eduFields, append: appendEdu, remove: removeEdu } = useFieldArray({
control,
name: "education"
});
useEffect(() => {
fetchProfile();
}, []);
const fetchProfile = async () => {
try {
setLoading(true);
// Assuming getMe exists, if not we create it
// const userData = await usersApi.getMe();
// For now, let's assume valid response structure based on our backend implementation
// But api.ts might not have getMe yet.
// To be safe, we might implement getMe in api.ts first?
// Or we check what is available.
// Current 'authApi.me' might be available?
// Let's assume we can fetch user.
// Fallback mock for development if backend not ready
// const userData = mockUser;
const userData = await usersApi.getMe(); // We will ensure this exists in api.ts
setUser(userData);
setProfilePic(userData.profilePictureUrl || null);
reset({
fullName: userData.name,
email: userData.email,
// phone: userData.phone, // Phone might not be in Core User yet? We didn't add it to Entity.
bio: userData.bio || "",
skills: userData.skills?.join(", ") || "",
experience: userData.experience || [],
education: userData.education || []
});
} catch (error) {
console.error(error);
toast({
title: "Erro ao carregar perfil",
description: "Não foi possível carregar seus dados.",
variant: "destructive"
});
} finally {
setLoading(false);
}
};
const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
try {
toast({ title: "Enviando foto..." });
toast({ title: "Enviando foto..." });
// 1. Upload via Proxy (avoids CORS)
const { publicUrl } = await storageApi.uploadFile(file, "avatars");
// 2. Update state
setProfilePic(publicUrl);
toast({ title: "Foto enviada!", description: "Não esqueça de salvar o perfil." });
} catch (err) {
console.error(err);
toast({ title: "Erro no upload", variant: "destructive" });
}
};
const onSubmit = async (data: ProfileFormValues) => {
try {
setSaving(true);
const skillsArray = data.skills.split(",").map(s => s.trim()).filter(Boolean);
await usersApi.updateMe({
fullName: data.fullName,
bio: data.bio,
profilePictureUrl: profilePic || undefined,
skills: skillsArray,
experience: data.experience,
education: data.education
});
toast({ title: "Perfil atualizado com sucesso!" });
} catch (error) {
console.error(error);
toast({ title: "Erro ao atualizar", variant: "destructive" });
} finally {
setSaving(false);
}
};
if (loading) {
return <div className="flex h-screen items-center justify-center"><Loader2 className="animate-spin" /></div>;
}
return (
<div className="min-h-screen bg-muted/40 pb-10">
<Navbar />
<div className="container py-10">
<div className="mb-8">
<h1 className="text-3xl font-bold">Meu Perfil</h1>
<p className="text-muted-foreground">Gerencie suas informações profissionais e pessoais.</p>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
{/* Basic Info & Photo */}
<Card>
<CardHeader>
<CardTitle>Informações Básicas</CardTitle>
<CardDescription>Sua identidade na plataforma.</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex flex-col md:flex-row gap-6 items-center md:items-start">
<div className="flex flex-col items-center gap-3">
<Avatar className="w-24 h-24 border-2 border-primary/20">
<AvatarImage src={profilePic || ""} />
<AvatarFallback><UserIcon className="w-10 h-10" /></AvatarFallback>
</Avatar>
<div className="relative">
<input
type="file"
id="avatar-upload"
className="hidden"
accept="image/*"
onChange={handleAvatarUpload}
/>
<Button type="button" variant="outline" size="sm" onClick={() => document.getElementById('avatar-upload')?.click()}>
<Upload className="w-3 h-3 mr-2" /> Alterar Foto
</Button>
</div>
</div>
<div className="grid gap-4 flex-1 w-full">
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Nome Completo</Label>
<Input {...register("fullName")} />
</div>
<div className="space-y-2">
<Label>Email</Label>
<Input {...register("email")} disabled className="bg-muted" />
</div>
</div>
<div className="space-y-2">
<Label>Bio / Resumo Profissional</Label>
<Textarea
{...register("bio")}
placeholder="Conte um pouco sobre você..."
className="min-h-[100px]"
/>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Skills */}
<Card>
<CardHeader>
<CardTitle>Competências</CardTitle>
<CardDescription>Liste suas principais habilidades técnicas e comportamentais.</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label>Skills (separadas por vírgula)</Label>
<Input
{...register("skills")}
placeholder="Ex: Javscript, Go, Liderança, Scrum"
/>
<p className="text-xs text-muted-foreground">
Estas tags ajudarão recrutadores a encontrar seu perfil.
</p>
</div>
</CardContent>
</Card>
{/* Experience */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Experiência Profissional</CardTitle>
<CardDescription>Seu histórico de trabalho.</CardDescription>
</div>
<Button type="button" variant="outline" size="sm" onClick={() => appendExp({ company: "", position: "", description: "", startDate: "", endDate: "" })}>
<Plus className="w-4 h-4 mr-2" /> Adicionar
</Button>
</CardHeader>
<CardContent className="space-y-6">
{expFields.map((field, index) => (
<div key={field.id} className="relative grid gap-4 p-4 border rounded-md">
<Button
type="button"
variant="ghost"
size="icon"
className="absolute top-2 right-2 text-destructive hover:text-destructive/80"
onClick={() => removeExp(index)}
>
<Trash2 className="w-4 h-4" />
</Button>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Empresa</Label>
<Input {...register(`experience.${index}.company`)} placeholder="Ex: Google" />
</div>
<div className="space-y-2">
<Label>Cargo</Label>
<Input {...register(`experience.${index}.position`)} placeholder="Ex: Engenheiro de Software" />
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Início</Label>
<Input type="month" {...register(`experience.${index}.startDate`)} />
</div>
<div className="space-y-2">
<Label>Fim</Label>
<Input type="month" {...register(`experience.${index}.endDate`)} />
</div>
</div>
<div className="space-y-2">
<Label>Descrição</Label>
<Textarea {...register(`experience.${index}.description`)} placeholder="Descreva suas responsabilidades e conquistas..." />
</div>
</div>
))}
{expFields.length === 0 && (
<div className="text-center py-8 text-muted-foreground border-2 border-dashed rounded-md">
Nenhuma experiência adicionada.
</div>
)}
</CardContent>
</Card>
{/* Education */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Formação Acadêmica</CardTitle>
<CardDescription>Escolaridade e cursos.</CardDescription>
</div>
<Button type="button" variant="outline" size="sm" onClick={() => appendEdu({ institution: "", degree: "", field: "", startDate: "", endDate: "" })}>
<Plus className="w-4 h-4 mr-2" /> Adicionar
</Button>
</CardHeader>
<CardContent className="space-y-6">
{eduFields.map((field, index) => (
<div key={field.id} className="relative grid gap-4 p-4 border rounded-md">
<Button
type="button"
variant="ghost"
size="icon"
className="absolute top-2 right-2 text-destructive hover:text-destructive/80"
onClick={() => removeEdu(index)}
>
<Trash2 className="w-4 h-4" />
</Button>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Instituição</Label>
<Input {...register(`education.${index}.institution`)} placeholder="Ex: USP" />
</div>
<div className="space-y-2">
<Label>Grau / Nível</Label>
<Input {...register(`education.${index}.degree`)} placeholder="Ex: Bacharelado" />
</div>
</div>
<div className="space-y-2">
<Label>Curso / Área de Estudo</Label>
<Input {...register(`education.${index}.field`)} placeholder="Ex: Ciência da Computação" />
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Início</Label>
<Input type="month" {...register(`education.${index}.startDate`)} />
</div>
<div className="space-y-2">
<Label>Fim</Label>
<Input type="month" {...register(`education.${index}.endDate`)} />
</div>
</div>
</div>
))}
{eduFields.length === 0 && (
<div className="text-center py-8 text-muted-foreground border-2 border-dashed rounded-md">
Nenhuma formação adicionada.
</div>
)}
</CardContent>
</Card>
<div className="flex justify-end gap-4 sticky bottom-4 z-10 bg-background/80 backdrop-blur-sm p-4 rounded-lg border shadow-lg">
<Button type="button" variant="outline" onClick={() => reset()}>Descartar Alterações</Button>
<Button type="submit" disabled={saving}>
{saving && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}
Salvar Alterações
</Button>
</div>
</form>
</div>
<Footer />
</div>
);
}