- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
194 lines
8.4 KiB
TypeScript
194 lines
8.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import { Button, Input } from "@/components/ui";
|
|
import toast, { Toaster } from 'react-hot-toast';
|
|
import { EnvelopeIcon } from "@heroicons/react/24/outline";
|
|
|
|
export default function RecuperarSenhaPage() {
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [email, setEmail] = useState("");
|
|
const [emailSent, setEmailSent] = useState(false);
|
|
const [subdomain, setSubdomain] = useState<string>('');
|
|
const [isSuperAdmin, setIsSuperAdmin] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined') {
|
|
const hostname = window.location.hostname;
|
|
const sub = hostname.split('.')[0];
|
|
setSubdomain(sub);
|
|
setIsSuperAdmin(sub === 'dash');
|
|
}
|
|
}, []);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// Validações básicas
|
|
if (!email) {
|
|
toast.error('Por favor, insira seu email');
|
|
return;
|
|
}
|
|
|
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
toast.error('Por favor, insira um email válido');
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
// Simular envio de email
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
setEmailSent(true);
|
|
toast.success('Email de recuperação enviado com sucesso!');
|
|
} catch (error) {
|
|
toast.error('Erro ao enviar email. Tente novamente.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Toaster
|
|
position="top-center"
|
|
toastOptions={{
|
|
duration: 5000,
|
|
style: {
|
|
background: '#FFFFFF',
|
|
color: '#000000',
|
|
padding: '16px',
|
|
borderRadius: '8px',
|
|
border: '1px solid #E5E5E5',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
},
|
|
error: {
|
|
icon: '⚠️',
|
|
style: {
|
|
background: '#ef4444',
|
|
color: '#FFFFFF',
|
|
border: 'none',
|
|
},
|
|
},
|
|
success: {
|
|
icon: '✓',
|
|
style: {
|
|
background: '#10B981',
|
|
color: '#FFFFFF',
|
|
border: 'none',
|
|
},
|
|
},
|
|
}}
|
|
/>
|
|
<div className="flex min-h-screen">
|
|
{/* Lado Esquerdo - Formulário */}
|
|
<div className="w-full lg:w-1/2 flex items-center justify-center px-6 sm:px-12 py-12">
|
|
<div className="w-full max-w-md">
|
|
{/* Logo mobile */}
|
|
<div className="lg:hidden text-center mb-8">
|
|
<div className="inline-block px-6 py-3 rounded-2xl" style={{ background: 'var(--brand-color)' }}>
|
|
<h1 className="text-3xl font-bold text-white">
|
|
{isSuperAdmin ? 'aggios' : subdomain}
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
{!emailSent ? (
|
|
<>
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<h2 className="text-[28px] font-bold text-zinc-900 dark:text-white">
|
|
Recuperar Senha
|
|
</h2>
|
|
<p className="text-[14px] text-zinc-600 dark:text-zinc-400 mt-2">
|
|
Digite seu email e enviaremos um link para redefinir sua senha
|
|
</p>
|
|
</div>
|
|
|
|
{/* Form */}
|
|
<form onSubmit={handleSubmit} className="space-y-5">
|
|
<Input
|
|
label="Email"
|
|
type="email"
|
|
placeholder="seu@email.com"
|
|
leftIcon={<EnvelopeIcon className="w-5 h-5" />}
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
required
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="primary"
|
|
size="lg"
|
|
className="w-full"
|
|
isLoading={isLoading}
|
|
>
|
|
Enviar link de recuperação
|
|
</Button>
|
|
|
|
<div className="text-center">
|
|
<Link
|
|
href="/login"
|
|
className="text-[14px] font-medium hover:opacity-80 transition-opacity"
|
|
style={{ color: 'var(--brand-color)' }}
|
|
>
|
|
Voltar para o login
|
|
</Link>
|
|
</div>
|
|
</form>
|
|
</>
|
|
) : (
|
|
<div className="text-center">
|
|
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<i className="ri-mail-check-line text-3xl text-green-600"></i>
|
|
</div>
|
|
<h2 className="text-[24px] font-bold text-zinc-900 dark:text-white mb-2">
|
|
Verifique seu email
|
|
</h2>
|
|
<p className="text-zinc-600 dark:text-zinc-400 mb-8">
|
|
Enviamos um link de recuperação para <strong>{email}</strong>
|
|
</p>
|
|
<Button
|
|
variant="outline"
|
|
className="w-full"
|
|
onClick={() => setEmailSent(false)}
|
|
>
|
|
Tentar outro email
|
|
</Button>
|
|
<div className="mt-6">
|
|
<Link
|
|
href="/login"
|
|
className="text-[14px] font-medium hover:opacity-80 transition-opacity"
|
|
style={{ color: 'var(--brand-color)' }}
|
|
>
|
|
Voltar para o login
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Lado Direito - Branding */}
|
|
<div className="hidden lg:flex lg:w-1/2 relative overflow-hidden" style={{ background: 'var(--brand-color)' }}>
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center p-12 text-white">
|
|
<div className="max-w-md text-center">
|
|
<h1 className="text-5xl font-bold mb-6">
|
|
{isSuperAdmin ? 'aggios' : subdomain}
|
|
</h1>
|
|
<p className="text-xl opacity-90">
|
|
Recupere o acesso à sua conta de forma segura e rápida.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|