Files
ai-contract-platform/yuqei-contract-platform/apps/contract-web/src/app/login/page.tsx
T

117 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { Building2, LogIn, UserPlus } from "lucide-react";
import { PasswordInput } from "@/components/password-input";
import { completeAuthenticatedEntry } from "@/lib/auth-entry-routing";
import { getIdentityCapabilities, type IdentityCapabilities } from "@/lib/identity-capabilities";
export default function LoginPage() {
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState<string | null>(null);
const [capabilities, setCapabilities] = useState<IdentityCapabilities | null>(null);
const [draft, setDraft] = useState({
email: "",
password: "",
});
useEffect(() => {
getIdentityCapabilities().then(setCapabilities).catch(() => setCapabilities(null));
}, []);
async function submit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setSaving(true);
setMessage(null);
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: draft.email,
password: draft.password,
}),
});
if (!response.ok) {
throw new Error(await readError(response));
}
completeAuthenticatedEntry((pathname) => window.location.replace(pathname));
} catch (error) {
setMessage(error instanceof Error ? error.message : "登录失败。");
} finally {
setSaving(false);
}
}
return (
<div className="page-layout auth-page login-auth-page">
<div className="page-heading auth-page-heading">
<div>
<p className="eyebrow">Workspace access</p>
<h1>登录工作空间</h1>
<p className="muted">使用邮箱登录,进入你的个人或组织工作空间。</p>
</div>
{capabilities?.registration_enabled && (
<Link className="button button-soft" href="/register">
<UserPlus size={16} />
注册
</Link>
)}
</div>
<div className="login-auth-body">
<div className="login-auth-stack">
{message && <div className="empty-state identity-message">{message}</div>}
<section className="panel auth-panel">
<div className="panel-header">
<div>
<p className="eyebrow">Account</p>
<h2 className="panel-title">账号登录</h2>
</div>
<Building2 size={20} />
</div>
<form className="panel-body identity-form" onSubmit={submit}>
<input
value={draft.email}
onChange={(event) => setDraft((value) => ({ ...value, email: event.target.value }))}
placeholder="用户邮箱"
type="email"
required
/>
<PasswordInput
autoComplete="current-password"
value={draft.password}
onChange={(event) => setDraft((value) => ({ ...value, password: event.target.value }))}
placeholder="登录密码"
required
/>
{capabilities?.password_reset_enabled && (
<div className="auth-inline-actions">
<Link href="/password-reset">忘记密码?</Link>
</div>
)}
<button className="button button-primary" disabled={saving} type="submit">
<LogIn size={16} />
{saving ? "正在登录..." : "进入系统"}
</button>
</form>
</section>
</div>
</div>
</div>
);
}
async function readError(response: Response) {
if (response.status === 401) {
return "邮箱或密码错误,请检查后重试。";
}
if (response.status === 429) {
return "登录尝试过于频繁,请稍后再试。";
}
const payload = await response.json().catch(() => null) as { detail?: string; message?: string } | null;
return payload?.detail ?? payload?.message ?? "请求失败。";
}