import React, { useState, useEffect } from "react"; import { User, Mail, Phone, MapPin, DollarSign, Briefcase, Camera, FileText, Check, CreditCard, Save, ChevronRight, Loader2, X } from "lucide-react"; import { Navbar } from "../components/Navbar"; import { Button } from "../components/Button"; import { useAuth } from "../contexts/AuthContext"; import { getFunctions, createProfessional, updateProfessional, updateUserProfile } from "../services/apiService"; import { toast } from "react-hot-toast"; import { formatCPFCNPJ, formatPhone } from "../utils/masks"; // --- Helper Components --- interface InputFieldProps { label: string; icon?: any; value?: any; className?: string; onChange?: (e: any) => void; type?: string; placeholder?: string; maxLength?: number; required?: boolean; name?: string; disabled?: boolean; readOnly?: boolean; onBlur?: (e: any) => void; } const InputField = ({ label, icon: Icon, className, ...props }: InputFieldProps) => (
{Icon && (
)}
); const Toggle = ({ label, checked, onChange }: { label: string, checked: boolean, onChange: (val: boolean) => void }) => ( ); const SidebarItem = ({ id, label, icon: Icon, active, onClick }: any) => ( ); // --- Main Component --- export const ProfilePage: React.FC = () => { const { user, token } = useAuth(); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isUploading, setIsUploading] = useState(false); const [activeTab, setActiveTab] = useState("personal"); const [functions, setFunctions] = useState([]); const [isNewProfile, setIsNewProfile] = useState(false); const [showInitModal, setShowInitModal] = useState(false); // Form State const [formData, setFormData] = useState({ id: "", nome: "", email: "", whatsapp: "", cpf_cnpj_titular: "", cep: "", rua: "", numero: "", bairro: "", endereco: "", cidade: "", uf: "", banco: "", agencia: "", conta: "", conta_pix: "", carro_disponivel: false, tem_estudio: false, qtd_estudio: 0, tipo_cartao: "", equipamentos: "", extra_por_equipamento: false, funcoes_ids: [], avatar_url: "" }); // Fetch Data useEffect(() => { const fetchData = async () => { setIsLoading(true); try { if (!token) return; const funcsRes = await getFunctions(); if (funcsRes.data) setFunctions(funcsRes.data); if (user?.role === "EVENT_OWNER") { // Clients don't have professional profile. Populate from User. // Ensure user object has these fields (mapped in AuthContext) setFormData({ ...formData, nome: user.name || "", email: user.email || "", whatsapp: user.phone || "", cpf_cnpj_titular: user.cpf_cnpj || "", cep: user.cep || "", endereco: user.endereco || "", numero: user.numero || "", complemento: user.complemento || "", bairro: user.bairro || "", cidade: user.cidade || "", uf: user.estado || "", }); setIsLoading(false); return; } // Try to fetch existing profile const response = await fetch(`${import.meta.env.VITE_API_URL || "http://localhost:8080"}/api/profissionais/me`, { headers: { Authorization: `Bearer ${token}` } }); if (!response.ok) { if (response.status === 404) { // Profile not found -> Initialize New Profile setIsNewProfile(true); setShowInitModal(true); // Pre-fill from User Authentication setFormData((prev: any) => ({ ...prev, nome: user?.name || "", email: user?.email || "", })); return; } throw new Error("Falha ao carregar perfil"); } const data = await response.json(); setFormData({ ...data, carro_disponivel: data.carro_disponivel || false, tem_estudio: data.tem_estudio || false, extra_por_equipamento: data.extra_por_equipamento || false, qtd_estudio: data.qtd_estudio || 0, conta: data.conta || "", // Populate email if missing from backend, fallback to user email email: data.email || user?.email || "", funcoes_ids: data.functions ? data.functions.map((f: any) => f.id) : [] }); } catch (error) { console.error(error); toast.error("Erro ao carregar dados do perfil."); } finally { setIsLoading(false); } }; fetchData(); }, [token, user]); const handleChange = (field: string, value: any) => { setFormData((prev: any) => ({ ...prev, [field]: value })); }; const handleFunctionToggle = (funcId: string) => { setFormData((prev: any) => { const currentIds = prev.funcoes_ids || []; return currentIds.includes(funcId) ? { ...prev, funcoes_ids: currentIds.filter((id: string) => id !== funcId) } : { ...prev, funcoes_ids: [...currentIds, funcId] }; }); }; // Avatar Upload const handleAvatarChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (file.size > 2 * 1024 * 1024) { toast.error("A imagem deve ter no máximo 2MB."); return; } setIsUploading(true); try { const filename = `avatar_${user?.id}_${Date.now()}_${file.name}`; const resUrl = await fetch(`${import.meta.env.VITE_API_URL || "http://localhost:8080"}/auth/upload-url`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ filename, content_type: file.type }) }); if (!resUrl.ok) throw new Error("Falha ao obter URL de upload"); const { upload_url, public_url } = await resUrl.json(); const uploadRes = await fetch(upload_url, { method: 'PUT', headers: { 'Content-Type': file.type }, body: file }); if (!uploadRes.ok) throw new Error("Falha ao enviar imagem"); setFormData((prev: any) => ({ ...prev, avatar_url: public_url })); toast.success("Imagem enviada! Salve o perfil para confirmar."); } catch (error) { console.error(error); toast.error("Erro ao enviar imagem."); } finally { setIsUploading(false); } }; const handleCepBlur = async (e: any) => { const cep = e.target.value?.replace(/\D/g, ''); if (cep?.length !== 8) return; const toastId = toast.loading("Buscando endereço..."); try { const res = await fetch(`https://viacep.com.br/ws/${cep}/json/`); const data = await res.json(); if (data.erro) { toast.error("CEP não encontrado.", { id: toastId }); return; } const formattedAddress = `${data.logradouro}, ${data.bairro}`; setFormData((prev: any) => ({ ...prev, endereco: formattedAddress, cidade: data.localidade, uf: data.uf, rua: data.logradouro, bairro: data.bairro })); toast.success("Endereço encontrado!", { id: toastId }); } catch (error) { console.error(error); toast.error("Erro ao buscar CEP.", { id: toastId }); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSaving(true); try { if (!token) throw new Error("Usuário não autenticado"); if (user?.role === "EVENT_OWNER") { const clientPayload = { name: formData.nome, phone: formData.whatsapp, cpf_cnpj: formData.cpf_cnpj_titular, cep: formData.cep, endereco: formData.endereco, numero: formData.numero, complemento: formData.complemento, bairro: formData.bairro, cidade: formData.cidade, estado: formData.uf }; const res = await updateUserProfile(clientPayload, token); if (res.error) throw new Error(res.error); toast.success("Perfil atualizado com sucesso!"); setIsSaving(false); return; } // Payload preparation // For create/update, we need `funcao_profissional_id` (single) for backward compatibility optionally // But we primarily use `funcoes_ids`. // If `funcoes_ids` is empty, user needs to select at least one? // For now, let's just pick the first one as "primary" if backend requires it. // Backend create DTO has `funcao_profissional_id`. const payload = { ...formData, // Backend compatibility: if funcao_profissional_id is empty/string, try to set from array funcao_profissional_id: formData.funcoes_ids && formData.funcoes_ids.length > 0 ? formData.funcoes_ids[0] : formData.funcao_profissional_id }; if (!payload.funcao_profissional_id && isNewProfile && formData.funcoes_ids.length === 0) { // If no functions selected for new profile, it might fail if backend requires it. // Let's allow it for now, user might add later. // Or toast warning? } let res; if (isNewProfile) { // CREATE res = await createProfessional(payload, token); } else { // UPDATE res = await updateProfessional(formData.id, payload, token); } if (res.error) throw new Error(res.error); toast.success(isNewProfile ? "Perfil criado com sucesso!" : "Perfil atualizado com sucesso!"); // If created, switch to edit mode if (isNewProfile && res.data) { setIsNewProfile(false); setFormData((prev: any) => ({ ...prev, id: res.data.id })); } } catch (error: any) { console.error(error); toast.error(error.message || "Erro ao salvar alterações"); } finally { setIsSaving(false); } }; if (isLoading) { return (
); } return (
window.location.href = `/${page}`} currentPage="profile" />
{/* Header */}

