Files
model-platform/frontend/app/routes/login.tsx
T
2026-08-28 12:00:14 +08:00

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>
);
}