Files
lotteryFront/src/features/hall/hall-wallet-strip.tsx
kang 7743c14e83 feat: 增强钱包 API 与玩家会话管理
- 新增钱包 API 函数:getWalletLogs(获取钱包日志)、postWalletTransferIn(充值)及 postWalletTransferOut(提现)
- 更新钱包相关类型定义,提升类型安全性
- 改进玩家会话管理:若当前无玩家资料,则自动拉取玩家信息
- 增强入口网关对过期会话的错误处理能力
- 更新 UI 组件,以适配新的结构与功能
2026-05-09 15:22:08 +08:00

119 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Wallet } from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { getWalletBalance } from "@/api/wallet";
import { buttonVariants } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import {
TransferInDialog,
TransferOutDialog,
} from "@/features/wallet/wallet-transfer-dialogs";
import { formatMinorAsCurrency } from "@/lib/money";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
/**
* 高保真稿:大厅顶部红卡 + Transfer In/ Transfer Out白底红边§4.2
*/
export function HallWalletStrip() {
const profile = usePlayerSessionStore((s) => s.profile);
const [balance, setBalance] = useState<WalletBalanceData | null>(null);
const [loading, setLoading] = useState(true);
const currency = useMemo(
() =>
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
[balance?.currency_code, profile?.default_currency],
);
const refresh = useCallback(async () => {
const b = await getWalletBalance();
setBalance(b);
}, []);
useEffect(() => {
let c = false;
void (async () => {
setLoading(true);
try {
await refresh();
} finally {
if (!c) setLoading(false);
}
})();
return () => {
c = true;
};
}, [refresh]);
const lotteryMinor = Number(balance?.balance ?? 0);
const availableMinor = Number(balance?.available_balance ?? 0);
return (
<section className="space-y-2" aria-label="Wallet balance">
<div
className={cn(
"relative overflow-hidden rounded-2xl bg-gradient-to-br from-[#dc2626] to-[#991b1b] px-4 py-3.5 text-white shadow-md",
"ring-1 ring-black/10",
)}
>
<div className="flex items-start gap-3">
<div className="flex size-11 shrink-0 items-center justify-center rounded-xl bg-white/15">
<Wallet className="size-6 text-white" aria-hidden />
</div>
<div className="min-w-0 flex-1">
<p className="text-xs font-medium uppercase tracking-wide text-white/80">
Wallet Balance
</p>
{loading ? (
<Skeleton className="mt-2 h-8 w-40 rounded-md bg-white/20" />
) : (
<p className="font-heading text-2xl font-bold tabular-nums tracking-tight">
{formatMinorAsCurrency(lotteryMinor, currency)}
</p>
)}
</div>
<Link
href="/wallet"
className={cn(
buttonVariants({
variant: "secondary",
size: "sm",
}),
"shrink-0 border-0 bg-white/15 text-xs text-white hover:bg-white/25",
)}
>
</Link>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<TransferInDialog
idPrefix="hall-"
triggerVariant="hall"
triggerLabel="Transfer In"
triggerClassName="w-full min-w-0"
currency={currency}
lotteryMinor={lotteryMinor}
onSuccess={refresh}
/>
<TransferOutDialog
idPrefix="hall-"
triggerVariant="hall"
triggerLabel="Transfer Out"
triggerClassName="w-full min-w-0"
currency={currency}
availableMinor={availableMinor}
onSuccess={refresh}
/>
</div>
</section>
);
}