"use client"; import Link from "next/link"; import { useCallback, useEffect, useId, useState, useSyncExternalStore, type FormEvent, type ReactNode } from "react"; import { useMockUser } from "@/components/profile/use-mock-user"; import { ACCOUNT_RULE_HINT, CREDENTIAL_MAX_LEN, isValidCredential, PASSWORD_RULE_HINT, } from "@/lib/auth/credentials"; type Mode = "login" | "register"; let currentMode: Mode | null = null; const listeners = new Set<() => void>(); function setDialogMode(mode: Mode | null) { currentMode = mode; listeners.forEach((listener) => listener()); } export function openLoginDialog(mode: Mode = "login") { setDialogMode(mode); } export function openRegisterDialog() { setDialogMode("register"); } function subscribe(listener: () => void) { listeners.add(listener); return () => listeners.delete(listener); } const getSnapshot = () => currentMode; const getServerSnapshot = () => null; const desktopQuery = "(min-width: 1024px)"; function subscribeDesktop(callback: () => void) { const media = window.matchMedia(desktopQuery); media.addEventListener("change", callback); return () => media.removeEventListener("change", callback); } const getDesktopSnapshot = () => window.matchMedia(desktopQuery).matches; const getDesktopServerSnapshot = () => false; const emptyValues = { account: "", password: "", confirmPassword: "" }; type Field = keyof typeof emptyValues; type Errors = Partial>; function validate(mode: Mode, values: typeof emptyValues): Errors { const errors: Errors = {}; if (!values.account) errors.account = "请输入账号"; else if (!isValidCredential(values.account)) errors.account = ACCOUNT_RULE_HINT; if (!values.password) errors.password = "请输入密码"; else if (!isValidCredential(values.password)) errors.password = mode === "login" ? "密码错误,请重新输入" : PASSWORD_RULE_HINT; if (mode === "register") { if (!values.confirmPassword) errors.confirmPassword = "请再次输入密码"; else if (values.confirmPassword !== values.password) errors.confirmPassword = "两次输入的密码不一致"; } return errors; } function mapAuthError(message: string, mode: Mode): Errors { if (/账户不存在|账号不存在|用户不存在/.test(message)) return { account: message }; if (/已存在|重复|already exists/i.test(message)) return { account: "该账号已被注册" }; if (/数据库|database/i.test(message)) return { account: "注册服务暂时异常,请稍后重试" }; if (/密码|password/i.test(message)) return { password: message }; if (/参数/.test(message)) return { account: message }; return mode === "login" ? { password: message } : { account: message }; } export function LoginDialogHost() { const mode = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); const isDesktop = useSyncExternalStore(subscribeDesktop, getDesktopSnapshot, getDesktopServerSnapshot); const { login, register } = useMockUser(); const [values, setValues] = useState(emptyValues); const [errors, setErrors] = useState({}); const [submitting, setSubmitting] = useState(false); const close = useCallback(() => { if (submitting) return; setValues(emptyValues); setErrors({}); setDialogMode(null); }, [submitting]); useEffect(() => { if (!mode) return; const onKeyDown = (event: KeyboardEvent) => event.key === "Escape" && close(); const { overflow } = document.body.style; document.body.style.overflow = "hidden"; window.addEventListener("keydown", onKeyDown); return () => { document.body.style.overflow = overflow; window.removeEventListener("keydown", onKeyDown); }; }, [mode, close]); if (!mode) return null; const isLogin = mode === "login"; const placeholderClass = isLogin ? "" : "lg:placeholder:text-[#746E85]"; const switchMode = (next: Mode) => { if (submitting) return; setErrors({}); setValues((current) => ({ ...current, confirmPassword: "" })); setDialogMode(next); }; const update = (key: Field) => (value: string) => { setValues((current) => ({ ...current, [key]: value })); setErrors((current) => ({ ...current, [key]: undefined })); }; const submit = async (event: FormEvent) => { event.preventDefault(); const nextErrors = validate(mode, values); setErrors(nextErrors); if (Object.keys(nextErrors).length || submitting) return; setSubmitting(true); try { if (isLogin) await login(values.account, values.password); else await register(values.account, values.password); if (process.env.NODE_ENV === "development") { console.info(isLogin ? "[auth-login]" : "[auth-register]", { account: values.account }); } setValues(emptyValues); setErrors({}); setDialogMode(null); } catch (error) { const message = error instanceof Error ? error.message : isLogin ? "登录失败,请稍后重试" : "注册失败,请稍后重试"; setErrors(mapAuthError(message, mode)); } finally { setSubmitting(false); } }; return (
event.target === event.currentTarget && close()} >
海角社区 {isLogin ? (
switchMode("login")}> 登录 switchMode("register")}> 注册
) : null}

{isLogin ? "登录" : "创建账号"}

{isLogin ? (

登录后同步收藏、历史与会员权益

) : (

注册后同步收藏、历史与会员权益 注册成功即可同步收藏与追剧记录

)}
} placeholder="请输入账号" value={values.account} onChange={update("account")} autoComplete="username" placeholderClass={placeholderClass} error={errors.account} /> } placeholder="请输入密码" value={values.password} onChange={update("password")} autoComplete={isLogin ? "current-password" : "new-password"} secret placeholderClass={placeholderClass} error={errors.password} /> {isLogin ? null : ( } placeholder={isDesktop ? "请再次输入密码" : "确认密码"} value={values.confirmPassword} onChange={update("confirmPassword")} autoComplete="new-password" secret placeholderClass={placeholderClass} error={errors.confirmPassword} /> )}

{isLogin ? "还没有账号?" : "已有账号?"}

{isLogin ? "登录" : "注册"}即代表同意 《用户协议》 和 《隐私政策》

); } function TabButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: ReactNode }) { return ( ); } function AuthField({ label, icon, placeholder, value, onChange, autoComplete, secret = false, placeholderClass = "", error, }: { label: string; icon: ReactNode; placeholder: string; value: string; onChange: (value: string) => void; autoComplete: string; secret?: boolean; placeholderClass?: string; error?: string; }) { const [visible, setVisible] = useState(false); const errorId = useId(); const outlineClass = error ? "outline outline-1 -outline-offset-1 outline-[#FF4A5C]" : "focus-within:outline focus-within:outline-1 focus-within:-outline-offset-1 focus-within:outline-[#F12B9E]/60 lg:outline lg:outline-1 lg:-outline-offset-1 lg:outline-white/10 lg:focus-within:outline-[#F12B9E]/60"; return (
{error ? ( ) : null}
); } function UserIcon() { return ( ); } function LockIcon() { return ( ); }