import { useState, useEffect, useRef } from 'react'; import { Eye, EyeOff, ArrowRight, Sun, Moon, Cpu, Shield, Zap, Lock, X, CheckCircle } from 'lucide-react'; import { fetchApi } from '../../api'; import { supabase } from '../../supabaseClient'; import { isPersonalEmailAllowed, PERSONAL_EMAIL_ERROR } from '../../utils/personalEmail'; const FONT_LINK = 'https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Syne:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&display=swap'; const KEYFRAMES = ` @keyframes lp-up { from{opacity:0;transform:translateY(18px)} to{opacity:1;transform:none} } @keyframes lp-left { from{opacity:0;transform:translateX(-18px)} to{opacity:1;transform:none} } @keyframes lp-glow { 0%,100%{opacity:.35;transform:scale(1)} 50%{opacity:.7;transform:scale(1.06)} } @keyframes lp-sway { 0%,100%{transform:rotate(-3deg) scale(1)} 50%{transform:rotate(3deg) scale(1.02)} } @keyframes lp-twinkle{ 0%,100%{opacity:.1;transform:scale(.7)} 50%{opacity:.85;transform:scale(1.3)} } @keyframes lp-orbit { from{transform:rotate(0deg) translateX(160px) rotate(0deg)} to{transform:rotate(360deg) translateX(160px) rotate(-360deg)} } @keyframes lp-shake { 0%,100%{transform:translateX(0)} 20%,60%{transform:translateX(-5px)} 40%,80%{transform:translateX(5px)} } @keyframes lp-spin { to{transform:rotate(360deg)} } @keyframes modal-in { from{opacity:0} to{opacity:1} } @keyframes modal-slide { from{opacity:0;transform:translateY(28px) scale(.97)} to{opacity:1;transform:none} } @keyframes success-pop { 0%{transform:scale(.5);opacity:0} 70%{transform:scale(1.15)} 100%{transform:scale(1);opacity:1} } @keyframes bar-fill { from{width:0%} to{width:100%} } .lp-form { animation: lp-up .5s cubic-bezier(.16,1,.3,1) forwards } .lp-panel { animation: lp-left .5s cubic-bezier(.16,1,.3,1) forwards } .lp-shake { animation: lp-shake .4s ease } .lp-spin { animation: lp-spin 1s linear infinite } .modal-in { animation: modal-in .22s ease forwards } .modal-slide{ animation: modal-slide .32s cubic-bezier(.16,1,.3,1) forwards } .success-pop{ animation: success-pop .45s cubic-bezier(.16,1,.3,1) forwards } .bar-fill { animation: bar-fill 2.4s linear forwards } `; const STARS = Array.from({ length: 70 }, (_, i) => ({ x: ((i * 137.508) % 100).toFixed(2), y: ((i * 97.3) % 100).toFixed(2), r: (0.5 + (i % 4) * 0.45).toFixed(1), d: ((i * 0.22) % 3).toFixed(2), t: (2 + (i % 5) * 0.7).toFixed(1), })); const LIGHT = { pageBg: '#ffffff', panelBg: '#111827', panelText: '#f9fafb', panelSub: 'rgba(249,250,251,.5)', panelAccent: '#0d9488', panelGrad: 'linear-gradient(135deg,#0d9488,#14b8a6)', cardBg: '#ffffff', cardBorder: 'rgba(209,213,219,.6)', cardShadow: '0 20px 60px rgba(0,0,0,.07)', t1: '#111827', t2: '#6b7280', t3: '#9ca3af', t4: 'rgba(209,213,219,.4)', acc: '#0d9488', acc2: '#14b8a6', accGrad: 'linear-gradient(135deg,#0d9488,#14b8a6)', pillBg: 'rgba(204,251,241,.5)', pillBorder: 'rgba(153,246,228,.8)', pillText: '#0f766e', inputBg: '#f9fafb', inputBorder: '#e5e7eb', inputFocus: '#0d9488', inputText: '#111827', inputPH: '#9ca3af', inputIcon: '#9ca3af', labelColor: '#6b7280', linkColor: '#0d9488', btnBg: 'linear-gradient(135deg,#0d9488,#14b8a6)', btnShadow: '0 6px 24px rgba(13,148,136,.3)', btnText: '#ffffff', dividerBg: 'rgba(209,213,219,.6)', dividerText: '#9ca3af', socialBg: '#f9fafb', socialBorder: '#e5e7eb', socialText: '#6b7280', footerText: '#d1d5db', toggleBg: '#f3f4f6', toggleBorder: '#e5e7eb', errorBg: '#fff5f5', errorBorder: '#fecaca', errorText: '#dc2626', modalOverlay: 'rgba(0,0,0,.45)', modalBg: '#ffffff', modalBorder: 'rgba(209,213,219,.6)', modalShadow: '0 32px 80px rgba(0,0,0,.18)', strengthTrack: '#e5e7eb', successIcon: '#0d9488', successSub: '#6b7280', }; const DARK = { pageBg: '#0c0908', panelBg: '#080605', panelText: '#ecfeff', panelSub: 'rgba(207,250,254,.45)', panelAccent: '#22d3ee', panelGrad: 'linear-gradient(135deg,#0e7490,#22d3ee)', cardBg: '#15100d', cardBorder: 'rgba(21,94,117,.3)', cardShadow: '0 20px 60px rgba(0,0,0,.7)', t1: '#ecfeff', t2: 'rgba(207,250,254,.6)', t3: 'rgba(207,250,254,.35)', t4: 'rgba(21,94,117,.2)', acc: '#22d3ee', acc2: '#a5f3fc', accGrad: 'linear-gradient(135deg,#0e7490,#22d3ee)', pillBg: 'rgba(21,94,117,.18)', pillBorder: 'rgba(21,94,117,.4)', pillText: '#a5f3fc', inputBg: '#080605', inputBorder: 'rgba(35,26,21,.8)', inputFocus: '#22d3ee', inputText: '#ecfeff', inputPH: 'rgba(207,250,254,.25)', inputIcon: 'rgba(34,211,238,.4)', labelColor: '#a5f3fc', linkColor: '#67e8f9', btnBg: '#e0f2fe', btnShadow: '0 6px 24px rgba(34,211,238,.2)', btnText: '#0c0908', dividerBg: 'rgba(35,26,21,.8)', dividerText: 'rgba(207,250,254,.35)', socialBg: 'rgba(21,94,117,.08)', socialBorder: 'rgba(35,26,21,.8)', socialText: '#a5f3fc', footerText: 'rgba(21,94,117,.5)', toggleBg: '#1a1310', toggleBorder: '#2a1f1a', errorBg: 'rgba(239,68,68,.08)', errorBorder: 'rgba(239,68,68,.25)', errorText: '#fca5a5', modalOverlay: 'rgba(0,0,0,.72)', modalBg: '#15100d', modalBorder: 'rgba(21,94,117,.35)', modalShadow: '0 32px 80px rgba(0,0,0,.8)', strengthTrack: 'rgba(35,26,21,.8)', successIcon: '#22d3ee', successSub: 'rgba(207,250,254,.6)', }; // ── defined only ONCE ── function getStrength(pw) { if (!pw) return { score: 0, label: '', color: 'transparent' }; let s = 0; if (pw.length >= 8) s++; if (/[A-Z]/.test(pw)) s++; if (/[0-9]/.test(pw)) s++; if (/[^A-Za-z0-9]/.test(pw)) s++; const map = [ { label: 'Too short', color: '#ef4444' }, { label: 'Weak', color: '#f97316' }, { label: 'Fair', color: '#eab308' }, { label: 'Strong', color: '#22c55e' }, { label: 'Very strong', color: '#14b8a6' }, ]; return { score: s, ...map[s] }; } const GoogleIcon = () => ( ); /* ══ FORGOT PASSWORD MODAL ════════════════════════════════════════════ */ function ForgotModal({ dark, T, onClose }) { const [step, setStep] = useState('email'); const [email, setEmail] = useState(''); const [code, setCode] = useState(''); const [newPw, setNewPw] = useState(''); const [confirm, setConfirm] = useState(''); const [showPw, setShowPw] = useState(false); const [showCf, setShowCf] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [shake, setShake] = useState(false); const overlayRef = useRef(null); const mono = { fontFamily: "'JetBrains Mono',monospace" }; const bebas = { fontFamily: "'Bebas Neue',cursive" }; const dm = { fontFamily: "'DM Sans',sans-serif" }; const strength = getStrength(newPw); const confirmMatch = confirm.length > 0 && newPw === confirm; const confirmBad = confirm.length > 0 && newPw !== confirm; const base = { ...dm, width: '100%', fontSize: 13, borderRadius: 12, outline: 'none', boxSizing: 'border-box', background: T.inputBg, border: `1px solid ${T.inputBorder}`, color: T.inputText, transition: 'border-color .2s' }; const triggerError = (msg) => { setError(msg); setShake(true); setTimeout(() => setShake(false), 450); }; const sendCode = async (e) => { e.preventDefault(); if (!email.trim()) return triggerError('Please enter your email.'); setError(''); setLoading(true); try { // TODO: replace with → POST /api/auth/check-email then supabase.auth.resetPasswordForEmail() await new Promise(r => setTimeout(r, 1000)); setStep('code'); } catch (err) { triggerError(err.message || 'Failed to send reset code. Please try again.'); } finally { setLoading(false); } }; const verifyCode = async (e) => { e.preventDefault(); if (code.length < 4) return triggerError('Enter the 6-digit code sent to your email.'); setError(''); setLoading(true); await new Promise(r => setTimeout(r, 800)); setLoading(false); setStep('reset'); }; const resetPassword = async (e) => { e.preventDefault(); if (newPw.length < 8) return triggerError('Password must be at least 8 characters.'); if (newPw !== confirm) return triggerError('Passwords do not match.'); setError(''); setLoading(true); await new Promise(r => setTimeout(r, 1200)); setLoading(false); setStep('done'); setTimeout(onClose, 2200); }; const stepProgress = { email: 0, code: 1, reset: 2, done: 3 }; const stepLabel = { email: 'FORGOT PASSWORD', code: 'CHECK YOUR EMAIL', reset: 'NEW PASSWORD', done: 'ALL DONE' }; const stepTitle = { email: 'Reset Your\nPassword', code: 'Enter Your\nVerification\nCode', reset: 'Create New\nPassword', done: 'Password\nUpdated!' }; const ForgotBtn = ({ lbl }) => ( ); return (
Your password has been updated.
You can now sign in.
Welcome, {name}!
Taking you to upload your first document…
No credit card required · 14-day full access
Already have an account?{' '}
Secured by PolicyLens · SOC 2 Compliant
> )}Upload contracts and policies. Our AI scans every clause, flags every risk, and answers every question.
Sign in to continue to your dashboard.
Don't have an account?{' '}
Secured by PolicyLens · v2.4.1 · SOC 2 Compliant