Meu Perfil

Gerencie suas informações de cadastro.

{/* Sidebar (Desktop) */}
{/* Mobile Tabs */}
{['personal', 'address', ...(user?.role !== "EVENT_OWNER" ? ['bank', 'equipment'] : []) ].map(id => ( ))}
{/* Content Area */}

{activeTab === "personal" && "Informações Pessoais"} {activeTab === "address" && "Endereço e Contato"} {activeTab === "bank" && "Dados Bancários"} {activeTab === "equipment" && "Perfil Profissional"}

{/* --- PERSONAL --- */} {activeTab === "personal" && (
{/* Avatar Upload */}
{formData.avatar_url ? ( Profile ) : ( )} {isUploading && (
)}

Foto de Perfil

Recomendado: 400x400px. Max: 2MB.

handleChange("nome", e.target.value)} required /> handleChange("cpf_cnpj_titular", formatCPFCNPJ(e.target.value))} maxLength={18} />
)} {/* --- ADDRESS --- */} {activeTab === "address" && (
handleChange("email", e.target.value)} required /> handleChange("whatsapp", formatPhone(e.target.value))} maxLength={15} />

Endereço (Busca por CEP)

handleChange("endereco", e.target.value)} readOnly />
{ const num = e.target.value; setFormData((prev: any) => ({ ...prev, numero: num, // Update main address string: Rua X, - Bairro // Only if we have parts endereco: prev.rua ? `${prev.rua}, ${num} - ${prev.bairro || ''}` : prev.endereco })); }} />
)} {/* --- BANK --- */} {activeTab === "bank" && (
handleChange("banco", e.target.value)} /> handleChange("agencia", e.target.value)} /> handleChange("conta", e.target.value)} placeholder="Número da Conta" /> handleChange("conta_pix", e.target.value)} />
)} {/* --- EQUIPMENT --- */} {activeTab === "equipment" && (
{functions.map((func) => ( ))}
handleChange("carro_disponivel", checked)} /> {/* REMOVED: Cobra extra por equipamento */} handleChange("tem_estudio", checked)} /> {formData.tem_estudio && ( handleChange("qtd_estudio", parseInt(e.target.value))} /> )}
handleChange("tipo_cartao", e.target.value)} placeholder="Ex: SD, CF Express..." />