feat: 增强钱包 API 与玩家会话管理
- 新增钱包 API 函数:getWalletLogs(获取钱包日志)、postWalletTransferIn(充值)及 postWalletTransferOut(提现) - 更新钱包相关类型定义,提升类型安全性 - 改进玩家会话管理:若当前无玩家资料,则自动拉取玩家信息 - 增强入口网关对过期会话的错误处理能力 - 更新 UI 组件,以适配新的结构与功能
This commit is contained in:
34
src/features/hall/hall-screen.tsx
Normal file
34
src/features/hall/hall-screen.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
import { HallWalletStrip } from "@/features/hall/hall-wallet-strip";
|
||||
|
||||
/**
|
||||
* 下注大厅:顶部钱包条对齐高保真稿;以下为期号/表格占位。
|
||||
*/
|
||||
export function HallScreen() {
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<HallWalletStrip />
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">下注大厅</CardTitle>
|
||||
<CardDescription>
|
||||
Issue No.、倒计时、2D/3D/4D 表格与 Submit Bet 将按界面文档 §4.2 接续开发。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground">
|
||||
封盘态、WebSocket 降级轮询等与 PRD §2 一致时再接入。
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
118
src/features/hall/hall-wallet-strip.tsx
Normal file
118
src/features/hall/hall-wallet-strip.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user