feat: 增强钱包 API 与玩家会话管理

- 新增钱包 API 函数:getWalletLogs(获取钱包日志)、postWalletTransferIn(充值)及 postWalletTransferOut(提现)
- 更新钱包相关类型定义,提升类型安全性
- 改进玩家会话管理:若当前无玩家资料,则自动拉取玩家信息
- 增强入口网关对过期会话的错误处理能力
- 更新 UI 组件,以适配新的结构与功能
This commit is contained in:
2026-05-09 15:22:08 +08:00
parent 14c297fe1a
commit 7743c14e83
28 changed files with 1719 additions and 33 deletions

View File

@@ -0,0 +1,100 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { getWalletLogs } from "@/api/wallet";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
import { formatWalletClientError } from "@/lib/wallet-api-error";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { WalletLogsData } from "@/types/api/wallet-logs";
/** 独立路由 `/wallet/logs` */
export function WalletLogsScreen() {
const profile = usePlayerSessionStore((s) => s.profile);
const [logs, setLogs] = useState<WalletLogsData | null>(null);
const [filter, setFilter] = useState("");
const [loading, setLoading] = useState(true);
const [logsLoading, setLogsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const currency = useMemo(
() => (profile?.default_currency ?? "NPR").toUpperCase(),
[profile?.default_currency],
);
const fetchPassRef = useRef(true);
const load = useCallback(async () => {
setError(null);
if (fetchPassRef.current) {
setLoading(true);
fetchPassRef.current = false;
} else {
setLogsLoading(true);
}
try {
const L = await getWalletLogs({
page: 1,
size: 50,
type: filter || undefined,
});
setLogs(L);
} catch (e) {
setError(formatWalletClientError(e));
} finally {
setLoading(false);
setLogsLoading(false);
}
}, [filter]);
useEffect(() => {
void load();
}, [load]);
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<h1 className="text-lg font-semibold tracking-tight"></h1>
<Link
href="/wallet"
className={cn(buttonVariants({ variant: "outline", size: "sm" }))}
>
</Link>
</div>
{error ? (
<Card className="border-destructive/40">
<CardHeader>
<CardTitle className="text-destructive"></CardTitle>
<CardDescription>{error}</CardDescription>
</CardHeader>
<CardContent>
<Button type="button" onClick={() => void load()}>
</Button>
</CardContent>
</Card>
) : null}
<WalletLogsBlock
logs={logs}
logsLoading={loading || logsLoading}
filter={filter}
onFilterChange={setFilter}
currency={currency}
title="类型筛选"
/>
</div>
);
}