import { useEffect, useRef, useState } from 'react'; import { useAuthStore } from '../stores/authStore'; import { BrandMarkIcon, SpinnerIcon } from './icons'; export default function LoginPage() { const login = useAuthStore(s => s.login); const error = useAuthStore(s => s.error); const retryAfter = useAuthStore(s => s.retryAfter); const submitting = useAuthStore(s => s.submitting); const clearError = useAuthStore(s => s.clearError); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [countdown, setCountdown] = useState(0); const userRef = useRef(null); useEffect(() => { userRef.current?.focus(); }, []); // 被限速时倒计时 useEffect(() => { if (!retryAfter) return; setCountdown(retryAfter); const t = setInterval(() => { setCountdown(c => { if (c <= 1) { clearInterval(t); clearError(); return 0; } return c - 1; }); }, 1000); return () => clearInterval(t); }, [retryAfter]); const locked = countdown > 0; const canSubmit = username.trim() !== '' && password !== '' && !submitting && !locked; const submit = async (e: React.FormEvent) => { e.preventDefault(); if (!canSubmit) return; const ok = await login(username, password); if (!ok) setPassword(''); }; // 卡片高约 371px,比横屏手机(或软键盘弹出后)的可视高度还高。 // // 居中用卡片自己的 `my-auto` 而**不是**容器的 `items-center`:后者在内容超高时 // 会让卡片上下同时溢出,而溢出到顶部那段滚不到(scrollTop 最小是 0)—— // 实测 568x280 下「登录」按钮完全在视口外,光加 overflow-y-auto 也够不着。 // auto margin 在空间不足时自动退化为 0,于是矮屏变成正常的顶对齐可滚布局。 return (

AgentMail

邮件驱动的多智能体协作平台

{ setUsername(e.target.value); if (error) clearError(); }} autoComplete="username" spellCheck={false} className="w-full text-sm border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400" />
{ setPassword(e.target.value); if (error) clearError(); }} autoComplete="current-password" className="w-full text-sm border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400" />
{error && (

{error} {locked && `(${countdown} 秒后可重试)`}

)}
); }