// Wander — Auth screens (Login / Register / Forgot / Reset) function AuthScreen({ onLogin, onBack, initialMode, resetToken }) { const [mode, setMode] = React.useState(resetToken ? 'reset' : (initialMode || 'login')); const [form, setForm] = React.useState({ email:'', name:'', password:'', confirm:'', newPassword:'' }); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(''); const [success, setSuccess] = React.useState(''); const wrapRef = React.useRef(null); const formRef = React.useRef(null); // Entrance animation React.useEffect(() => { if (window.anime && wrapRef.current) { anime({ targets: wrapRef.current, translateY: [32, 0], opacity: [0, 1], duration: 480, easing: 'easeOutQuart' }); } }, []); // Animate fields on mode switch React.useEffect(() => { setError(''); setSuccess(''); if (window.anime && formRef.current) { anime({ targets: formRef.current.querySelectorAll('.af'), translateX: [-16, 0], opacity: [0, 1], duration: 260, delay: anime.stagger(40), easing: 'easeOutQuart', }); } }, [mode]); const set = k => e => setForm(f => ({ ...f, [k]: e.target.value })); const animateButton = e => { if (window.anime) anime({ targets: e.currentTarget, scale: [1, 0.96, 1], duration: 220, easing: 'easeInOutQuad' }); }; const handleSubmit = async e => { e.preventDefault(); setError(''); setLoading(true); try { if (mode === 'login') { const r = await fetch('/api/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ email: form.email, password: form.password }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error); localStorage.setItem('wander_token', d.token); localStorage.setItem('wander_user', JSON.stringify(d.user)); onLogin(d.user, d.token); } else if (mode === 'register') { if (form.password !== form.confirm) throw new Error('Passwords do not match'); const r = await fetch('/api/auth/register', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ email: form.email, name: form.name, password: form.password }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error); localStorage.setItem('wander_token', d.token); localStorage.setItem('wander_user', JSON.stringify(d.user)); onLogin(d.user, d.token); } else if (mode === 'forgot') { const r = await fetch('/api/auth/forgot-password', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ email: form.email }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error); setSuccess('Check your inbox — a reset link is on its way. ✉️'); } else if (mode === 'reset') { if (form.newPassword.length < 8) throw new Error('Password must be at least 8 characters'); const r = await fetch('/api/auth/reset-password', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ token: resetToken, password: form.newPassword }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error); localStorage.setItem('wander_token', d.token); localStorage.setItem('wander_user', JSON.stringify(d.user)); onLogin(d.user, d.token); } } catch (e) { setError(e.message); if (window.anime && formRef.current) { anime({ targets: formRef.current, translateX: [-6,6,-6,6,-4,4,0], duration: 380, easing:'easeInOutQuad' }); } } finally { setLoading(false); } }; const S = { wrap: { height:'100%', background:'var(--w-cream)', display:'flex', flexDirection:'column', overflowY:'auto' }, hero: { padding:'48px 24px 20px', textAlign:'center' }, compass: { fontSize:52, lineHeight:1, marginBottom:8, display:'block' }, wordmark: { fontFamily:"'Fraunces',Georgia,serif", fontSize:30, fontWeight:600, color:'var(--w-ink)', margin:'0 0 4px' }, tagline: { fontSize:13, color:'var(--w-mute)', margin:0 }, tabRow: { display:'flex', margin:'0 20px 20px', background:'var(--w-line)', borderRadius:14, padding:3, gap:3 }, tab: active => ({ flex:1, padding:'9px 0', borderRadius:11, border:'none', cursor:'pointer', fontFamily:'inherit', fontWeight:700, fontSize:13, transition:'all .2s', background: active?'var(--w-paper)':'transparent', color: active?'var(--w-ink)':'var(--w-mute)', boxShadow: active?'0 1px 5px rgba(0,0,0,.1)':'none' }), form: { padding:'0 20px', display:'flex', flexDirection:'column', gap:14 }, field: { display:'flex', flexDirection:'column', gap:5 }, label: { fontSize:11, fontWeight:700, color:'var(--w-mute)', textTransform:'uppercase', letterSpacing:0.6 }, input: { padding:'13px 16px', borderRadius:14, border:'1.5px solid var(--w-line)', fontSize:15, fontFamily:'inherit', color:'var(--w-ink)', background:'#fff', outline:'none', transition:'border-color .15s' }, btn: { margin:'4px 0 0', padding:'15px', borderRadius:9999, background:'var(--w-yellow)', color:'var(--w-yellow-ink)', fontWeight:700, fontSize:16, border:'none', cursor:'pointer', fontFamily:'inherit', transition:'opacity .15s' }, ghost: { background:'transparent', border:'1.5px solid var(--w-line)', color:'var(--w-mute)' }, link: { textAlign:'center', fontSize:13, color:'var(--w-mute)', cursor:'pointer', padding:'6px 0', textDecoration:'underline', textUnderlineOffset:3 }, err: { padding:'11px 16px', borderRadius:12, background:'#FFE9E9', color:'#C0392B', fontSize:13, textAlign:'center', lineHeight:1.4 }, ok: { padding:'11px 16px', borderRadius:12, background:'#E6FBF0', color:'#1A7A4A', fontSize:13, textAlign:'center', lineHeight:1.4 }, }; const isSpecial = mode === 'forgot' || mode === 'reset'; return (
{/* Hero */}
🧭

Wander

{mode==='forgot' ? 'Reset your password' : mode==='reset' ? 'Create a new password' : 'Local stories, guided by locals'}

{/* Tab bar — only for login/register */} {!isSpecial && (
)} {/* Form */}
{mode==='register' && (
)} {mode!=='reset' && (
)} {(mode==='login'||mode==='register') && (
)} {mode==='register' && (
)} {mode==='reset' && (
)} {error &&
{error}
} {success &&
{success}
} {!success && ( )} {mode==='login' && (

setMode('forgot')}>Forgot your password?

)} {isSpecial && (

setMode('login')}>← Back to Sign In

)} {onBack && ( )}
{/* Terms note */} {mode==='register' && (

By creating an account you agree to our Terms of Service and Privacy Policy.

)} {mode!=='register' &&
}
); }