Files
gitlab-instance-0a899031_no…/app/vip/components/landing/LoginForm.tsx
2026-03-27 18:20:36 -05:00

248 lines
6.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, useRef, useState, type FormEvent } from "react";
import styles from "../vip.module.css";
type LoginFormProps = {
onVerify: (userId: string) => Promise<boolean>;
onVerifyByEmail?: (
email: string,
password: string,
user_id?: string
) => Promise<boolean | { ok: boolean; error?: string }>;
onSuccess: () => void;
autoFocus?: boolean;
/** 验证请求进行中(用于全屏 loading 等) */
onBusyChange?: (busy: boolean) => void;
};
export function LoginForm({
onVerify,
onVerifyByEmail,
onSuccess,
autoFocus,
onBusyChange,
}: LoginFormProps) {
const [mode, setMode] = useState<"user_id" | "email">("user_id");
const [userId, setUserId] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const getCandidateUserId = (): string => {
if (typeof window === "undefined") return "";
const fromMemos = localStorage.getItem("memosAccount")?.trim() || "";
if (/^user\d+$/.test(fromMemos)) return fromMemos;
const fromUserId = localStorage.getItem("userId")?.trim() || "";
if (/^user\d+$/.test(fromUserId)) return fromUserId;
return "";
};
useEffect(() => {
if (autoFocus) inputRef.current?.focus();
}, [autoFocus, mode]);
useEffect(() => {
onBusyChange?.(loading);
}, [loading, onBusyChange]);
const handleUserIdSubmit = async (e: FormEvent) => {
e.preventDefault();
const trimmed = userId.trim();
if (!trimmed) {
setError("请输入您的账号");
return;
}
setError(null);
setLoading(true);
let succeeded = false;
try {
const ok = await onVerify(trimmed);
if (ok) {
succeeded = true;
onSuccess();
} else {
setError("未找到该账号的会员记录,请确认账号正确");
}
} catch {
setError("验证失败,请稍后重试");
} finally {
if (!succeeded) setLoading(false);
}
};
const handleEmailSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!onVerifyByEmail) return;
const trimmedEmail = email.trim();
const trimmedPassword = password.trim();
if (!trimmedEmail) {
setError("请输入邮箱");
return;
}
if (!trimmedPassword) {
setError("请输入密码");
return;
}
setError(null);
setLoading(true);
let succeeded = false;
try {
const candidateUserId = getCandidateUserId();
const result = await onVerifyByEmail(
trimmedEmail,
trimmedPassword,
candidateUserId || undefined
);
const ok = typeof result === "object" ? result.ok : result;
const errMsg = typeof result === "object" ? result.error : undefined;
if (ok) {
succeeded = true;
onSuccess();
} else {
setError(errMsg || "邮箱或密码错误,或未找到关联的会员记录");
}
} catch {
setError("验证失败,请稍后重试");
} finally {
if (!succeeded) setLoading(false);
}
};
const inputStyle = {
width: "100%" as const,
padding: "0.75rem 1rem",
fontSize: "1rem",
border: "1px solid var(--border)",
borderRadius: "8px",
background: "var(--background)",
color: "var(--foreground)",
marginBottom: "0.75rem",
};
return (
<>
<div className="mb-3 flex gap-2">
<button
type="button"
onClick={() => {
setMode("user_id");
setError(null);
}}
className={`flex-1 rounded-lg py-2 text-sm font-medium ${
mode === "user_id"
? "bg-[var(--primary)] text-[var(--primary-foreground)]"
: "bg-[var(--muted)] text-[var(--muted-foreground)]"
}`}
>
</button>
<button
type="button"
onClick={() => {
setMode("email");
setError(null);
}}
className={`flex-1 rounded-lg py-2 text-sm font-medium ${
mode === "email"
? "bg-[var(--primary)] text-[var(--primary-foreground)]"
: "bg-[var(--muted)] text-[var(--muted-foreground)]"
}`}
>
</button>
</div>
{mode === "user_id" ? (
<form onSubmit={handleUserIdSubmit}>
<input
ref={inputRef}
type="text"
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="例如user1773315608560"
disabled={loading}
style={inputStyle}
/>
{error && (
<p style={{ fontSize: "0.85rem", color: "#ef4444", marginBottom: "0.75rem" }}>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className={`${styles.btnPrimary} ${styles.btnLarge}`}
style={{ width: "100%" }}
>
{loading ? "验证中..." : "验证并登录"}
</button>
</form>
) : onVerifyByEmail ? (
<form onSubmit={handleEmailSubmit}>
<input
ref={inputRef}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
disabled={loading}
style={inputStyle}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="请输入密码"
disabled={loading}
style={inputStyle}
/>
{error && (
<p style={{ fontSize: "0.85rem", color: "#ef4444", marginBottom: "0.75rem" }}>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className={`${styles.btnPrimary} ${styles.btnLarge}`}
style={{ width: "100%" }}
>
{loading ? "验证中..." : "验证并登录"}
</button>
</form>
) : (
<form onSubmit={handleUserIdSubmit}>
<input
ref={inputRef}
type="text"
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="例如user1773315608560"
disabled={loading}
style={inputStyle}
/>
{error && (
<p style={{ fontSize: "0.85rem", color: "#ef4444", marginBottom: "0.75rem" }}>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className={`${styles.btnPrimary} ${styles.btnLarge}`}
style={{ width: "100%" }}
>
{loading ? "验证中..." : "验证并登录"}
</button>
</form>
)}
</>
);
}