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 } from "../services/apiService";
import { toast } from "react-hot-toast";
// --- 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) => (
);
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);
// 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");
// 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 */}
{/* Init Profile Modal */}
{showInitModal && (
Complete seu Cadastro
Seus dados ainda não estão completos.
Preencha as informações abaixo para começar.
)}
);
};