439 lines
17 KiB
TypeScript
439 lines
17 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
|
|
import { useAuth } from "../../contexts/AuthContext";
|
|
import api from "../../services/api";
|
|
import { Usuario } from "../../types/usuario";
|
|
import { UF_BRASIL } from "../../utils/constants/estados";
|
|
|
|
export function PerfilPage() {
|
|
const { user } = useAuth();
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [formData, setFormData] = useState<Partial<Usuario>>(user || {});
|
|
const [error, setError] = useState("");
|
|
const [success, setSuccess] = useState("");
|
|
const [senhaForm, setSenhaForm] = useState({
|
|
senhaAtual: "",
|
|
novaSenha: "",
|
|
confirmarSenha: "",
|
|
});
|
|
const [senhaError, setSenhaError] = useState("");
|
|
const [senhaSuccess, setSenhaSuccess] = useState("");
|
|
const [loadingSenha, setLoadingSenha] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const loadProfile = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await api.get(`/usuarios/${user?.id}`);
|
|
setFormData(response.data);
|
|
setError("");
|
|
} catch (error) {
|
|
console.error("Erro ao carregar perfil:", error);
|
|
setError("Erro ao carregar perfil");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (user?.id) {
|
|
loadProfile();
|
|
}
|
|
}, [user]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setError("");
|
|
setSuccess("");
|
|
|
|
try {
|
|
await api.put(`/usuarios/${user?.id}`, formData);
|
|
setSuccess("Perfil atualizado com sucesso!");
|
|
setIsEditing(false);
|
|
} catch (error) {
|
|
setError("Erro ao atualizar perfil");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleChange = (field: string, value: string) => {
|
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const handleSenhaSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (senhaForm.novaSenha !== senhaForm.confirmarSenha) {
|
|
setSenhaError("As senhas não coincidem");
|
|
return;
|
|
}
|
|
|
|
setLoadingSenha(true);
|
|
setSenhaError("");
|
|
setSenhaSuccess("");
|
|
|
|
try {
|
|
await api.post(`/usuarios/${user?.id}/alterar-senha`, {
|
|
senhaAtual: senhaForm.senhaAtual,
|
|
novaSenha: senhaForm.novaSenha,
|
|
});
|
|
setSenhaSuccess("Senha alterada com sucesso!");
|
|
setSenhaForm({ senhaAtual: "", novaSenha: "", confirmarSenha: "" });
|
|
} catch (error) {
|
|
setSenhaError("Erro ao alterar senha. Verifique sua senha atual.");
|
|
} finally {
|
|
setLoadingSenha(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-5xl mx-auto space-y-6">
|
|
{loading ? (
|
|
<div className="flex justify-center items-center h-64">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
) : (
|
|
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
|
{/* Cabeçalho */}
|
|
<div className="bg-gradient-to-r from-blue-600 to-blue-800 px-8 py-6">
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex items-center space-x-4">
|
|
<div className="h-20 w-20 rounded-full bg-white/20 flex items-center justify-center text-white text-2xl font-bold">
|
|
{formData.nome?.charAt(0)}
|
|
</div>
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">
|
|
{formData.nome}
|
|
</h1>
|
|
<p className="text-blue-100">{formData.email}</p>
|
|
</div>
|
|
</div>
|
|
{!isEditing ? (
|
|
<button
|
|
onClick={() => setIsEditing(true)}
|
|
className="bg-white text-blue-600 px-6 py-2 rounded-lg hover:bg-blue-50 transition-colors duration-200 font-medium shadow-sm"
|
|
>
|
|
Editar Perfil
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Alertas */}
|
|
{error && (
|
|
<div className="mx-8 mt-6 p-4 bg-red-50 border-l-4 border-red-500 text-red-700 rounded">
|
|
<div className="flex items-center">
|
|
<svg
|
|
className="h-5 w-5 mr-2"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
{error}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{success && (
|
|
<div className="mx-8 mt-6 p-4 bg-green-50 border-l-4 border-green-500 text-green-700 rounded">
|
|
<div className="flex items-center">
|
|
<svg
|
|
className="h-5 w-5 mr-2"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
{success}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Formulário */}
|
|
<form onSubmit={handleSubmit} className="p-8">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
{/* Dados Pessoais */}
|
|
<div className="space-y-6">
|
|
<div className="border-b border-gray-200 pb-4">
|
|
<h2 className="text-xl font-semibold text-gray-800 mb-6">
|
|
Dados Pessoais
|
|
</h2>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Nome Completo
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.nome || ""}
|
|
onChange={(e) => handleChange("nome", e.target.value)}
|
|
disabled={!isEditing}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:text-gray-500 transition-colors duration-200"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={formData.email || ""}
|
|
onChange={(e) => handleChange("email", e.target.value)}
|
|
disabled={!isEditing}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:text-gray-500 transition-colors duration-200"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
CPF
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.cpf || ""}
|
|
disabled={true}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 bg-gray-50 text-gray-500"
|
|
/>
|
|
<p className="mt-1 text-sm text-gray-500">
|
|
O CPF não pode ser alterado
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
RG
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.rg || ""}
|
|
onChange={(e) => handleChange("rg", e.target.value)}
|
|
disabled={!isEditing}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:text-gray-500 transition-colors duration-200"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Dados Profissionais */}
|
|
<div className="space-y-6">
|
|
<div className="border-b border-gray-200 pb-4">
|
|
<h2 className="text-xl font-semibold text-gray-800 mb-6">
|
|
Dados Profissionais
|
|
</h2>
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
OAB
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.oab || ""}
|
|
onChange={(e) => handleChange("oab", e.target.value)}
|
|
disabled={!isEditing}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:text-gray-500 transition-colors duration-200"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
UF
|
|
</label>
|
|
<select
|
|
value={formData.oabUF || ""}
|
|
onChange={(e) =>
|
|
handleChange("oabUF", e.target.value)
|
|
}
|
|
disabled={!isEditing}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:text-gray-500 transition-colors duration-200"
|
|
>
|
|
<option value="">Selecione</option>
|
|
{UF_BRASIL.map((uf) => (
|
|
<option key={uf.value} value={uf.value}>
|
|
{uf.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Telefone
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
value={formData.telefone || ""}
|
|
onChange={(e) =>
|
|
handleChange("telefone", e.target.value)
|
|
}
|
|
disabled={!isEditing}
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:text-gray-500 transition-colors duration-200"
|
|
placeholder="(00) 00000-0000"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Botões de ação */}
|
|
{isEditing && (
|
|
<div className="mt-8 flex justify-end space-x-4">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsEditing(false)}
|
|
className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors duration-200 font-medium"
|
|
disabled={loading}
|
|
>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium disabled:opacity-50"
|
|
disabled={loading}
|
|
>
|
|
{loading ? (
|
|
<span className="flex items-center space-x-2">
|
|
<svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
fill="none"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
<span>Salvando...</span>
|
|
</span>
|
|
) : (
|
|
"Salvar Alterações"
|
|
)}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</form>
|
|
</div>
|
|
)}
|
|
|
|
{/* Formulário de Alteração de Senha */}
|
|
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
|
<div className="bg-gradient-to-r from-blue-600 to-blue-800 px-8 py-4">
|
|
<h2 className="text-xl font-semibold text-white">Alterar Senha</h2>
|
|
</div>
|
|
|
|
{senhaError && (
|
|
<div className="mx-8 mt-6 p-4 bg-red-50 border-l-4 border-red-500 text-red-700 rounded">
|
|
{senhaError}
|
|
</div>
|
|
)}
|
|
|
|
{senhaSuccess && (
|
|
<div className="mx-8 mt-6 p-4 bg-green-50 border-l-4 border-green-500 text-green-700 rounded">
|
|
{senhaSuccess}
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleSenhaSubmit} className="p-8">
|
|
<div className="space-y-4 max-w-md">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Senha Atual
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={senhaForm.senhaAtual}
|
|
onChange={(e) =>
|
|
setSenhaForm({ ...senhaForm, senhaAtual: e.target.value })
|
|
}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Nova Senha
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={senhaForm.novaSenha}
|
|
onChange={(e) =>
|
|
setSenhaForm({ ...senhaForm, novaSenha: e.target.value })
|
|
}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Confirmar Nova Senha
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={senhaForm.confirmarSenha}
|
|
onChange={(e) =>
|
|
setSenhaForm({ ...senhaForm, confirmarSenha: e.target.value })
|
|
}
|
|
required
|
|
className="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<button
|
|
type="submit"
|
|
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium disabled:opacity-50"
|
|
disabled={loadingSenha}
|
|
>
|
|
{loadingSenha ? (
|
|
<span className="flex items-center space-x-2">
|
|
<svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
fill="none"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
<span>Alterando senha...</span>
|
|
</span>
|
|
) : (
|
|
"Alterar Senha"
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|