|
|
|
@@ -53,7 +53,7 @@ const EyeToggle: React.FC<{ show: boolean; onToggle: () => void }> = ({ show, on
|
|
|
|
|
const ErrorBanner: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
|
|
|
<div className="p-3 bg-red-50 border border-red-100 rounded-xl text-red-600 text-xs font-bold text-center uppercase tracking-wide">{children}</div>
|
|
|
|
|
);
|
|
|
|
|
const InputCls = 'w-full pl-10 pr-4 py-3.5 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium text-gray-900 placeholder:text-gray-400 outline-none focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-100 transition-all';
|
|
|
|
|
const InputCls = 'w-full pl-10 pr-4 py-3.5 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium text-gray-900 placeholder:text-gray-400 outline-none focus:border-teal-500 focus:bg-white focus:ring-2 focus:ring-teal-100 transition-all';
|
|
|
|
|
|
|
|
|
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
|
|
|
export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
@@ -139,13 +139,13 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
/* ─── Splash ─── */
|
|
|
|
|
if (initializing) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="h-dvh bg-[#0B1220] flex flex-col items-center justify-center gap-5">
|
|
|
|
|
<div className="w-16 h-16 bg-gradient-to-tr from-blue-500 to-indigo-500 rounded-2xl flex items-center justify-center shadow-xl shadow-blue-900/40">
|
|
|
|
|
<div className="h-dvh bg-[#04201d] flex flex-col items-center justify-center gap-5">
|
|
|
|
|
<div className="w-16 h-16 bg-gradient-to-tr from-teal-500 to-emerald-500 rounded-2xl flex items-center justify-center shadow-xl shadow-teal-900/40">
|
|
|
|
|
<Stethoscope className="text-white" size={30} />
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-2xl font-black text-white tracking-tighter uppercase italic">Score<span className="text-blue-400">Odonto</span></p>
|
|
|
|
|
<p className="text-2xl font-black text-white tracking-tighter uppercase italic">Score<span className="text-teal-400">Odonto</span></p>
|
|
|
|
|
<div className="flex gap-1.5">
|
|
|
|
|
{[0, 1, 2].map(i => <div key={i} className="w-2 h-2 rounded-full bg-blue-400" style={{ animation: `b 1.2s ease-in-out ${i * 0.2}s infinite` }} />)}
|
|
|
|
|
{[0, 1, 2].map(i => <div key={i} className="w-2 h-2 rounded-full bg-teal-400" style={{ animation: `b 1.2s ease-in-out ${i * 0.2}s infinite` }} />)}
|
|
|
|
|
</div>
|
|
|
|
|
<style>{`@keyframes b{0%,80%,100%{transform:translateY(0);opacity:.5}40%{transform:translateY(-8px);opacity:1}}`}</style>
|
|
|
|
|
</div>
|
|
|
|
@@ -165,9 +165,9 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
|
|
|
|
|
{/* ── LEFT — showcase ── */}
|
|
|
|
|
<div className="hidden lg:flex flex-col justify-between flex-1 h-full relative overflow-hidden p-12 xl:p-16
|
|
|
|
|
bg-gradient-to-br from-[#0B1220] via-[#0e1a33] to-[#1e3a8a] text-white">
|
|
|
|
|
<div className="absolute -top-24 -right-24 w-96 h-96 bg-blue-500/20 rounded-full blur-3xl pointer-events-none" style={{ animation: 'float 8s ease-in-out infinite' }} />
|
|
|
|
|
<div className="absolute -bottom-24 -left-24 w-80 h-80 bg-indigo-500/20 rounded-full blur-3xl pointer-events-none" />
|
|
|
|
|
bg-gradient-to-br from-[#04201d] via-[#065f54] to-[#0d9488] text-white">
|
|
|
|
|
<div className="absolute -top-24 -right-24 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl pointer-events-none" style={{ animation: 'float 8s ease-in-out infinite' }} />
|
|
|
|
|
<div className="absolute -bottom-24 -left-24 w-80 h-80 bg-emerald-500/20 rounded-full blur-3xl pointer-events-none" />
|
|
|
|
|
|
|
|
|
|
{/* Logo */}
|
|
|
|
|
<div className="flex items-center gap-3 relative z-10">
|
|
|
|
@@ -175,21 +175,21 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
<Stethoscope className="text-white" size={22} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-black text-xl tracking-tighter uppercase italic leading-none">Score<span className="text-blue-300">Odonto</span></p>
|
|
|
|
|
<p className="text-blue-300/70 text-[10px] font-black uppercase tracking-widest">Plataforma Odontológica</p>
|
|
|
|
|
<p className="font-black text-xl tracking-tighter uppercase italic leading-none">Score<span className="text-teal-300">Odonto</span></p>
|
|
|
|
|
<p className="text-teal-300/70 text-[10px] font-black uppercase tracking-widest">Plataforma Odontológica</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Headline + features */}
|
|
|
|
|
<div className="relative z-10 space-y-7">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest bg-blue-500/20 text-blue-200 border border-blue-400/30 px-3 py-1 rounded-full mb-4">
|
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest bg-teal-500/20 text-teal-200 border border-teal-400/30 px-3 py-1 rounded-full mb-4">
|
|
|
|
|
<Sparkles size={11} /> Novo: marketplace + locação de salas
|
|
|
|
|
</span>
|
|
|
|
|
<h2 className="text-4xl xl:text-5xl font-black tracking-tighter leading-[1.05]">
|
|
|
|
|
Gestão, conexão e<br /><span className="text-blue-300 italic">crescimento</span> da sua prática
|
|
|
|
|
Gestão, conexão e<br /><span className="text-teal-300 italic">crescimento</span> da sua prática
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-blue-100/70 text-base font-medium leading-relaxed max-w-md mt-4">
|
|
|
|
|
<p className="text-teal-100/70 text-base font-medium leading-relaxed max-w-md mt-4">
|
|
|
|
|
Do prontuário ao marketplace: encontre profissionais e salas por proximidade, gerencie a agenda sem choques e o financeiro num só lugar.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
@@ -197,24 +197,24 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
|
|
|
{FEATURES.map(({ icon: Icon, label, desc }) => (
|
|
|
|
|
<div key={label} className="bg-white/5 backdrop-blur-sm rounded-2xl p-4 border border-white/10 hover:bg-white/10 transition-colors">
|
|
|
|
|
<Icon size={20} className="text-blue-300 mb-2" />
|
|
|
|
|
<Icon size={20} className="text-teal-300 mb-2" />
|
|
|
|
|
<p className="font-black text-xs uppercase tracking-tight leading-none mb-1">{label}</p>
|
|
|
|
|
<p className="text-blue-200/60 text-[10px] font-medium leading-snug">{desc}</p>
|
|
|
|
|
<p className="text-teal-200/60 text-[10px] font-medium leading-snug">{desc}</p>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{ROLE_CHIPS.map(({ icon: Icon, label }) => (
|
|
|
|
|
<span key={label} className="inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wide bg-white/5 border border-white/10 text-blue-100/80 px-2.5 py-1 rounded-lg">
|
|
|
|
|
<Icon size={12} className="text-blue-300" /> {label}
|
|
|
|
|
<span key={label} className="inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wide bg-white/5 border border-white/10 text-teal-100/80 px-2.5 py-1 rounded-lg">
|
|
|
|
|
<Icon size={12} className="text-teal-300" /> {label}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
|
|
|
|
<div className="relative z-10 flex items-center gap-2 text-blue-300/50 text-[10px] font-black uppercase tracking-widest">
|
|
|
|
|
<div className="relative z-10 flex items-center gap-2 text-teal-300/50 text-[10px] font-black uppercase tracking-widest">
|
|
|
|
|
<ShieldCheck size={12} /> Dados criptografados · LGPD · {APP_VERSION}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
@@ -223,22 +223,22 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
<div className="flex-1 flex flex-col items-center justify-center p-6 lg:p-12 overflow-y-auto">
|
|
|
|
|
{/* Mobile logo */}
|
|
|
|
|
<div className="lg:hidden flex items-center gap-2 mb-8">
|
|
|
|
|
<div className="w-9 h-9 bg-gradient-to-tr from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-md shadow-blue-200">
|
|
|
|
|
<div className="w-9 h-9 bg-gradient-to-tr from-teal-600 to-emerald-600 rounded-xl flex items-center justify-center shadow-md shadow-teal-200">
|
|
|
|
|
<Stethoscope size={18} className="text-white" />
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-xl font-black text-gray-900 tracking-tighter uppercase italic">Score<span className="text-blue-600">Odonto</span></span>
|
|
|
|
|
<span className="text-xl font-black text-gray-900 tracking-tighter uppercase italic">Score<span className="text-teal-600">Odonto</span></span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="w-full max-w-md">
|
|
|
|
|
{authMode === 'reset' ? (
|
|
|
|
|
<div className="bg-white rounded-[2rem] shadow-[0_40px_80px_-20px_rgba(0,0,0,0.12)] border border-gray-100 overflow-hidden">
|
|
|
|
|
<div className="h-1.5 bg-gradient-to-r from-blue-500 to-indigo-500" />
|
|
|
|
|
<div className="h-1.5 bg-gradient-to-r from-teal-500 to-emerald-500" />
|
|
|
|
|
<div className="p-8">
|
|
|
|
|
<button onClick={() => setAuthMode('login')} className="flex items-center gap-1.5 text-xs font-black text-gray-400 hover:text-gray-600 uppercase tracking-widest mb-6 transition-colors">
|
|
|
|
|
<ChevronLeft size={14} /> Voltar ao login
|
|
|
|
|
</button>
|
|
|
|
|
<div className="text-center mb-6">
|
|
|
|
|
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-3"><KeyRound size={22} className="text-blue-600" /></div>
|
|
|
|
|
<div className="w-12 h-12 bg-teal-50 rounded-2xl flex items-center justify-center mx-auto mb-3"><KeyRound size={22} className="text-teal-600" /></div>
|
|
|
|
|
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tight">Nova Senha</h2>
|
|
|
|
|
<p className="text-xs text-gray-500 font-medium mt-1">Defina sua nova senha de acesso</p>
|
|
|
|
|
</div>
|
|
|
|
@@ -254,7 +254,7 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
</Field>
|
|
|
|
|
{error && <ErrorBanner>{error}</ErrorBanner>}
|
|
|
|
|
{success && <div className="p-3 bg-green-50 border border-green-100 rounded-xl text-green-700 text-xs font-bold text-center uppercase">{success}</div>}
|
|
|
|
|
<button type="submit" disabled={loading} className="w-full bg-blue-600 hover:bg-blue-700 disabled:opacity-60 text-white py-4 rounded-xl text-sm font-black uppercase tracking-widest shadow-lg shadow-blue-200 transition-all flex items-center justify-center gap-2">
|
|
|
|
|
<button type="submit" disabled={loading} className="w-full bg-teal-600 hover:bg-teal-700 disabled:opacity-60 text-white py-4 rounded-xl text-sm font-black uppercase tracking-widest shadow-lg shadow-teal-200 transition-all flex items-center justify-center gap-2">
|
|
|
|
|
{loading ? <><Loader2 size={18} className="sp" /> Salvando...</> : <>Redefinir Senha <ArrowRight size={18} /></>}
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
@@ -262,13 +262,13 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
</div>
|
|
|
|
|
) : authMode === 'forgot' ? (
|
|
|
|
|
<div className="bg-white rounded-[2rem] shadow-[0_40px_80px_-20px_rgba(0,0,0,0.12)] border border-gray-100 overflow-hidden">
|
|
|
|
|
<div className="h-1.5 bg-gradient-to-r from-blue-500 to-indigo-500" />
|
|
|
|
|
<div className="h-1.5 bg-gradient-to-r from-teal-500 to-emerald-500" />
|
|
|
|
|
<div className="p-8">
|
|
|
|
|
<button onClick={() => { setAuthMode('login'); setError(''); setSuccess(''); }} className="flex items-center gap-1.5 text-xs font-black text-gray-400 hover:text-gray-600 uppercase tracking-widest mb-6 transition-colors">
|
|
|
|
|
<ChevronLeft size={14} /> Voltar ao login
|
|
|
|
|
</button>
|
|
|
|
|
<div className="text-center mb-6">
|
|
|
|
|
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-3"><RotateCcw size={22} className="text-blue-600" /></div>
|
|
|
|
|
<div className="w-12 h-12 bg-teal-50 rounded-2xl flex items-center justify-center mx-auto mb-3"><RotateCcw size={22} className="text-teal-600" /></div>
|
|
|
|
|
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tight">Recuperar Senha</h2>
|
|
|
|
|
<p className="text-xs text-gray-500 font-medium mt-1">Informe o e-mail cadastrado</p>
|
|
|
|
|
</div>
|
|
|
|
@@ -279,7 +279,7 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
</Field>
|
|
|
|
|
{error && <ErrorBanner>{error}</ErrorBanner>}
|
|
|
|
|
{success && <div className="p-3 bg-green-50 border border-green-100 rounded-xl text-green-700 text-xs font-bold text-center uppercase">{success}</div>}
|
|
|
|
|
<button type="submit" disabled={loading} className="w-full bg-blue-600 hover:bg-blue-700 disabled:opacity-60 text-white py-4 rounded-xl text-sm font-black uppercase tracking-widest shadow-lg shadow-blue-200 transition-all flex items-center justify-center gap-2">
|
|
|
|
|
<button type="submit" disabled={loading} className="w-full bg-teal-600 hover:bg-teal-700 disabled:opacity-60 text-white py-4 rounded-xl text-sm font-black uppercase tracking-widest shadow-lg shadow-teal-200 transition-all flex items-center justify-center gap-2">
|
|
|
|
|
{loading ? <><Loader2 size={18} className="sp" /> Enviando...</> : <>Enviar Instruções <ArrowRight size={18} /></>}
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
@@ -287,11 +287,11 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="bg-white rounded-[2rem] shadow-[0_40px_80px_-20px_rgba(0,0,0,0.12)] border border-gray-100 overflow-hidden">
|
|
|
|
|
<div className="h-1.5 bg-gradient-to-r from-blue-500 to-indigo-500" />
|
|
|
|
|
<div className="h-1.5 bg-gradient-to-r from-teal-500 to-emerald-500" />
|
|
|
|
|
<div className="flex border-b border-gray-100">
|
|
|
|
|
{(['login', 'cadastro'] as const).map(t => (
|
|
|
|
|
<button key={t} type="button" onClick={() => switchTab(t)}
|
|
|
|
|
className={`flex-1 py-4 text-xs font-black uppercase tracking-widest transition-all ${tab === t ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-400 hover:text-gray-600'}`}>
|
|
|
|
|
className={`flex-1 py-4 text-xs font-black uppercase tracking-widest transition-all ${tab === t ? 'text-teal-600 border-b-2 border-teal-600' : 'text-gray-400 hover:text-gray-600'}`}>
|
|
|
|
|
{t === 'login' ? 'Entrar' : 'Criar Conta'}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
@@ -305,7 +305,7 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
<div className="w-16 h-16 bg-green-50 rounded-[1.5rem] flex items-center justify-center"><CheckCircle2 size={32} className="text-green-500" /></div>
|
|
|
|
|
<p className="text-gray-900 font-black text-lg uppercase tracking-tight">Bem-vindo!</p>
|
|
|
|
|
<p className="text-gray-500 text-sm">Redirecionando para o sistema...</p>
|
|
|
|
|
<Loader2 size={20} className="text-blue-600 sp" />
|
|
|
|
|
<Loader2 size={20} className="text-teal-600 sp" />
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<form onSubmit={handleLogin} className="space-y-5">
|
|
|
|
@@ -320,12 +320,12 @@ export const LoginView: React.FC<LoginViewProps> = ({ onLoginSuccess }) => {
|
|
|
|
|
</Field>
|
|
|
|
|
{error && <ErrorBanner>{error}</ErrorBanner>}
|
|
|
|
|
<button type="submit" disabled={loading}
|
|
|
|
|
className="w-full bg-blue-600 hover:bg-blue-700 disabled:opacity-60 disabled:cursor-not-allowed text-white py-4 rounded-2xl text-sm font-black uppercase tracking-widest shadow-lg shadow-blue-200 hover:shadow-xl hover:shadow-blue-300 hover:-translate-y-0.5 active:translate-y-0 transition-all flex items-center justify-center gap-2">
|
|
|
|
|
className="w-full bg-teal-600 hover:bg-teal-700 disabled:opacity-60 disabled:cursor-not-allowed text-white py-4 rounded-2xl text-sm font-black uppercase tracking-widest shadow-lg shadow-teal-200 hover:shadow-xl hover:shadow-teal-300 hover:-translate-y-0.5 active:translate-y-0 transition-all flex items-center justify-center gap-2">
|
|
|
|
|
{loading ? <><Loader2 size={18} className="sp" /> Verificando...</> : <>Entrar <ArrowRight size={18} /></>}
|
|
|
|
|
</button>
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<button type="button" onClick={() => { setAuthMode('forgot'); setError(''); setSuccess(''); }}
|
|
|
|
|
className="text-xs text-gray-400 hover:text-blue-600 font-bold uppercase tracking-widest transition-colors">
|
|
|
|
|
className="text-xs text-gray-400 hover:text-teal-600 font-bold uppercase tracking-widest transition-colors">
|
|
|
|
|
Esqueci minha senha
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|