import { useEffect, useRef, useState } from 'react'; import { useAuthStore } from '../stores/authStore'; import * as api from '../api/client'; import { MailboxIcon, SpinnerIcon } from './icons'; /** 首次初始化向导:系统无任何用户时展示,创建首个管理员 */ export default function SetupPage({ onDone }: { onDone: () => void }) { const bootstrap = useAuthStore(s => s.bootstrap); const [username, setUsername] = useState('admin'); const [displayName, setDisplayName] = useState(''); const [password, setPassword] = useState(''); const [confirm, setConfirm] = useState(''); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const nameRef = useRef(null); useEffect(() => { nameRef.current?.focus(); }, []); const mismatch = confirm !== '' && password !== confirm; const ok = username.trim().length >= 2 && password.length >= 8 && !mismatch && !busy; const submit = async (e: React.FormEvent) => { e.preventDefault(); if (!ok) return; setBusy(true); setError(null); try { await api.setupAdmin({ username: username.trim().toLowerCase(), password, display_name: displayName.trim() }); // 初始化后直接登录(后端已经种了 cookie),拉取用户态 await bootstrap(); onDone(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); setBusy(false); } }; // 卡片高约 371px,比横屏手机(或软键盘弹出后)的可视高度还高。 // // 居中用卡片自己的 `my-auto` 而**不是**容器的 `items-center`:后者在内容超高时 // 会让卡片上下同时溢出,而溢出到顶部那段滚不到(scrollTop 最小是 0)—— // 实测 568x280 下「登录」按钮完全在视口外,光加 overflow-y-auto 也够不着。 // auto margin 在空间不足时自动退化为 0,于是矮屏变成正常的顶对齐可滚布局。 return (

初始化系统

这是系统首次启动。创建一个管理员账号以开始使用 AgentMail。

setUsername(e.target.value)} placeholder="admin" spellCheck={false} className="w-full text-sm font-mono border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400" />

小写字母数字 . _ -,2-64 位;后续可以 `admin@.new` 形式作为收件人

setDisplayName(e.target.value)} placeholder="系统管理员" 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)} autoComplete="new-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" />
setConfirm(e.target.value)} autoComplete="new-password" className={`w-full text-sm border rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-100 ${ mismatch ? 'border-red-300' : 'border-gray-300 focus:border-blue-400' }`} /> {mismatch &&

两次输入的密码不一致

}
{error && (

{error}

)}
); }