114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { startTransition, useMemo, useState, type FormEvent } from "react";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import type { UserRole } from "@/lib/types";
|
|
|
|
interface LoginResponse {
|
|
ok?: boolean;
|
|
error?: string;
|
|
user?: {
|
|
role: UserRole;
|
|
};
|
|
}
|
|
|
|
function getErrorMessage(error: unknown, fallbackMessage: string): string {
|
|
return error instanceof Error ? error.message : fallbackMessage;
|
|
}
|
|
|
|
export default function LoginForm() {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [errorMessage, setErrorMessage] = useState("");
|
|
const [isPending, setIsPending] = useState(false);
|
|
|
|
const notice = useMemo(() => {
|
|
const reason = searchParams.get("reason");
|
|
|
|
if (reason === "inactive") {
|
|
return "当前账号不可用,请联系管理员。";
|
|
}
|
|
|
|
return "使用管理员或已创建的普通用户登录。";
|
|
}, [searchParams]);
|
|
|
|
async function handleSubmit(
|
|
event: FormEvent<HTMLFormElement>,
|
|
): Promise<void> {
|
|
event.preventDefault();
|
|
setErrorMessage("");
|
|
setIsPending(true);
|
|
|
|
try {
|
|
const response = await fetch("/api/auth/login", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
username,
|
|
password,
|
|
}),
|
|
});
|
|
const payload = (await response.json()) as LoginResponse;
|
|
|
|
if (!response.ok || payload.ok !== true || !payload.user) {
|
|
throw new Error(payload.error || "登录失败,请稍后重试。");
|
|
}
|
|
|
|
const currentUser = payload.user;
|
|
|
|
startTransition(() => {
|
|
router.push(currentUser.role === "admin" ? "/admin" : "/studio");
|
|
router.refresh();
|
|
});
|
|
} catch (error) {
|
|
setErrorMessage(getErrorMessage(error, "登录失败,请稍后重试。"));
|
|
} finally {
|
|
setIsPending(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form className="login-form" onSubmit={handleSubmit}>
|
|
<div className="login-copy">
|
|
<p className="eyebrow">Secure Access</p>
|
|
<h1>登录后才能使用图片生成与后台管理。</h1>
|
|
<p>{notice}</p>
|
|
</div>
|
|
|
|
<label className="field">
|
|
<span>用户名</span>
|
|
<input
|
|
type="text"
|
|
autoComplete="username"
|
|
placeholder="admin"
|
|
value={username}
|
|
onChange={(event) => setUsername(event.target.value)}
|
|
required
|
|
/>
|
|
</label>
|
|
|
|
<label className="field">
|
|
<span>密码</span>
|
|
<input
|
|
type="password"
|
|
autoComplete="current-password"
|
|
placeholder="输入密码"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
required
|
|
/>
|
|
</label>
|
|
|
|
{errorMessage ? <div className="error-banner">{errorMessage}</div> : null}
|
|
|
|
<button type="submit" className="primary-button" disabled={isPending}>
|
|
{isPending ? "登录中..." : "登录"}
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|