import { useEffect, useState } from 'react'; import { getMe, getWechatAuthConfig, login, loginLegacy, register, resetPassword, } from '../api/client'; import type { CapabilityMap, PortalUser } from '../types'; import { buildWechatAuthorizeUrl, isMobileExternalBrowser, isWechatContext, isWechatEntryUrl, readWechatAuthError, readWechatPendingToken, } from '../utils/wechat'; import { TKMindAvatar } from './TKMindAvatar'; import { resolvePlazaHomeUrl } from '../utils/publicUrl'; import { WechatBindGate } from './WechatBindGate'; import { WechatDesktopLogin } from './WechatDesktopLogin'; import { WechatIcon } from './WechatIcon'; import { WechatOpenGuide } from './WechatOpenGuide'; type Mode = 'login' | 'register' | 'reset'; const MODE_META: Record = { login: { title: 'TKMind', desc: '登录你的账号' }, register: { title: '创建账号', desc: '填写信息完成注册' }, reset: { title: '重置密码', desc: '验证注册邮箱后设置新密码' }, }; export function AuthView({ legacyMode, onAuth, }: { legacyMode?: boolean; onAuth: ( user?: PortalUser | null, capabilities?: CapabilityMap, grantedSkills?: string[], ) => void; }) { const [mode, setMode] = useState('login'); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [displayName, setDisplayName] = useState(''); const [email, setEmail] = useState(''); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [submitting, setSubmitting] = useState(false); const [wechatEnabled, setWechatEnabled] = useState(false); const [wechatContext, setWechatContext] = useState(() => isWechatContext({ search: window.location.search }), ); const [mobileExternal, setMobileExternal] = useState(() => isMobileExternalBrowser({ search: window.location.search }), ); const [showPasswordLogin, setShowPasswordLogin] = useState(false); const [showScanLogin, setShowScanLogin] = useState(false); const [pendingToken, setPendingToken] = useState(() => readWechatPendingToken()); const searchParams = new URLSearchParams(window.location.search); const fromPlaza = (searchParams.get('utm_source') ?? '').toLowerCase() === 'plaza'; const returnTo = searchParams.get('return_to'); useEffect(() => { const wechatError = readWechatAuthError(); if (wechatError) { setError(wechatError); } }, []); useEffect(() => { if (legacyMode) return; void getWechatAuthConfig().then((config) => { setWechatEnabled(config.enabled); setWechatContext((prev) => isWechatContext({ search: window.location.search, serverInWechat: config.inWechat, }) || prev, ); setMobileExternal( isMobileExternalBrowser({ search: window.location.search, serverInWechat: config.inWechat, }), ); const params = new URLSearchParams(window.location.search); if ( config.enabled && isWechatEntryUrl(window.location.search) && isWechatContext({ search: window.location.search, serverInWechat: config.inWechat, }) && !params.get('wechat_pending') && !params.get('wechat_error') ) { window.location.replace( buildWechatAuthorizeUrl({ returnTo: returnTo ?? undefined, utmSource: params.get('utm_source') ?? params.get('from') ?? 'wechat', utmMedium: params.get('utm_medium') ?? undefined, utmCampaign: params.get('utm_campaign') ?? undefined, intent: 'login', }), ); } }); }, [legacyMode, returnTo]); const isLogin = mode === 'login' && !legacyMode; const showWechatOneClick = isLogin && wechatContext && wechatEnabled; const showMobileWechatGuide = isLogin && mobileExternal && wechatEnabled; const showDesktopWechat = isLogin && wechatEnabled && !wechatContext && !mobileExternal; const isDesktopScanMode = showDesktopWechat && showScanLogin; const showAccountForm = !isDesktopScanMode && (legacyMode || mode !== 'login' || mobileExternal || showPasswordLogin || !showWechatOneClick || showDesktopWechat); const switchToPasswordLogin = () => { setShowScanLogin(false); setError(null); setSuccess(null); }; const switchToScanLogin = () => { setShowScanLogin(true); setError(null); setSuccess(null); }; const switchMode = (next: Mode) => { setMode(next); setError(null); setSuccess(null); setConfirmPassword(''); setShowScanLogin(false); }; const handleWechatLogin = () => { const params = new URLSearchParams(window.location.search); window.location.href = buildWechatAuthorizeUrl({ returnTo: returnTo ?? undefined, utmSource: params.get('utm_source') ?? params.get('from') ?? 'wechat', utmMedium: params.get('utm_medium') ?? undefined, utmCampaign: params.get('utm_campaign') ?? undefined, intent: 'login', }); }; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setSubmitting(true); setError(null); try { if (legacyMode) { await loginLegacy(password); onAuth(null); return; } const user = await login(username, password); const me = await getMe().catch(() => null); onAuth(user, me?.capabilities, me?.grantedSkills); } catch (err) { setError(err instanceof Error ? err.message : '登录失败'); } finally { setSubmitting(false); } }; const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setSubmitting(true); setError(null); try { await register( username, password, displayName || undefined, email, { utm_source: searchParams.get('utm_source') ?? undefined, utm_medium: searchParams.get('utm_medium') ?? undefined, utm_campaign: searchParams.get('utm_campaign') ?? undefined, ref: searchParams.get('ref') ?? undefined, }, ); const user = await login(username, password); const me = await getMe().catch(() => null); sessionStorage.setItem('tkmind_suggest_wechat_bind', '1'); onAuth(user, me?.capabilities, me?.grantedSkills); } catch (err) { setError(err instanceof Error ? err.message : '注册失败'); } finally { setSubmitting(false); } }; const handleReset = async (e: React.FormEvent) => { e.preventDefault(); if (password !== confirmPassword) { setError('两次输入的密码不一致'); return; } setSubmitting(true); setError(null); setSuccess(null); try { await resetPassword(username, email, password); setSuccess('密码已重置,请使用新密码登录'); setPassword(''); setConfirmPassword(''); setTimeout(() => switchMode('login'), 1500); } catch (err) { setError(err instanceof Error ? err.message : '重置失败'); } finally { setSubmitting(false); } }; if (pendingToken) { return ( ); } const meta = legacyMode ? { title: 'TKMind', desc: '内部访问入口' } : fromPlaza ? { ...MODE_META[mode], desc: mode === 'register' ? '注册后将返回 Plaza 继续浏览' : '登录后将返回 Plaza 继续浏览', } : MODE_META[mode]; const plazaBackUrl = (() => { if (!returnTo) return resolvePlazaHomeUrl(); try { return new URL(returnTo, window.location.origin).toString(); } catch { return resolvePlazaHomeUrl(); } })(); const onSubmit = mode === 'register' && !legacyMode ? handleRegister : mode === 'reset' && !legacyMode ? handleReset : handleLogin; const submitLabel = submitting ? '处理中…' : legacyMode ? '解锁' : mode === 'register' ? '注册并登录' : mode === 'reset' ? '确认重置' : '登录'; const submitDisabled = submitting || (legacyMode ? !password.trim() : mode === 'login' ? !username.trim() || !password.trim() : mode === 'register' ? !username.trim() || !password.trim() || !email.trim() : !username.trim() || !email.trim() || !password.trim() || !confirmPassword.trim()); const showAltLogin = isLogin && wechatEnabled && (showMobileWechatGuide || (showDesktopWechat && !showScanLogin)); return (
{!legacyMode && fromPlaza && ( )} {!legacyMode && (
{mode === 'login' ? ( <>
)}

{meta.title}

{meta.desc}

{isDesktopScanMode && ( <> setPendingToken(token)} onComplete={() => window.location.reload()} onError={(message) => setError(message)} /> {error &&

{error}

} {success &&

{success}

} )} {showWechatOneClick && ( <> {!showPasswordLogin ? ( ) : ( )} )} {showAccountForm && ( <>
{!legacyMode && ( setUsername(e.target.value)} /> )} {mode === 'register' && !legacyMode && ( <> setEmail(e.target.value)} /> setDisplayName(e.target.value)} /> )} {mode === 'reset' && !legacyMode && ( setEmail(e.target.value)} /> )} setPassword(e.target.value)} /> {mode === 'reset' && !legacyMode && ( setConfirmPassword(e.target.value)} /> )}
{error &&

{error}

} {success &&

{success}

} )} {showAltLogin && (
{showDesktopWechat && ( )} {showMobileWechatGuide && ( setSuccess('链接已复制,请在微信中打开')} /> )}
)}
); }