feat: 接入公开币种目录并统一多币种金额与语言初始化处理
This commit is contained in:
@@ -21,11 +21,14 @@ import {
|
||||
ticketNumberSpec,
|
||||
} from "@/features/hall/hall-bet-rules";
|
||||
import type { HallDrawLiveSnapshot } from "@/features/hall/use-hall-draw-live";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { triggerWalletPollingAfterBet } from "@/hooks/use-wallet-polling";
|
||||
import { getLotteryEcho } from "@/lib/lottery-echo";
|
||||
import { getLotteryRequestLocale } from "@/lib/lottery-locale";
|
||||
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { PlayEffectivePayload, PlayEffectivePlayRow } from "@/types/api/play-effective";
|
||||
import type { TicketLineInput, TicketPlaceData, TicketPreviewData } from "@/types/api/ticket";
|
||||
@@ -391,6 +394,7 @@ function quickFillKeys(category: HallCategory): { favorites: string; history: st
|
||||
export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }) {
|
||||
const { display, isBettable, reload: reloadDraw } = drawLive;
|
||||
const { t } = useTranslation("player");
|
||||
const profile = usePlayerSessionStore((s) => s.profile);
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<HallCategory>("D2");
|
||||
const [rows, setRows] = useState<DraftRow[]>(() => [newDraftRow()]);
|
||||
@@ -414,18 +418,14 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
number: null,
|
||||
longPress: false,
|
||||
});
|
||||
useCurrencyCatalog();
|
||||
|
||||
const currencyParam = useMemo(() => {
|
||||
const fromEnv = process.env.NEXT_PUBLIC_LOTTERY_PLAY_CURRENCY?.trim();
|
||||
return fromEnv !== undefined && fromEnv !== "" ? fromEnv : undefined;
|
||||
}, []);
|
||||
const currencyParam = useMemo(() => resolvePlayerCurrency(profile), [profile]);
|
||||
|
||||
const loadCatalog = useCallback(async () => {
|
||||
setCatalogState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
|
||||
try {
|
||||
const data = await getPlayEffective(
|
||||
currencyParam !== undefined ? { currency: currencyParam } : undefined,
|
||||
);
|
||||
const data = await getPlayEffective({ currency: currencyParam });
|
||||
setCatalogState({ kind: "ok", data });
|
||||
} catch (e) {
|
||||
const msg = e instanceof LotteryApiBizError ? e.message : t("hall.loadingError");
|
||||
@@ -435,9 +435,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
|
||||
const refreshWallet = useCallback(async () => {
|
||||
try {
|
||||
const wallet = await getWalletBalance(
|
||||
currencyParam !== undefined ? { currency: currencyParam } : undefined,
|
||||
);
|
||||
const wallet = await getWalletBalance({ currency: currencyParam });
|
||||
setAvailableMinor(Number(wallet.available_balance ?? 0));
|
||||
} catch {
|
||||
setAvailableMinor(0);
|
||||
@@ -470,7 +468,8 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
);
|
||||
}, [activeCategory, catalogState]);
|
||||
|
||||
const currencyCode = catalogState.kind === "ok" ? catalogState.data.currency_code : "NPR";
|
||||
const currencyCode =
|
||||
catalogState.kind === "ok" ? catalogState.data.currency_code : currencyParam;
|
||||
|
||||
const categoryPlays = useMemo(() => {
|
||||
if (catalogState.kind !== "ok") return [];
|
||||
@@ -656,7 +655,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
const entries: DraftEntry[] = [];
|
||||
rows.forEach((row, rowIndex) => {
|
||||
playColumns.forEach((column) => {
|
||||
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "");
|
||||
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "", currencyCode);
|
||||
if (amount === null || amount <= 0) return;
|
||||
const line = lineForPlay(activeCategory, column.play, row.number, amount, column.digitSlot);
|
||||
if (!line) return;
|
||||
@@ -673,7 +672,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
});
|
||||
});
|
||||
return entries;
|
||||
}, [activeCategory, playColumns, rows]);
|
||||
}, [activeCategory, currencyCode, playColumns, rows]);
|
||||
|
||||
const draftEntries = collectEntries();
|
||||
const draftSummary = useMemo(() => {
|
||||
|
||||
@@ -22,7 +22,9 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { getLotteryRequestLocale } from "@/lib/lottery-locale";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type {
|
||||
PlayEffectivePayload,
|
||||
@@ -77,19 +79,15 @@ function formatMoneyAmount(n: number): string {
|
||||
}
|
||||
|
||||
export function HallPlayCatalogPanel() {
|
||||
const profile = usePlayerSessionStore((s) => s.profile);
|
||||
const { t } = useTranslation("player");
|
||||
const [state, setState] = useState<LoadState>({ kind: "loading" });
|
||||
const currencyParam = useMemo(() => {
|
||||
const fromEnv = process.env.NEXT_PUBLIC_LOTTERY_PLAY_CURRENCY?.trim();
|
||||
return fromEnv !== undefined && fromEnv !== "" ? fromEnv : undefined;
|
||||
}, []);
|
||||
const currencyParam = useMemo(() => resolvePlayerCurrency(profile), [profile]);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
|
||||
try {
|
||||
const data = await getPlayEffective(
|
||||
currencyParam !== undefined ? { currency: currencyParam } : undefined,
|
||||
);
|
||||
const data = await getPlayEffective({ currency: currencyParam });
|
||||
setState({ kind: "ok", data });
|
||||
} catch (e) {
|
||||
if (e instanceof LotteryApiBizError && e.code === 9004) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
TransferOutDialog,
|
||||
} from "@/features/wallet/wallet-transfer-dialogs";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
@@ -26,9 +27,8 @@ export function HallWalletStrip() {
|
||||
const degradedWalletPollRef = useRef<number | null>(null);
|
||||
|
||||
const currency = useMemo(
|
||||
() =>
|
||||
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
|
||||
[balance?.currency_code, profile?.default_currency],
|
||||
() => resolvePlayerCurrency(profile, balance),
|
||||
[balance, profile],
|
||||
);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
|
||||
@@ -16,12 +16,15 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { norm4d } from "@/lib/norm-4d";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { playLabel } from "@/lib/play-labels";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { TicketItemDetailPayload } from "@/types/api/ticket-items";
|
||||
|
||||
type OddsSnapRow = { prize_scope?: string; odds_value?: number };
|
||||
@@ -66,6 +69,8 @@ type TicketItemDetailWithExtras = TicketItemDetailPayload & {
|
||||
/** 界面文档 §4.8 注单详情 */
|
||||
export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
|
||||
const { t } = useTranslation("player");
|
||||
const profile = usePlayerSessionStore((state) => state.profile);
|
||||
useCurrencyCatalog();
|
||||
const [data, setData] = useState<TicketItemDetailPayload | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -131,7 +136,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
const cur = data.currency_code ?? "NPR";
|
||||
const cur = data.currency_code ?? resolvePlayerCurrency(profile);
|
||||
const st = ticketStatusDisplay(data.status, data.win_amount, data.jackpot_win_amount, t);
|
||||
const totalWin = data.win_amount + data.jackpot_win_amount;
|
||||
const pub = data.published_draw_results;
|
||||
|
||||
@@ -15,11 +15,14 @@ import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { playLabel } from "@/lib/play-labels";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { TicketItemListRow } from "@/types/api/ticket-items";
|
||||
|
||||
const ORDERS_PAGE_SIZE = 20;
|
||||
@@ -42,6 +45,8 @@ function formatYmd(value: Date): string {
|
||||
export function TicketOrdersListScreen() {
|
||||
const searchParams = useSearchParams();
|
||||
const { t } = useTranslation("player");
|
||||
const profile = usePlayerSessionStore((state) => state.profile);
|
||||
useCurrencyCatalog();
|
||||
const drawNoFilter = useMemo(() => (searchParams.get("draw_no") ?? "").trim(), [searchParams]);
|
||||
const statusFilter = useMemo(
|
||||
() => searchParams.getAll("status").map((s) => s.trim()).filter(Boolean),
|
||||
@@ -353,7 +358,7 @@ export function TicketOrdersListScreen() {
|
||||
<>
|
||||
<div className="space-y-3">
|
||||
{items.map((row) => {
|
||||
const cur = row.currency_code ?? "NPR";
|
||||
const cur = row.currency_code ?? resolvePlayerCurrency(profile);
|
||||
const st = ticketStatusDisplay(row.status, row.win_amount, row.jackpot_win_amount, t);
|
||||
const totalWin = row.win_amount + row.jackpot_win_amount;
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { getPublicCurrencies } from "@/api/currency";
|
||||
import { getPlayerMe, getPlayerPing } from "@/api/player";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
@@ -93,7 +94,7 @@ export function EntryGate() {
|
||||
|
||||
const tokenFromUrl = searchParams.get("token") ?? "";
|
||||
|
||||
const { bearerToken, setBearerToken, setProfile, clearBearerToken } =
|
||||
const { bearerToken, setBearerToken, setProfile, setCurrencies, clearBearerToken } =
|
||||
usePlayerSessionStore();
|
||||
|
||||
const [phase, setPhase] = useState<Phase>("loading");
|
||||
@@ -141,6 +142,13 @@ export function EntryGate() {
|
||||
try {
|
||||
const [me] = await Promise.all([getPlayerMe(), sleep(300)]);
|
||||
|
||||
try {
|
||||
const currencies = await getPublicCurrencies();
|
||||
setCurrencies(currencies.items);
|
||||
} catch {
|
||||
// 不阻断进场主流程;金额精度会退回默认 2 位兜底。
|
||||
}
|
||||
|
||||
updateStep("token", "done");
|
||||
updateStep("account", "done");
|
||||
|
||||
@@ -216,6 +224,7 @@ export function EntryGate() {
|
||||
tokenFromUrl,
|
||||
setBearerToken,
|
||||
setProfile,
|
||||
setCurrencies,
|
||||
clearBearerToken,
|
||||
router,
|
||||
updateStep,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { getPublicCurrencies } from "@/api/currency";
|
||||
import { getPlayerMe } from "@/api/player";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
|
||||
@@ -14,20 +15,31 @@ export function HydratePlayerAuth(): null {
|
||||
(state) => state.restoreBearerToken,
|
||||
);
|
||||
const setProfile = usePlayerSessionStore((state) => state.setProfile);
|
||||
const setCurrencies = usePlayerSessionStore((state) => state.setCurrencies);
|
||||
|
||||
useEffect(() => {
|
||||
const token = restoreBearerToken();
|
||||
if (!token) return;
|
||||
if (usePlayerSessionStore.getState().profile !== null) return;
|
||||
void (async () => {
|
||||
try {
|
||||
if (usePlayerSessionStore.getState().currencies.length === 0) {
|
||||
try {
|
||||
const currencies = await getPublicCurrencies();
|
||||
setCurrencies(currencies.items);
|
||||
} catch {
|
||||
// 币种元数据失败时不影响登录态恢复。
|
||||
}
|
||||
}
|
||||
|
||||
if (!token) return;
|
||||
if (usePlayerSessionStore.getState().profile !== null) return;
|
||||
|
||||
const me = await getPlayerMe();
|
||||
setProfile(me);
|
||||
} catch {
|
||||
/* 401 由 lottery-http 拦截跳转 */
|
||||
}
|
||||
})();
|
||||
}, [restoreBearerToken, setProfile]);
|
||||
}, [restoreBearerToken, setCurrencies, setProfile]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,12 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { playLabel } from "@/lib/play-labels";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { DrawResultListItem } from "@/types/api/draw-results";
|
||||
import type { TicketDrawMyMatchPayload, TicketItemListRow } from "@/types/api/ticket-items";
|
||||
|
||||
@@ -31,6 +34,7 @@ type WinningCheckResult = {
|
||||
|
||||
export function CheckWinningScreen() {
|
||||
const { t } = useTranslation("player");
|
||||
useCurrencyCatalog();
|
||||
const [ticketNo, setTicketNo] = useState("");
|
||||
const [latestDraw, setLatestDraw] = useState<DrawResultListItem | null>(null);
|
||||
const [recent, setRecent] = useState<string[]>([]);
|
||||
@@ -197,9 +201,11 @@ function WinningResultDialog({
|
||||
onCheckAnother: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation("player");
|
||||
const profile = usePlayerSessionStore((state) => state.profile);
|
||||
const totalWin = (data?.match.total_win_minor ?? 0) + (data?.match.total_jackpot_win_minor ?? 0);
|
||||
const isWon = totalWin > 0 || (data?.match.winning_ticket_count ?? 0) > 0;
|
||||
const firstTicket = useMemo(() => data?.tickets[0] ?? null, [data]);
|
||||
const currency = firstTicket?.currency_code ?? resolvePlayerCurrency(profile);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
@@ -250,7 +256,7 @@ function WinningResultDialog({
|
||||
{t("results.check.amount")}
|
||||
</p>
|
||||
<p className="mt-2 font-mono text-lg font-black text-[#0a8f3e]">
|
||||
{formatMinorAsCurrency(totalWin, firstTicket?.currency_code ?? "NPR")}
|
||||
{formatMinorAsCurrency(totalWin, currency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,11 +18,14 @@ import {
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
|
||||
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { getPlayerBearerTokenPayload } from "@/lib/lottery-auth";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { norm4d } from "@/lib/norm-4d";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { DrawResultDetailPayload } from "@/types/api/draw-results";
|
||||
|
||||
type DrawResultDetailScreenProps = {
|
||||
@@ -32,6 +35,8 @@ type DrawResultDetailScreenProps = {
|
||||
/** §4.6 开奖结果详情:23 分区 + [< >] 切换 + 本人命中高亮 + Jackpot */
|
||||
export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps) {
|
||||
const { t } = useTranslation("player");
|
||||
const profile = usePlayerSessionStore((state) => state.profile);
|
||||
useCurrencyCatalog();
|
||||
const [data, setData] = useState<DrawResultDetailPayload | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -135,7 +140,7 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
|
||||
);
|
||||
}
|
||||
|
||||
const currency = "NPR";
|
||||
const currency = data.jackpot?.currency_code ?? resolvePlayerCurrency(profile);
|
||||
const showMyPayout =
|
||||
myTotals &&
|
||||
myTotals.hasBets &&
|
||||
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
|
||||
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { DrawResultListItem } from "@/types/api/draw-results";
|
||||
|
||||
const RESULTS_PAGE_SIZE = 10;
|
||||
@@ -32,6 +35,8 @@ const MONTH_OPTIONS = Array.from({ length: 12 }, (_, value) => ({
|
||||
|
||||
export function DrawResultsListScreen() {
|
||||
const { t } = useTranslation("player");
|
||||
const profile = usePlayerSessionStore((state) => state.profile);
|
||||
useCurrencyCatalog();
|
||||
const [items, setItems] = useState<DrawResultListItem[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [date, setDate] = useState("");
|
||||
@@ -46,6 +51,7 @@ export function DrawResultsListScreen() {
|
||||
const businessDate = /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : undefined;
|
||||
const quickYears = useMemo(() => buildYearOptions(calendarMonth), [calendarMonth]);
|
||||
const featured = items?.[0] ?? null;
|
||||
const jackpotCurrency = featured?.jackpot?.currency_code ?? resolvePlayerCurrency(profile);
|
||||
|
||||
const fetchList = useCallback(async (targetPage = 1, append = false) => {
|
||||
setError(null);
|
||||
@@ -104,7 +110,7 @@ export function DrawResultsListScreen() {
|
||||
return (
|
||||
<PlayerPanel title={t("results.title")} subtitle={t("results.subtitle")} eyebrow={t("brand.name")}>
|
||||
<div className="space-y-4">
|
||||
<JackpotResultsStrip currencyCode="NPR" />
|
||||
<JackpotResultsStrip currencyCode={jackpotCurrency} />
|
||||
|
||||
<div className="rounded-xl border border-[#e6edf8] bg-[#f8fbff] p-3">
|
||||
<p className="mb-2 text-xs font-bold text-[#32518d]">
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { getWalletBalance } from "@/api/wallet";
|
||||
import { TransferInPage } from "@/features/wallet/wallet-transfer-forms";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { WalletBalanceData } from "@/types/api/wallet-balance";
|
||||
|
||||
@@ -17,9 +18,8 @@ export function TransferInScreen() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const currency = useMemo(
|
||||
() =>
|
||||
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
|
||||
[balance?.currency_code, profile?.default_currency],
|
||||
() => resolvePlayerCurrency(profile, balance),
|
||||
[balance, profile],
|
||||
);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { getWalletBalance } from "@/api/wallet";
|
||||
import { TransferOutPage } from "@/features/wallet/wallet-transfer-forms";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { WalletBalanceData } from "@/types/api/wallet-balance";
|
||||
|
||||
@@ -17,9 +18,8 @@ export function TransferOutScreen() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const currency = useMemo(
|
||||
() =>
|
||||
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
|
||||
[balance?.currency_code, profile?.default_currency],
|
||||
() => resolvePlayerCurrency(profile, balance),
|
||||
[balance, profile],
|
||||
);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getWalletLogs } from "@/api/wallet";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { formatWalletClientError } from "@/lib/wallet-api-error";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import { getWalletLogsLastPage, type WalletLogsData } from "@/types/api/wallet-logs";
|
||||
@@ -25,8 +26,8 @@ export function WalletLogsScreen() {
|
||||
const loadMoreRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const currency = useMemo(
|
||||
() => (profile?.default_currency ?? "NPR").toUpperCase(),
|
||||
[profile?.default_currency],
|
||||
() => resolvePlayerCurrency(profile),
|
||||
[profile],
|
||||
);
|
||||
|
||||
const fetchPassRef = useRef(true);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@/features/wallet/wallet-transfer-dialogs";
|
||||
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { resolvePlayerCurrency } from "@/lib/player-currency";
|
||||
import { formatWalletClientError } from "@/lib/wallet-api-error";
|
||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||
import type { WalletBalanceData } from "@/types/api/wallet-balance";
|
||||
@@ -34,13 +35,10 @@ export function WalletScreen() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const loadMoreRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const currency = useMemo(() => {
|
||||
return (
|
||||
balance?.currency_code ??
|
||||
profile?.default_currency ??
|
||||
"NPR"
|
||||
).toUpperCase();
|
||||
}, [balance?.currency_code, profile?.default_currency]);
|
||||
const currency = useMemo(
|
||||
() => resolvePlayerCurrency(profile, balance),
|
||||
[balance, profile],
|
||||
);
|
||||
|
||||
const fetchPassRef = useRef(true);
|
||||
|
||||
|
||||
@@ -18,7 +18,12 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
|
||||
import {
|
||||
formatMinorAsCurrency,
|
||||
getCurrencyDecimalPlaces,
|
||||
parseDecimalInputToMinor,
|
||||
} from "@/lib/money";
|
||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||
import { formatWalletClientError } from "@/lib/wallet-api-error";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
|
||||
@@ -64,14 +69,15 @@ export function TransferInPanel({
|
||||
variant?: PanelVariant;
|
||||
}) {
|
||||
const { t } = useTranslation("player");
|
||||
useCurrencyCatalog();
|
||||
const [amountText, setAmountText] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [localError, setLocalError] = useState<string | null>(null);
|
||||
const tid = `${idPrefix}in-amount`;
|
||||
|
||||
const parsedMinor = useMemo(
|
||||
() => parseDecimalInputToMinor(amountText),
|
||||
[amountText],
|
||||
() => parseDecimalInputToMinor(amountText, currency),
|
||||
[amountText, currency],
|
||||
);
|
||||
const previewAfter =
|
||||
parsedMinor != null ? lotteryMinor + parsedMinor : lotteryMinor;
|
||||
@@ -204,14 +210,15 @@ export function TransferOutPanel({
|
||||
variant?: PanelVariant;
|
||||
}) {
|
||||
const { t } = useTranslation("player");
|
||||
useCurrencyCatalog();
|
||||
const [amountText, setAmountText] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [localError, setLocalError] = useState<string | null>(null);
|
||||
const tid = `${idPrefix}out-amount`;
|
||||
|
||||
const parsedMinor = useMemo(
|
||||
() => parseDecimalInputToMinor(amountText),
|
||||
[amountText],
|
||||
() => parseDecimalInputToMinor(amountText, currency),
|
||||
[amountText, currency],
|
||||
);
|
||||
const previewAfter =
|
||||
parsedMinor != null
|
||||
@@ -219,8 +226,9 @@ export function TransferOutPanel({
|
||||
: availableMinor;
|
||||
|
||||
const fillAll = () => {
|
||||
const major = availableMinor / 100;
|
||||
setAmountText(major.toFixed(2));
|
||||
const decimals = getCurrencyDecimalPlaces(currency);
|
||||
const major = availableMinor / 10 ** decimals;
|
||||
setAmountText(major.toFixed(decimals));
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
|
||||
Reference in New Issue
Block a user