import { useEffect, useState, type FormEvent } from 'react'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import * as authApi from '../../api/auth'; import { ApiError } from '../../api/http'; import { DEFAULT_TENANT } from '../../config'; import { useAuth } from '../../context/AuthContext'; import * as permApi from '../../api/permission'; export function LoginPage() { const navigate = useNavigate(); const location = useLocation(); const { syncSession, refreshRoles } = useAuth(); const [tenant, setTenant] = useState( () => localStorage.getItem('tenant_slug') ?? DEFAULT_TENANT, ); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [mfaChallengeId, setMfaChallengeId] = useState(null); const [totpCode, setTotpCode] = useState(''); const [error, setError] = useState(''); const [info, setInfo] = useState(''); const [loading, setLoading] = useState(false); useEffect(() => { const state = location.state as { message?: string } | null; if (state?.message) { setInfo(state.message); navigate(location.pathname, { replace: true, state: null }); } }, [location.pathname, location.state, navigate]); const finishLogin = async () => { syncSession(); await refreshRoles(); const me = await permApi.getMyPermissions(); const admin = permApi.isAdminRole(me.roles ?? []); navigate(admin ? '/admin' : '/app'); }; const submit = async (e: FormEvent) => { e.preventDefault(); setError(''); setLoading(true); try { localStorage.setItem('tenant_slug', tenant); const result = await authApi.login(tenant, email, password); if (result.mfa_required) { if (!result.mfa_challenge_id) { throw new Error('缺少 MFA challenge'); } setMfaChallengeId(result.mfa_challenge_id); return; } await finishLogin(); } catch (err) { if (err instanceof ApiError && err.code === 28505000) { setError('帳號尚未完成 Email 驗證,請先完成註冊驗證。'); } else { setError(err instanceof ApiError ? err.message : '登入失敗'); } } finally { setLoading(false); } }; const submitMfa = async (e: FormEvent) => { e.preventDefault(); if (!mfaChallengeId) return; setError(''); setLoading(true); try { await authApi.loginMfaConfirm(tenant, mfaChallengeId, totpCode); await finishLogin(); } catch (err) { setError(err instanceof ApiError ? err.message : '驗證碼錯誤'); } finally { setLoading(false); } }; const backToPassword = () => { setMfaChallengeId(null); setTotpCode(''); setError(''); }; if (mfaChallengeId) { return (

雙因素驗證

請輸入驗證器 App 的 6 位數驗證碼,或備援碼。

{error &&

{error}

}
); } return (

登入

{error &&

{error}

} {info &&

{info}

}

還沒有帳號? 註冊 {' · '} 尚未完成驗證? {' · '} 忘記密碼?

); }