Some checks failed
lotteryfront CI / build (push) Has been cancelled
- Updated `getPublicCurrencies` and `getPlayerAuthCaptcha` functions to include `skipGlobalServerError` option, improving error handling for API requests. - Refactored the NotFoundPage component to enhance mobile responsiveness and UI consistency, integrating `PlayerMobileViewport`. - Improved the NetworkStatusBanner and OfflineBanner components by adding player banner height reference for better layout management. - Updated Wallet components to utilize `PlayerMoneyDisplay` for consistent currency representation and added credit mode handling in various screens. - Enhanced the WalletScreen and Transfer screens with loading panels and credit guard logic for better user experience during wallet operations.
153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
"use client";
|
||
|
||
import { Wallet } from "lucide-react";
|
||
import Image from "next/image";
|
||
import { useEffect, useRef } from "react";
|
||
import { useTranslation } from "react-i18next";
|
||
import { useSWRConfig } from "swr";
|
||
|
||
import { getWalletBalance } from "@/api/wallet";
|
||
import { PlayerMoneyDisplay } from "@/components/player-money-display";
|
||
import { Skeleton } from "@/components/ui/skeleton";
|
||
import {
|
||
TransferInDialog,
|
||
TransferOutDialog,
|
||
} from "@/features/wallet/wallet-transfer-dialogs";
|
||
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
|
||
import { useApiQuery } from "@/hooks/use-api-query";
|
||
import { formatMinorAsCurrency } from "@/lib/money";
|
||
import { isCreditFundingPlayer } from "@/lib/player-funding-mode";
|
||
import { PLAYER_CURRENCY_CHANGE_EVENT } from "@/lib/player-currency-preference";
|
||
import { cn } from "@/lib/utils";
|
||
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
|
||
|
||
const BALANCE_KEY = (currency: string) => ["wallet/balance", currency];
|
||
|
||
export function HallWalletStrip() {
|
||
const mode = useNetworkConnectionStore((s) => s.mode);
|
||
const { t } = useTranslation("player");
|
||
const { activeCurrency } = useActivePlayerCurrency();
|
||
const { mutate } = useSWRConfig();
|
||
const degradedWalletPollRef = useRef<number | null>(null);
|
||
|
||
const currency = activeCurrency;
|
||
const isDegraded = mode === "polling" || mode === "offline";
|
||
|
||
const { data: balance, isLoading: loading } = useApiQuery(
|
||
BALANCE_KEY(currency),
|
||
() => getWalletBalance({ currency }),
|
||
{
|
||
// 降级模式下 60 秒自动刷新,SWR 内置定时器代替手动 setInterval
|
||
refreshInterval: isDegraded ? 60_000 : undefined,
|
||
},
|
||
);
|
||
|
||
// 全局事件触发刷新(下注后、币种切换等场景)
|
||
useEffect(() => {
|
||
const onRefresh = () => void mutate(BALANCE_KEY(currency));
|
||
window.addEventListener("lottery-wallet-refresh", onRefresh);
|
||
window.addEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
|
||
return () => {
|
||
window.removeEventListener("lottery-wallet-refresh", onRefresh);
|
||
window.removeEventListener(PLAYER_CURRENCY_CHANGE_EVENT, onRefresh);
|
||
};
|
||
}, [mutate, currency]);
|
||
|
||
// 非降级模式时清理遗留计时器(refreshInterval 已由 SWR 管理)
|
||
useEffect(() => {
|
||
if (!isDegraded && degradedWalletPollRef.current !== null) {
|
||
window.clearInterval(degradedWalletPollRef.current);
|
||
degradedWalletPollRef.current = null;
|
||
}
|
||
}, [isDegraded]);
|
||
|
||
const availableMinor = Number(balance?.available_balance ?? balance?.balance ?? 0);
|
||
const isCreditPlayer = isCreditFundingPlayer(balance);
|
||
const balanceMinor = Number(balance?.balance ?? 0);
|
||
const headlineMinor = isCreditPlayer ? availableMinor : balanceMinor;
|
||
const transferInLotteryMinor = isCreditPlayer ? availableMinor : balanceMinor;
|
||
const mainMinor =
|
||
balance?.main_balance === null || balance?.main_balance === undefined
|
||
? null
|
||
: Number(balance.main_balance);
|
||
|
||
return (
|
||
<section
|
||
className="mb-3 space-y-2.5"
|
||
aria-label={
|
||
isCreditPlayer
|
||
? t("wallet.creditAvailable", { defaultValue: "可用信用" })
|
||
: t("wallet.balance")
|
||
}
|
||
>
|
||
<div
|
||
className={cn(
|
||
"relative overflow-hidden rounded-xl bg-[#e5002c] px-3 py-3 text-white shadow-[0_10px_28px_rgba(229,0,44,0.25)]",
|
||
)}
|
||
>
|
||
<Image
|
||
src="/entry/image5.png"
|
||
alt=""
|
||
fill
|
||
className="pointer-events-none object-cover object-center"
|
||
aria-hidden
|
||
/>
|
||
<div className="relative flex items-center gap-3">
|
||
<div className="flex size-14 shrink-0 items-center justify-center rounded-full bg-white text-[#d81435] shadow-sm">
|
||
<Wallet className="size-7" aria-hidden />
|
||
</div>
|
||
<div className="min-w-0 flex-1">
|
||
<p className="text-sm font-semibold text-white/90">
|
||
{isCreditPlayer
|
||
? t("wallet.creditAvailable", { defaultValue: "可用信用" })
|
||
: t("wallet.balance")}
|
||
</p>
|
||
{loading ? (
|
||
<Skeleton className="mt-2 h-8 w-44 rounded-md bg-white/25" />
|
||
) : (
|
||
<PlayerMoneyDisplay
|
||
amountMinor={headlineMinor}
|
||
currency={currency}
|
||
className="mt-1 text-white"
|
||
/>
|
||
)}
|
||
{isCreditPlayer && !loading && balance ? (
|
||
<p className="mt-2 text-xs text-white/75">
|
||
{t("wallet.creditSummary", {
|
||
defaultValue: "授信 {{limit}} · 已用 {{used}}",
|
||
limit: formatMinorAsCurrency(balance.credit_limit ?? 0, currency),
|
||
used: formatMinorAsCurrency(balance.used_credit ?? 0, currency),
|
||
})}
|
||
</p>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{isCreditPlayer ? null : (
|
||
<div className="grid grid-cols-2 gap-2.5">
|
||
<TransferInDialog
|
||
idPrefix="hall-"
|
||
triggerVariant="hall"
|
||
triggerLabel={t("wallet.transferIn")}
|
||
triggerClassName="h-12 rounded-lg text-base font-bold"
|
||
currency={currency}
|
||
lotteryMinor={transferInLotteryMinor}
|
||
mainMinor={mainMinor}
|
||
onSuccess={async () => { await mutate(BALANCE_KEY(currency)); }}
|
||
/>
|
||
<TransferOutDialog
|
||
idPrefix="hall-"
|
||
triggerVariant="hall"
|
||
triggerLabel={t("wallet.transferOut")}
|
||
triggerClassName="h-12 rounded-lg text-base font-bold"
|
||
currency={currency}
|
||
availableMinor={availableMinor}
|
||
onSuccess={async () => { await mutate(BALANCE_KEY(currency)); }}
|
||
/>
|
||
</div>
|
||
)}
|
||
</section>
|
||
);
|
||
}
|