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:
115
src/features/player/player-login-screen.tsx
Normal file
115
src/features/player/player-login-screen.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user