feat: enhance wallet functionality and improve error handling

- Updated .env.example to include optional player site code configuration.
- Modified error messages in hall-bet-errors for better clarity.
- Improved the betting grid logic to handle invalid draw IDs and reload draws when necessary.
- Adjusted wallet components to differentiate between credit and main wallet balances, including new translations for credit-related terms.
- Enhanced wallet logs to support credit activity tracking and improved UI for displaying credit information.
This commit is contained in:
2026-06-05 18:01:11 +08:00
parent 3cd87ce014
commit 36adf8699d
21 changed files with 496 additions and 80 deletions

View File

@@ -0,0 +1,115 @@
"use client";
import { Loader2 } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { getPlayerMe } from "@/api/player";
import { postPlayerAuthLogin } from "@/api/player-auth";
import { getPublicCurrencies } from "@/api/currency";
import { LanguageSwitcher } from "@/components/language-switcher";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import { LotteryApiBizError } from "@/types/api/errors";
const DEPLOY_SITE_CODE = process.env.NEXT_PUBLIC_PLAYER_SITE_CODE?.trim() ?? "";
export function PlayerLoginScreen(): React.ReactElement {
const { t } = useTranslation("entry");
const router = useRouter();
const setBearerToken = usePlayerSessionStore((s) => s.setBearerToken);
const setProfile = usePlayerSessionStore((s) => s.setProfile);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
async function handleSubmit(event: React.FormEvent): Promise<void> {
event.preventDefault();
if (!username.trim() || !password) {
toast.error(t("login.missingFields", { defaultValue: "请填写账号和密码" }));
return;
}
setLoading(true);
try {
const data = await postPlayerAuthLogin({
...(DEPLOY_SITE_CODE !== "" ? { site_code: DEPLOY_SITE_CODE } : {}),
username: username.trim(),
password,
});
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", { defaultValue: "登录失败" }));
} finally {
setLoading(false);
}
}
return (
<div className="relative flex min-h-dvh flex-col bg-white">
<div className="relative h-[32vh] min-h-[220px] bg-red-600">
<Image src="/entry/image1.png" alt="" fill className="object-cover object-center" priority />
<div className="absolute left-0 right-0 top-0 z-20 flex items-center px-4 py-3">
<LanguageSwitcher variant="header" showFlag={false} />
</div>
</div>
<div className="mx-auto w-full max-w-md flex-1 px-4 py-8">
<h1 className="text-xl font-bold text-gray-900">
{t("login.title", { defaultValue: "代理玩家登录" })}
</h1>
<p className="mt-2 text-sm text-muted-foreground">
{t("login.hint", { defaultValue: "使用代理为您开通的账号登录。主站用户请从主站进入。" })}
</p>
<form className="mt-6 space-y-4" onSubmit={(e) => void handleSubmit(e)}>
<div className="space-y-1">
<Label htmlFor="login-user">{t("login.username", { defaultValue: "登录账号" })}</Label>
<Input
id="login-user"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username"
/>
</div>
<div className="space-y-1">
<Label htmlFor="login-pass">{t("login.password", { defaultValue: "密码" })}</Label>
<Input
id="login-pass"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
/>
</div>
<Button type="submit" className="w-full bg-red-600 hover:bg-red-700" disabled={loading}>
{loading ? <Loader2 className="mr-2 size-4 animate-spin" /> : null}
{t("login.submit", { defaultValue: "登录" })}
</Button>
</form>
<p className="mt-4 text-center text-sm text-muted-foreground">
<Link href="/" className="text-red-600 underline">
{t("login.backEntry", { defaultValue: "返回入口" })}
</Link>
</p>
</div>
</div>
);
}