Files
NianAIGC/components/auth-login-panel.tsx
2026-05-29 15:54:13 +08:00

143 lines
4.9 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, useMemo, useRef, useState } from "react";
import type { FormEvent } from "react";
import Image from "next/image";
import { Loader2, LogIn, RefreshCw } from "lucide-react";
import { pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion";
export function AuthLoginPanel({
next,
configured,
message,
missing
}: {
next: string;
configured: boolean;
message?: string | null;
missing?: string[];
}) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [code, setCode] = useState("");
const [randomStr, setRandomStr] = useState("");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const screenRef = useRef<HTMLDivElement | null>(null);
const feedbackRef = useRef<HTMLDivElement | null>(null);
const hasMissingConfig = !configured && Boolean(missing?.length);
useEffect(() => {
refreshCaptcha();
}, []);
useEffect(() => {
return runScopedMotion(screenRef, (scope) => revealChildren(scope));
}, []);
useEffect(() => {
pulseFeedback(feedbackRef.current);
}, [error, message, missing?.join(",")]);
const captchaSrc = useMemo(() => {
if (!randomStr) return "";
return `/api/auth/captcha?randomStr=${encodeURIComponent(randomStr)}`;
}, [randomStr]);
function refreshCaptcha() {
setCode("");
setRandomStr(crypto.randomUUID());
}
async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!configured || submitting) return;
setSubmitting(true);
setError(null);
try {
const response = await fetch("/api/auth/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password, code, randomStr, next })
});
const payload = await response.json();
if (!response.ok) throw new Error(payload.error || "登录失败");
window.location.assign(payload.redirectTo || next || "/create");
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
refreshCaptcha();
} finally {
setSubmitting(false);
}
}
return (
<div className="auth-page" ref={screenRef}>
<section className="auth-brand-panel" aria-label="智念AIGC平台" data-animate>
<Image className="auth-brand-logo" src="/logo/zhinian-logo.png" alt="" width={168} height={38} priority />
<h1>AIGC平台</h1>
</section>
<section className="panel auth-panel" data-animate>
<h2></h2>
{message || hasMissingConfig || error ? (
<div ref={feedbackRef}>
{message ? <div className="callout" role="alert">{message}</div> : null}
{hasMissingConfig ? (
<div className="callout" role="alert">
{missing?.join("、")}
</div>
) : null}
{error ? <div className="callout" role="alert">{error}</div> : null}
</div>
) : null}
<form className="auth-password-form" onSubmit={submit}>
<label className="field" data-animate>
<span></span>
<input
autoComplete="username"
value={username}
onChange={(event) => setUsername(event.target.value)}
disabled={!configured || submitting}
placeholder="请输入账号"
/>
</label>
<label className="field" data-animate>
<span></span>
<input
autoComplete="current-password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
disabled={!configured || submitting}
placeholder="请输入密码"
/>
</label>
<label className="field" data-animate>
<span></span>
<div className="auth-captcha-row">
<input
autoComplete="off"
value={code}
onChange={(event) => setCode(event.target.value)}
disabled={!configured || submitting}
placeholder="请输入验证码"
/>
<button className="auth-captcha-button" type="button" onClick={refreshCaptcha} disabled={!configured || submitting} aria-label="刷新验证码" title="刷新验证码">
{captchaSrc ? <img src={captchaSrc} alt="验证码" /> : <RefreshCw size={18} />}
</button>
</div>
</label>
<button className="button primary auth-submit" type="submit" disabled={!configured || submitting || !username.trim() || !password || !code.trim()} data-animate>
{submitting ? <Loader2 className="spin" size={18} /> : <LogIn size={18} />}
</button>
</form>
</section>
</div>
);
}