100 lines
3.9 KiB
TypeScript
100 lines
3.9 KiB
TypeScript
import { type FormEvent, useState } from "react";
|
|
|
|
import type { Route } from "./+types/login";
|
|
import { useAuth } from "../context/AuthContext";
|
|
|
|
export function meta({}: Route.MetaArgs) {
|
|
return [
|
|
{ title: "登录 · 模型实验开发平台" },
|
|
{ name: "description", content: "登录模型实验开发平台" },
|
|
];
|
|
}
|
|
|
|
export default function LoginRoute() {
|
|
const { login } = useAuth();
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const submit = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
if (!username.trim() || !password) {
|
|
setError("请输入用户名和密码");
|
|
return;
|
|
}
|
|
setBusy(true);
|
|
setError(null);
|
|
try {
|
|
await login(username.trim(), password);
|
|
} catch (cause) {
|
|
setError(cause instanceof Error ? cause.message : "登录失败,请稍后重试");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<main className="flex min-h-screen items-center justify-center bg-[radial-gradient(circle_at_20%_15%,rgba(22,119,255,0.12),transparent_34%),linear-gradient(145deg,#eef5fb,#f8fafc_55%,#edf3f8)] p-12">
|
|
<section
|
|
aria-labelledby="login-title"
|
|
className="w-[420px] rounded-[18px] border border-[#dbe6ef] bg-white/[0.96] p-[42px] shadow-xl"
|
|
>
|
|
<div
|
|
aria-hidden="true"
|
|
className="grid h-[46px] w-[46px] place-items-center rounded-[13px] bg-[linear-gradient(145deg,#1177e8,#25a1f2)] text-white"
|
|
>
|
|
◆
|
|
</div>
|
|
<p className="mb-[8px] mt-[26px] text-[11px] font-[750] tracking-[0.14em] text-[#1677ff]">
|
|
MODEL EXPERIMENT PLATFORM
|
|
</p>
|
|
<h1 id="login-title" className="m-0 text-[27px]">
|
|
模型实验开发平台
|
|
</h1>
|
|
<p className="mb-[28px] mt-[10px] text-[#728398]">
|
|
使用平台账号登录并选择你的 Workspace。
|
|
</p>
|
|
<form onSubmit={(event) => void submit(event)} className="grid gap-[18px]">
|
|
<label className="grid gap-2 text-[13px] text-[#465b70]">
|
|
<span>用户名</span>
|
|
<input
|
|
autoComplete="username"
|
|
autoFocus
|
|
value={username}
|
|
onChange={(event) => setUsername(event.target.value)}
|
|
placeholder="请输入用户名"
|
|
disabled={busy}
|
|
className="w-full rounded-[9px] border border-[#cad8e5] bg-white px-[14px] py-[12px] text-[#18334f] outline-none placeholder:text-[#728398] focus:border-[#1677ff] focus:ring-3 focus:ring-brand/10 disabled:opacity-65 disabled:cursor-not-allowed"
|
|
/>
|
|
</label>
|
|
<label className="grid gap-2 text-[13px] text-[#465b70]">
|
|
<span>密码</span>
|
|
<input
|
|
type="password"
|
|
autoComplete="current-password"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
placeholder="请输入密码"
|
|
disabled={busy}
|
|
className="w-full rounded-[9px] border border-[#cad8e5] bg-white px-[14px] py-[12px] text-[#18334f] outline-none placeholder:text-[#728398] focus:border-[#1677ff] focus:ring-3 focus:ring-brand/10 disabled:opacity-65 disabled:cursor-not-allowed"
|
|
/>
|
|
</label>
|
|
{error && (
|
|
<p role="alert" className="-mt-1 mb-0 text-[13px] text-[#d4380d]">
|
|
{error}
|
|
</p>
|
|
)}
|
|
<button
|
|
type="submit"
|
|
disabled={busy}
|
|
className="min-h-[44px] cursor-pointer rounded-[9px] border-0 bg-[#1677ff] text-white disabled:cursor-wait disabled:opacity-65"
|
|
>
|
|
{busy ? "正在登录…" : "登录"}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|