"use client"; import { useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { getPlayerMe } from "@/api/player"; import { getPlayerAuthCaptcha, postPlayerAuthLogin } from "@/api/player-auth"; import { getPublicCurrencies } from "@/api/currency"; import { LanguageSwitcher } from "@/components/language-switcher"; import { EntryHeroBanner } from "@/features/player/entry-hero-banner"; import { PlayerLoginDesktop } from "@/features/player/player-login-desktop"; import { PlayerLoginForm } from "@/features/player/player-login-form"; import { usePlayerSessionStore } from "@/stores/player-session-store"; import { validatePlayerLoginPassword, validatePlayerLoginUsername, } from "@/lib/player-input-validation"; import { LotteryApiBizError } from "@/types/api/errors"; import "@/i18n"; function stripSearchParamFromBrowserUrl(name: string): void { if (typeof window === "undefined") return; const url = new URL(window.location.href); if (!url.searchParams.has(name)) return; url.searchParams.delete(name); const next = `${url.pathname}${url.search}${url.hash}`; window.history.replaceState(null, "", next); } export function PlayerLoginScreen(): React.ReactElement { const { t } = useTranslation("entry"); const tRef = useRef(t); useEffect(() => { tRef.current = t; }, [t]); const router = useRouter(); const searchParams = useSearchParams(); const setBearerToken = usePlayerSessionStore((s) => s.setBearerToken); const setProfile = usePlayerSessionStore((s) => s.setProfile); const clearBearerToken = usePlayerSessionStore((s) => s.clearBearerToken); const sessionExpiredHandled = useRef(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [captchaCode, setCaptchaCode] = useState(""); const [captchaKey, setCaptchaKey] = useState(null); const [captchaSrc, setCaptchaSrc] = useState(null); const [loadingCaptcha, setLoadingCaptcha] = useState(false); const [loading, setLoading] = useState(false); const loadCaptcha = useCallback(async () => { setLoadingCaptcha(true); try { const data = await getPlayerAuthCaptcha(); setCaptchaKey(data.captcha_key); setCaptchaSrc(`data:image/svg+xml;base64,${data.image_base64}`); setCaptchaCode(""); } catch { toast.error(tRef.current("login.captchaLoadFailed")); setCaptchaKey(null); setCaptchaSrc(null); } finally { setLoadingCaptcha(false); } }, []); useEffect(() => { let cancelled = false; void (async () => { if (cancelled) { return; } await loadCaptcha(); })(); return () => { cancelled = true; }; }, [loadCaptcha]); useEffect(() => { if (sessionExpiredHandled.current) return; if (searchParams.get("session") !== "expired") return; sessionExpiredHandled.current = true; clearBearerToken(); toast.error(t("errors.sessionExpired")); stripSearchParamFromBrowserUrl("session"); }, [clearBearerToken, searchParams, t]); async function handleSubmit(event: React.FormEvent): Promise { event.preventDefault(); if (!username.trim() || !password) { toast.error(t("login.missingFields")); return; } if (!captchaKey || !captchaSrc) { toast.error(t("login.captchaRequired")); void loadCaptcha(); return; } if (!captchaCode.trim()) { toast.error(t("login.captchaRequired")); return; } const usernameIssue = validatePlayerLoginUsername(username); if (usernameIssue === "invalid_charset") { toast.error(t("login.usernameInvalidCharset")); return; } const passwordIssue = validatePlayerLoginPassword(password); if (passwordIssue === "too_short") { toast.error(t("login.passwordMinLength")); return; } setLoading(true); try { const data = await postPlayerAuthLogin({ username: username.trim(), password, captcha_key: captchaKey, captcha_code: captchaCode.trim(), }); setBearerToken(data.access_token); const me = await getPlayerMe(); setProfile(me); try { const currencies = await getPublicCurrencies(); usePlayerSessionStore.getState().setCurrencies(currencies.items); } catch { // 不阻断登录 } router.replace("/hall"); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("login.failed")); void loadCaptcha(); } finally { setLoading(false); } } const formProps = { t, username, password, captchaCode, captchaSrc, loading, loadingCaptcha, onUsernameChange: setUsername, onPasswordChange: setPassword, onCaptchaCodeChange: setCaptchaCode, onRefreshCaptcha: () => void loadCaptcha(), onSubmit: (event: React.FormEvent) => void handleSubmit(event), }; return ( <>

{t("login.title")}

{t("login.hint")}

); }