edumanagerpro2/manager/components/Auth.tsx

234 lines
9.2 KiB
TypeScript

import React, { useState } from 'react';
import { SchoolData, User } from '../types';
import { BookOpen, User as UserIcon, Lock, ArrowRight, Loader2, Shield } from 'lucide-react';
interface AuthProps {
data: SchoolData;
onLogin: (user: User) => void;
onUpdateUsers: (newUsers: User[]) => void;
}
const Auth: React.FC<AuthProps> = ({ data, onLogin, onUpdateUsers }) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [isRecovering, setIsRecovering] = useState(false);
const [formData, setFormData] = useState({
name: '',
displayName: '',
password: '',
newPassword: '',
cpf: ''
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setSuccess('');
setIsLoading(true);
// Simulate network delay for better UX
await new Promise(resolve => setTimeout(resolve, 800));
// Normalize inputs
const loginName = (formData.name || '').trim().toLowerCase();
const loginPass = formData.password.trim();
const loginCpf = formData.cpf.replace(/\D/g, '');
// Ensure data.users exists before finding
const usersList = data.users || [];
if (isRecovering) {
const userIndex = usersList.findIndex(u => (u.name || '').toLowerCase() === loginName);
const user = usersList[userIndex];
if (user) {
const storedCpf = (user.cpf || '').replace(/\D/g, '');
if (!loginCpf) {
setError('O CPF é obrigatório para recuperação.');
} else if (storedCpf && storedCpf !== loginCpf) {
setError('CPF incorreto para este usuário.');
} else if (!storedCpf) {
setError('Este usuário não possui CPF cadastrado. Entre em contato com o suporte.');
} else if (!formData.newPassword) {
setError('Digite a nova senha.');
} else if (!formData.displayName) {
setError('O Nome Completo é obrigatório.');
} else {
const updatedUsers = [...usersList];
updatedUsers[userIndex] = {
...updatedUsers[userIndex],
password: formData.newPassword.trim(),
displayName: formData.displayName.trim()
};
onUpdateUsers(updatedUsers);
setSuccess('Dados atualizados com sucesso! Faça login.');
setIsRecovering(false);
setFormData({ name: '', displayName: '', password: '', newPassword: '', cpf: '' });
}
} else {
setError('Usuário não encontrado.');
}
} else {
// Login Logic
const user = usersList.find(u =>
(u.name || '').toLowerCase() === loginName &&
u.password === loginPass
);
if (user) {
onLogin(user);
} else {
setError('Usuário ou senha inválidos.');
}
}
setIsLoading(false);
};
const inputClass = "w-full pl-10 pr-4 py-3 bg-slate-50 text-slate-900 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all font-medium text-sm";
return (
<div className="min-h-screen bg-slate-50 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden border border-slate-100 animate-zoom-in">
<div className="p-8 pb-6 bg-white">
<div className="flex justify-center mb-6">
{data.logo ? (
<img src={data.logo} alt="Logo" className="h-28 w-auto max-w-full object-contain" />
) : (
<div className="w-16 h-16 bg-indigo-50 rounded-2xl flex items-center justify-center text-indigo-600 shadow-sm">
<BookOpen size={32} />
</div>
)}
</div>
<h2 className="text-2xl font-black text-center text-slate-800 mb-1">
{isRecovering ? 'Recuperar Senha' : 'Acesso Restrito'}
</h2>
<p className="text-center text-slate-500 text-sm font-medium mb-8">
{isRecovering ? 'Crie uma nova senha para o seu usuário.' : 'Insira suas credenciais para acessar o EduManager.'}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="relative group">
<UserIcon className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-indigo-500 transition-colors" size={18} />
<input
type="text"
placeholder="Nome de Usuário"
required
className={inputClass}
value={formData.name}
onChange={e => setFormData({...formData, name: e.target.value})}
/>
</div>
{isRecovering && (
<>
<div className="relative group">
<UserIcon className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-indigo-500 transition-colors" size={18} />
<input
type="text"
placeholder="Nome Completo (Ex: João Silva)"
required
className={inputClass}
value={formData.displayName}
onChange={e => setFormData({...formData, displayName: e.target.value})}
/>
</div>
<div className="relative group">
<Shield className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-indigo-500 transition-colors" size={18} />
<input
type="text"
placeholder="Confirme seu CPF"
required
className={inputClass}
value={formData.cpf}
onChange={e => {
const val = e.target.value.replace(/\D/g, '').replace(/(\d{3})(\d)/, '$1.$2').replace(/(\d{3})(\d)/, '$1.$2').replace(/(\d{3})(\d{1,2})/, '$1-$2').slice(0, 14);
setFormData({...formData, cpf: val});
}}
/>
</div>
</>
)}
{!isRecovering ? (
<div className="relative group">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-indigo-500 transition-colors" size={18} />
<input
type="password"
placeholder="Senha"
required
className={inputClass}
value={formData.password}
onChange={e => setFormData({...formData, password: e.target.value})}
/>
</div>
) : (
<div className="relative group">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-indigo-500 transition-colors" size={18} />
<input
type="password"
placeholder="Nova Senha"
required
className={inputClass}
value={formData.newPassword}
onChange={e => setFormData({...formData, newPassword: e.target.value})}
/>
</div>
)}
{error && (
<div className="p-3 bg-red-50 text-red-600 text-xs font-bold rounded-lg text-center animate-in fade-in">
{error}
</div>
)}
{success && (
<div className="p-3 bg-emerald-50 text-emerald-600 text-xs font-bold rounded-lg text-center animate-in fade-in">
{success}
</div>
)}
<button
type="submit"
disabled={isLoading}
className="w-full bg-indigo-600 text-white py-3.5 rounded-xl font-bold text-sm shadow-lg shadow-indigo-200 hover:bg-indigo-700 hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0 transition-all flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed mt-4"
>
{isLoading ? (
<Loader2 className="animate-spin" size={20} />
) : (
<>
{isRecovering ? 'Redefinir Senha' : 'Entrar'}
{!isRecovering && <ArrowRight size={18} />}
</>
)}
</button>
</form>
<div className="mt-6 text-center">
<button
type="button"
onClick={() => {
setIsRecovering(!isRecovering);
setError('');
setSuccess('');
setFormData({ name: '', displayName: '', password: '', newPassword: '', cpf: '' });
}}
className="text-sm font-bold text-indigo-600 hover:text-indigo-800 transition-colors"
>
{isRecovering ? 'Voltar para o Login' : 'Esqueceu a senha?'}
</button>
</div>
</div>
<div className="p-4 bg-slate-50 border-t border-slate-100 text-center">
<p className="text-[10px] text-slate-400 uppercase font-bold">Acesso Padrão: admin / admin</p>
</div>
</div>
</div>
);
};
export default Auth;