// auth.jsx — 登录 / 注册,对接真实后端 API const AuthScreen = ({ mode = 'signup' }) => { const { t } = useI18n(); const { isMobile } = useBreakpoint(); const [step, setStep] = React.useState(1); // 1=form, 2=verify code, 3=forgot, 4=reset code, 5=new password const [email, setEmail] = React.useState(''); const [password, setPassword] = React.useState(''); const [newPassword, setNewPassword] = React.useState(''); const [code, setCode] = React.useState(['', '', '', '', '', '']); const [verifyToken, setVerifyToken] = React.useState(''); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(''); // 已登录则直接跳 dashboard React.useEffect(() => { if (window.YiteAPI && window.YiteAPI.isLoggedIn()) { window.location.href = '/profile'; } }, []); // Google One Tap for login/signup page React.useEffect(() => { if (window.initGoogleOneTap && !window.YiteAPI?.isLoggedIn?.()) { window.initGoogleOneTap(); } }, []); /* ── 密码强度 ─────────────────────────────────────────── */ const pwStrength = React.useMemo(() => { if (!password) return 0; let s = 0; if (password.length >= 6) s++; if (password.length >= 10) s++; if (/[A-Z]/.test(password) || /[0-9]/.test(password)) s++; if (/[^A-Za-z0-9]/.test(password)) s++; return s; }, [password]); const pwStrengthColor = ['var(--line-2)', 'var(--danger)', 'var(--warn)', 'var(--accent)', '#4ade80'][pwStrength]; /* ── 登录 ─────────────────────────────────────────────── */ const handleLogin = async () => { if (!email || !password) { setError(t('auth.errEmailPassword')); return; } setLoading(true); setError(''); try { const data = await window.YiteAPI.auth.login(email, password); window.YiteAPI.auth.saveAndRedirect(data, '/discover'); } catch (e) { setError(e.message || t('auth.errLoginFailed')); } finally { setLoading(false); } }; const handleSendCode = async () => { if (!email) { setError(t('auth.errEmail')); return; } if (!password || password.length < 6) { setError(t('auth.errPasswordLen')); return; } setLoading(true); setError(''); try { const res = await fetch('/api/v1/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email.trim(), password }), }).then(r => r.json()); if (res.error) { setError(res.error === 'email already registered' ? t('auth.errEmailExists') : res.error); return; } if (res.token) { window.YiteAPI.setToken(res.token); if (res.user) window.YiteAPI.setUser(res.user); window.location.href = '/discover'; } } catch (e) { setError(e.message || t('auth.errSendCode')); } finally { setLoading(false); } }; const handleVerify = async () => { const codeStr = code.join(''); if (codeStr.length < 6) { setError(t('auth.errCodeLength')); return; } setLoading(true); setError(''); try { const data = await window.YiteAPI.auth.registerWithCode(email, verifyToken, codeStr, password); window.YiteAPI.auth.saveAndRedirect(data, '/discover'); } catch (e) { if (e.status === 400) setError(t('auth.errInvalidCode')); else setError(e.message || t('auth.errVerifyFailed')); } finally { setLoading(false); } }; /* ── 验证码输入框 ─────────────────────────────────────── */ const codeRefs = Array.from({ length: 6 }, () => React.useRef(null)); const handleCodeInput = (i, val) => { const v = val.replace(/\D/g, '').slice(-1); const next = [...code]; next[i] = v; setCode(next); if (v && i < 5) codeRefs[i + 1].current?.focus(); }; const handleCodeKey = (i, e) => { if (e.key === 'Backspace' && !code[i] && i > 0) codeRefs[i - 1].current?.focus(); }; /* ── 重发验证码 ───────────────────────────────────────── */ const handleResend = async () => { setError(''); try { const data = await window.YiteAPI.auth.sendRegisterCode(email); setVerifyToken(data.token); setCode(['', '', '', '', '', '']); } catch (e) { setError(e.message || 'Failed to resend.'); } }; /* ── Forgot password ──────────────────────────────────── */ const handleForgotSendCode = async () => { if (!email) { setError(t('auth.errEnterEmail')); return; } setLoading(true); setError(''); try { const data = await window.YiteAPI.auth.sendResetCode(email); setVerifyToken(data.token); setCode(['', '', '', '', '', '']); setStep(4); } catch (e) { setError(e.message || t('auth.errSendCodeFailed')); } finally { setLoading(false); } }; const handleResetPassword = async () => { const codeStr = code.join(''); if (codeStr.length < 6) { setError(t('auth.errEnterCode')); return; } if (!newPassword || newPassword.length < 6) { setError(t('auth.errPasswordMin6')); return; } setLoading(true); setError(''); try { await window.YiteAPI.auth.resetPassword(email, verifyToken, codeStr, newPassword); setStep(1); setError(''); setPassword(''); setNewPassword(''); // Show success ytAlert(t('auth.resetSuccess')); } catch (e) { setError(e.message || t('auth.errResetFailed')); } finally { setLoading(false); } }; /* ── Google OAuth ─────────────────────────────────────── */ const handleGmail = () => { window.location.href = window.YiteAPI.auth.gmailLoginUrl(); }; /* ── 渲染 ─────────────────────────────────────────────── */ return (
{/* LEFT: 视觉面板 — hidden on mobile */} {!isMobile && (
{t('auth.whatsInside')}

{t('auth.tagline1')}
{t('auth.tagline2')}
{t('auth.tagline3')}

{[ ['image', t('auth.pitch1')], ['layers', t('auth.pitch2')], ['lock', t('auth.pitch3')], ].map(([ic, txt]) => (
{txt}
))}
)} {/* RIGHT: 表单 */}
{/* 错误提示 */} {error && (
{error}
)} {step === 1 && ( <>

{mode === 'signup' ? t('auth.createAccount') : t('auth.welcomeBack')}

{mode === 'signup' ? t('auth.startFree') : t('auth.signInDesc')}

{t('auth.orEmail')}
); }; Object.assign(window, { AuthScreen });