fix: 优化嵌入登录等待态并修复大厅与订单筛选交互
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
/**
|
/**
|
||||||
* 玩家端(lotteryfront)宝塔 PM2 配置
|
* 玩家端(lotteryfront)宝塔 PM2 配置
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { PlayerNotificationBell } from "@/components/layout/player-notification-
|
|||||||
import {
|
import {
|
||||||
playerHeaderControl,
|
playerHeaderControl,
|
||||||
playerPageHeader,
|
playerPageHeader,
|
||||||
playerPageInset,
|
|
||||||
} from "@/lib/player-spacing";
|
} from "@/lib/player-spacing";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ export function PlayerPanel({
|
|||||||
className,
|
className,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
}: PlayerPanelProps) {
|
}: PlayerPanelProps) {
|
||||||
const { t } = useTranslation("common");
|
|
||||||
const { t: tp } = useTranslation("player");
|
const { t: tp } = useTranslation("player");
|
||||||
const resolvedBackLabel = backLabel ?? tp("panel.home");
|
const resolvedBackLabel = backLabel ?? tp("panel.home");
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import type { HallDrawLiveSnapshot } from "@/features/hall/use-hall-draw-live";
|
|||||||
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
|
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
|
||||||
import { triggerWalletPollingAfterBet } from "@/hooks/use-wallet-polling";
|
import { triggerWalletPollingAfterBet } from "@/hooks/use-wallet-polling";
|
||||||
import { getLotteryEcho } from "@/lib/lottery-echo";
|
import { getLotteryEcho } from "@/lib/lottery-echo";
|
||||||
import { getLotteryRequestLocale } from "@/lib/lottery-locale";
|
|
||||||
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
|
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
|
||||||
import { playLabel } from "@/lib/play-labels";
|
import { playLabel } from "@/lib/play-labels";
|
||||||
import {
|
import {
|
||||||
@@ -459,6 +458,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
const [resultOpen, setResultOpen] = useState(false);
|
const [resultOpen, setResultOpen] = useState(false);
|
||||||
const [resultData, setResultData] = useState<TicketPlaceData | null>(null);
|
const [resultData, setResultData] = useState<TicketPlaceData | null>(null);
|
||||||
const [quickFillState, setQuickFillState] = useState<QuickFillState>(() => loadQuickFillState());
|
const [quickFillState, setQuickFillState] = useState<QuickFillState>(() => loadQuickFillState());
|
||||||
|
const [riskStateDrawNo, setRiskStateDrawNo] = useState<string | null>(display?.draw_no ?? null);
|
||||||
const [liveSoldOutNumbers, setLiveSoldOutNumbers] = useState<Set<string>>(() => new Set());
|
const [liveSoldOutNumbers, setLiveSoldOutNumbers] = useState<Set<string>>(() => new Set());
|
||||||
const [liveWarningNumbers, setLiveWarningNumbers] = useState<Set<string>>(() => new Set());
|
const [liveWarningNumbers, setLiveWarningNumbers] = useState<Set<string>>(() => new Set());
|
||||||
const [debouncedSummary, setDebouncedSummary] = useState({ bet: 0, rebate: 0, actual: 0 });
|
const [debouncedSummary, setDebouncedSummary] = useState({ bet: 0, rebate: 0, actual: 0 });
|
||||||
@@ -475,9 +475,9 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
? crypto.randomUUID()
|
? crypto.randomUUID()
|
||||||
: `pl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
: `pl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
|
|
||||||
const clearPlaceTraceId = () => {
|
const clearPlaceTraceId = useCallback(() => {
|
||||||
placeTraceIdRef.current = null;
|
placeTraceIdRef.current = null;
|
||||||
};
|
}, []);
|
||||||
const loadCatalog = useCallback(async () => {
|
const loadCatalog = useCallback(async () => {
|
||||||
setCatalogState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
|
setCatalogState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
|
||||||
try {
|
try {
|
||||||
@@ -576,34 +576,17 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
|
|
||||||
const drawNo = display?.draw_no ?? null;
|
const drawNo = display?.draw_no ?? null;
|
||||||
|
|
||||||
useEffect(() => {
|
if (riskStateDrawNo !== drawNo) {
|
||||||
|
setRiskStateDrawNo(drawNo);
|
||||||
setLiveSoldOutNumbers(new Set());
|
setLiveSoldOutNumbers(new Set());
|
||||||
setLiveWarningNumbers(new Set());
|
setLiveWarningNumbers(new Set());
|
||||||
}, [drawNo]);
|
}
|
||||||
|
|
||||||
const alertRows = display?.risk_pool_alerts ?? [];
|
const alertRows = display?.risk_pool_alerts ?? [];
|
||||||
const jackpot = display?.jackpot;
|
const jackpot = display?.jackpot;
|
||||||
const currentQuickFill = quickFillState[activeCategory] ?? { favorites: [], history: [] };
|
const currentQuickFill = quickFillState[activeCategory] ?? { favorites: [], history: [] };
|
||||||
const favorites = currentQuickFill.favorites;
|
const favorites = currentQuickFill.favorites;
|
||||||
const historyNumbers = currentQuickFill.history;
|
const historyNumbers = currentQuickFill.history;
|
||||||
const jackpotPanelCopy = !jackpot?.enabled
|
|
||||||
? {
|
|
||||||
title: t("hall.jackpotPanel.disabledTitle"),
|
|
||||||
subtitle: t("hall.jackpotPanel.disabledSubtitle"),
|
|
||||||
description: t("hall.jackpotPanel.disabledDescription"),
|
|
||||||
}
|
|
||||||
: isBettable
|
|
||||||
? {
|
|
||||||
title: t("hall.jackpotPanel.infoTitle"),
|
|
||||||
subtitle: t("hall.jackpotPanel.infoSubtitle"),
|
|
||||||
description: t("hall.jackpotPanel.infoDescription"),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
title: t("hall.jackpotPanel.closedInfoTitle"),
|
|
||||||
subtitle: t("hall.jackpotPanel.closedInfoSubtitle"),
|
|
||||||
description: t("hall.jackpotPanel.closedInfoDescription"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableDisabled = !isBettable || catalogState.kind !== "ok";
|
const tableDisabled = !isBettable || catalogState.kind !== "ok";
|
||||||
const sealedBetUi = Boolean(display && isHallSealedCountdownUi(display.status));
|
const sealedBetUi = Boolean(display && isHallSealedCountdownUi(display.status));
|
||||||
const defaultNumberPlaceholder =
|
const defaultNumberPlaceholder =
|
||||||
@@ -715,8 +698,18 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAmountsForPlay = useCallback((playCode: string): boolean => {
|
const hasAmountsForPlay = useCallback(
|
||||||
let removed = false;
|
(playCode: string): boolean =>
|
||||||
|
rows.some((row) =>
|
||||||
|
Object.entries(row.amounts).some(
|
||||||
|
([amountKey, amountValue]) =>
|
||||||
|
amountKey.split("@")[0] === playCode && Boolean(amountValue?.trim()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[rows],
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearAmountsForPlay = useCallback((playCode: string) => {
|
||||||
setRows((current) =>
|
setRows((current) =>
|
||||||
current.map((row) => {
|
current.map((row) => {
|
||||||
const nextAmounts = { ...row.amounts };
|
const nextAmounts = { ...row.amounts };
|
||||||
@@ -726,13 +719,10 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
if (keyPlayCode !== playCode || !nextAmounts[amountKey]) return;
|
if (keyPlayCode !== playCode || !nextAmounts[amountKey]) return;
|
||||||
nextAmounts[amountKey] = "";
|
nextAmounts[amountKey] = "";
|
||||||
changed = true;
|
changed = true;
|
||||||
removed = true;
|
|
||||||
});
|
});
|
||||||
return changed ? { ...row, amounts: nextAmounts } : row;
|
return changed ? { ...row, amounts: nextAmounts } : row;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return removed;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -743,7 +733,8 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
|
|
||||||
const onPlayToggle = (evt: PlayToggleWsEvent) => {
|
const onPlayToggle = (evt: PlayToggleWsEvent) => {
|
||||||
if (evt.enabled === false && typeof evt.play_code === "string") {
|
if (evt.enabled === false && typeof evt.play_code === "string") {
|
||||||
const removed = clearAmountsForPlay(evt.play_code);
|
const removed = hasAmountsForPlay(evt.play_code);
|
||||||
|
clearAmountsForPlay(evt.play_code);
|
||||||
setPreviewOpen(false);
|
setPreviewOpen(false);
|
||||||
setPreviewData(null);
|
setPreviewData(null);
|
||||||
clearPlaceTraceId();
|
clearPlaceTraceId();
|
||||||
@@ -809,7 +800,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
|||||||
channel.stopListening(".risk.sold_out");
|
channel.stopListening(".risk.sold_out");
|
||||||
channel.stopListening(".risk.warning");
|
channel.stopListening(".risk.warning");
|
||||||
};
|
};
|
||||||
}, [clearAmountsForPlay, drawNo, loadCatalog, reloadDraw, t]);
|
}, [clearAmountsForPlay, clearPlaceTraceId, drawNo, hasAmountsForPlay, reloadDraw, t]);
|
||||||
|
|
||||||
const collectDraftLineIssues = useCallback((): DraftLineIssue[] => {
|
const collectDraftLineIssues = useCallback((): DraftLineIssue[] => {
|
||||||
if (activeCategory === "JACKPOT") return [];
|
if (activeCategory === "JACKPOT") return [];
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
|
|||||||
window.removeEventListener("lottery-hall-refresh", refresh);
|
window.removeEventListener("lottery-hall-refresh", refresh);
|
||||||
window.removeEventListener("lottery-wallet-refresh", refresh);
|
window.removeEventListener("lottery-wallet-refresh", refresh);
|
||||||
};
|
};
|
||||||
}, [data?.status, load]);
|
}, [data, load]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ export function TicketOrdersListScreen() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [queryDrawNo, setQueryDrawNo] = useState(drawNoFilter);
|
const [queryDrawNoState, setQueryDrawNoState] = useState(() => ({
|
||||||
|
base: drawNoFilter,
|
||||||
|
draft: drawNoFilter,
|
||||||
|
}));
|
||||||
const [queryNumber, setQueryNumber] = useState("");
|
const [queryNumber, setQueryNumber] = useState("");
|
||||||
const [queryStatuses, setQueryStatuses] = useState<string[]>(statusFilter);
|
const [queryStatuses, setQueryStatuses] = useState<string[]>(statusFilter);
|
||||||
const [fromDate, setFromDate] = useState("");
|
const [fromDate, setFromDate] = useState("");
|
||||||
@@ -82,9 +85,9 @@ export function TicketOrdersListScreen() {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const initialLoadDone = useRef(false);
|
const initialLoadDone = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const queryDrawNoInput =
|
||||||
setQueryDrawNo(drawNoFilter);
|
queryDrawNoState.base === drawNoFilter ? queryDrawNoState.draft : drawNoFilter;
|
||||||
}, [drawNoFilter]);
|
const queryDrawNo = queryDrawNoInput || drawNoFilter;
|
||||||
|
|
||||||
const selectedRange = useMemo(() => {
|
const selectedRange = useMemo(() => {
|
||||||
const from = parseSchedulePickerYmd(fromDate);
|
const from = parseSchedulePickerYmd(fromDate);
|
||||||
@@ -118,7 +121,7 @@ export function TicketOrdersListScreen() {
|
|||||||
const res = await getTicketItems({
|
const res = await getTicketItems({
|
||||||
page: nextPage,
|
page: nextPage,
|
||||||
per_page: ORDERS_PAGE_SIZE,
|
per_page: ORDERS_PAGE_SIZE,
|
||||||
draw_no: queryDrawNo || drawNoFilter || undefined,
|
draw_no: queryDrawNo || undefined,
|
||||||
number: queryNumber || undefined,
|
number: queryNumber || undefined,
|
||||||
status: queryStatuses.length ? queryStatuses : undefined,
|
status: queryStatuses.length ? queryStatuses : undefined,
|
||||||
start_date: fromDate || undefined,
|
start_date: fromDate || undefined,
|
||||||
@@ -136,7 +139,7 @@ export function TicketOrdersListScreen() {
|
|||||||
setLoadingMore(false);
|
setLoadingMore(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[drawNoFilter, fromDate, queryDrawNo, queryNumber, queryStatuses, t, toDate],
|
[fromDate, queryDrawNo, queryNumber, queryStatuses, t, toDate],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -219,13 +222,13 @@ export function TicketOrdersListScreen() {
|
|||||||
>
|
>
|
||||||
{t("orders.betNow")}
|
{t("orders.betNow")}
|
||||||
</Link>
|
</Link>
|
||||||
{(queryDrawNo || queryNumber || fromDate || toDate || queryStatuses.length > 0) ? (
|
{(queryDrawNoInput || queryNumber || fromDate || toDate || queryStatuses.length > 0) ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-9 rounded-full border-[#dce7f7] bg-white px-3 text-xs font-bold text-[#32518d] hover:bg-[#f8fbff]"
|
className="h-9 rounded-full border-[#dce7f7] bg-white px-3 text-xs font-bold text-[#32518d] hover:bg-[#f8fbff]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setQueryDrawNo(drawNoFilter);
|
setQueryDrawNoState({ base: drawNoFilter, draft: drawNoFilter });
|
||||||
setQueryNumber("");
|
setQueryNumber("");
|
||||||
setFromDate("");
|
setFromDate("");
|
||||||
setToDate("");
|
setToDate("");
|
||||||
@@ -243,8 +246,10 @@ export function TicketOrdersListScreen() {
|
|||||||
<div className="mt-3 grid grid-cols-2 gap-2 lg:grid-cols-4">
|
<div className="mt-3 grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||||
<div className="flex h-9 min-w-0 items-center rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3">
|
<div className="flex h-9 min-w-0 items-center rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3">
|
||||||
<Input
|
<Input
|
||||||
value={queryDrawNo}
|
value={queryDrawNoInput}
|
||||||
onChange={(e) => setQueryDrawNo(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setQueryDrawNoState({ base: drawNoFilter, draft: e.target.value })
|
||||||
|
}
|
||||||
placeholder={t("orders.drawNo")}
|
placeholder={t("orders.drawNo")}
|
||||||
aria-label={t("orders.drawNo")}
|
aria-label={t("orders.drawNo")}
|
||||||
className="h-7 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0"
|
className="h-7 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0"
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ export function EntryGate() {
|
|||||||
|
|
||||||
const { bearerToken, setBearerToken, setProfile, setCurrencies, clearBearerToken } =
|
const { bearerToken, setBearerToken, setProfile, setCurrencies, clearBearerToken } =
|
||||||
usePlayerSessionStore();
|
usePlayerSessionStore();
|
||||||
|
const waitingForEmbeddedToken =
|
||||||
|
!sessionExpired &&
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
isInIframe() &&
|
||||||
|
!tokenFromUrl &&
|
||||||
|
!(bearerToken ?? "").trim();
|
||||||
|
|
||||||
const [phase, setPhase] = useState<Phase>(sessionExpired ? "failed" : "loading");
|
const [phase, setPhase] = useState<Phase>(sessionExpired ? "failed" : "loading");
|
||||||
const [failureDetails, setFailureDetails] = useState<FailureRow[]>(() =>
|
const [failureDetails, setFailureDetails] = useState<FailureRow[]>(() =>
|
||||||
@@ -273,19 +279,12 @@ export function EntryGate() {
|
|||||||
}, [sessionExpired, clearBearerToken]);
|
}, [sessionExpired, clearBearerToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sessionExpired) return;
|
if (sessionExpired || waitingForEmbeddedToken) return;
|
||||||
|
|
||||||
const embedded = typeof window !== "undefined" && isInIframe() && !tokenFromUrl;
|
|
||||||
if (embedded && !effectiveToken) {
|
|
||||||
setPhase("loading");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmr = window.setTimeout(() => {
|
const tmr = window.setTimeout(() => {
|
||||||
void doEntry();
|
void doEntry();
|
||||||
}, 300);
|
}, 300);
|
||||||
return () => window.clearTimeout(tmr);
|
return () => window.clearTimeout(tmr);
|
||||||
}, [doEntry, sessionExpired, effectiveToken, tokenFromUrl]);
|
}, [doEntry, sessionExpired, waitingForEmbeddedToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sessionExpired) return;
|
if (sessionExpired) return;
|
||||||
@@ -320,7 +319,7 @@ export function EntryGate() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col px-3 py-5">
|
<div className="flex flex-1 flex-col px-3 py-5">
|
||||||
{phase === "loading" ? (
|
{phase === "loading" || waitingForEmbeddedToken ? (
|
||||||
<div className="mx-auto w-full max-w-md">
|
<div className="mx-auto w-full max-w-md">
|
||||||
<div className="mb-6 flex items-center gap-2">
|
<div className="mb-6 flex items-center gap-2">
|
||||||
<div className="flex size-8 items-center justify-center rounded bg-red-600 text-white">
|
<div className="flex size-8 items-center justify-center rounded bg-red-600 text-white">
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ export function NotificationsScreen() {
|
|||||||
return (
|
return (
|
||||||
<PlayerPanel title={t("notifications.title")} backHref="/hall">
|
<PlayerPanel title={t("notifications.title")} backHref="/hall">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="rounded-xl border border-amber-200 bg-amber-50/90 px-3 py-2.5 text-xs leading-relaxed text-amber-900">
|
|
||||||
{t("notifications.subtitle")}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between rounded-xl border border-[#dce7f7] bg-[#f8fbff] px-3 py-2.5">
|
<div className="flex items-center justify-between rounded-xl border border-[#dce7f7] bg-[#f8fbff] px-3 py-2.5">
|
||||||
<p className="text-sm font-semibold text-[#0b3f96]">
|
<p className="text-sm font-semibold text-[#0b3f96]">
|
||||||
{t("notifications.unreadCount", { count: unreadCount })}
|
{t("notifications.unreadCount", { count: unreadCount })}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { getWalletBalance, getWalletLogs } from "@/api/wallet";
|
import { getWalletBalance, getWalletLogs } from "@/api/wallet";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
|
||||||
import { listBettableCurrencies } from "@/lib/player-currency-options";
|
import { listBettableCurrencies, pickActivePlayerCurrency } from "@/lib/player-currency-options";
|
||||||
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
import { usePlayerSessionStore } from "@/stores/player-session-store";
|
||||||
|
|
||||||
export function useActivePlayerCurrency() {
|
export function useActivePlayerCurrency() {
|
||||||
@@ -19,7 +19,10 @@ export function useActivePlayerCurrency() {
|
|||||||
[currencies],
|
[currencies],
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeCurrency = selectedCurrency ?? profile?.default_currency?.toUpperCase() ?? "NPR";
|
const activeCurrency = useMemo(
|
||||||
|
() => pickActivePlayerCurrency(profile, currencies, selectedCurrency),
|
||||||
|
[currencies, profile, selectedCurrency],
|
||||||
|
);
|
||||||
|
|
||||||
const canSwitchCurrency = bettableCurrencies.length > 1;
|
const canSwitchCurrency = bettableCurrencies.length > 1;
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,13 @@ export function usePlayEffectiveWs(): void {
|
|||||||
channel.listen(".play.toggle", onRefresh("play_toggle"));
|
channel.listen(".play.toggle", onRefresh("play_toggle"));
|
||||||
channel.listen(".odds.update", onRefresh("odds"));
|
channel.listen(".odds.update", onRefresh("odds"));
|
||||||
channel.listen(".play.catalog_updated", (payload: { module?: string }) => {
|
channel.listen(".play.catalog_updated", (payload: { module?: string }) => {
|
||||||
const module = payload?.module;
|
const payloadModule = payload?.module;
|
||||||
const source: PlayCatalogRefreshSource =
|
const source: PlayCatalogRefreshSource =
|
||||||
module === "odds" ? "odds" : module === "risk_cap" ? "risk_cap" : "play_config";
|
payloadModule === "odds"
|
||||||
|
? "odds"
|
||||||
|
: payloadModule === "risk_cap"
|
||||||
|
? "risk_cap"
|
||||||
|
: "play_config";
|
||||||
dispatchPlayCatalogRefresh(source);
|
dispatchPlayCatalogRefresh(source);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -178,15 +178,15 @@
|
|||||||
"disabledDescription": "The admin has turned off Jackpot for now. This tab remains visible for information only.",
|
"disabledDescription": "The admin has turned off Jackpot for now. This tab remains visible for information only.",
|
||||||
"infoTitle": "Pool Information",
|
"infoTitle": "Pool Information",
|
||||||
"infoSubtitle": "Jackpot is currently enabled.",
|
"infoSubtitle": "Jackpot is currently enabled.",
|
||||||
"infoDescription": "Jackpot is not a standalone play. Eligible tickets placed in the active plays will join the pool automatically based on admin rules.",
|
"infoDescription": "Jackpot is not a standalone play. Only successful tickets that meet the pool threshold and admin rules will join the pool automatically.",
|
||||||
"closedInfoTitle": "Pool Information",
|
"closedInfoTitle": "Pool Information",
|
||||||
"closedInfoSubtitle": "Jackpot is currently enabled.",
|
"closedInfoSubtitle": "Jackpot is currently enabled.",
|
||||||
"closedInfoDescription": "Jackpot uses automatic participation for eligible tickets. This issue is closed, so please wait for the next issue."
|
"closedInfoDescription": "Jackpot uses automatic participation only for tickets that meet the pool threshold and admin rules. This issue is closed, so please wait for the next issue."
|
||||||
},
|
},
|
||||||
"jackpotParticipation": {
|
"jackpotParticipation": {
|
||||||
"title": "Automatic Jackpot Participation",
|
"title": "Automatic Jackpot Participation",
|
||||||
"previewDescription": "If this ticket is submitted successfully and meets the configured rules, it will join the Jackpot pool automatically. No separate Jackpot bet is needed.",
|
"previewDescription": "If this ticket is submitted successfully and its lines meet the pool threshold and configured rules, they will join the Jackpot pool automatically. No separate Jackpot bet is needed.",
|
||||||
"resultDescription": "The successful ticket lines in this order will join the Jackpot pool automatically under the configured rules. If a first-prize hit later triggers the pool, it will settle together with the normal payout."
|
"resultDescription": "Among the successful ticket lines in this order, only those that meet the pool threshold and configured rules will join the Jackpot pool automatically. If a first-prize hit later triggers the pool, it will settle together with the normal payout."
|
||||||
},
|
},
|
||||||
"boxMode": {
|
"boxMode": {
|
||||||
"iboxTitle": "iBox",
|
"iboxTitle": "iBox",
|
||||||
|
|||||||
@@ -178,15 +178,15 @@
|
|||||||
"disabledDescription": "एडमिनले Jackpot बन्द गरेको छ। यो ट्याब अहिले जानकारीका लागि मात्र देखाइन्छ।",
|
"disabledDescription": "एडमिनले Jackpot बन्द गरेको छ। यो ट्याब अहिले जानकारीका लागि मात्र देखाइन्छ।",
|
||||||
"infoTitle": "पूल जानकारी",
|
"infoTitle": "पूल जानकारी",
|
||||||
"infoSubtitle": "Jackpot अहिले सक्षम छ।",
|
"infoSubtitle": "Jackpot अहिले सक्षम छ।",
|
||||||
"infoDescription": "Jackpot छुट्टै बेट होइन। उपलब्ध खेलमा गरिएको योग्य टिकटहरू एडमिन नियमअनुसार स्वतः Jackpot पूलमा सहभागी हुन्छन्।",
|
"infoDescription": "Jackpot छुट्टै बेट होइन। सफल भई pool threshold र admin नियम पूरा गरेका टिकटहरू मात्र स्वतः Jackpot पूलमा सहभागी हुन्छन्।",
|
||||||
"closedInfoTitle": "पूल जानकारी",
|
"closedInfoTitle": "पूल जानकारी",
|
||||||
"closedInfoSubtitle": "Jackpot अहिले सक्षम छ।",
|
"closedInfoSubtitle": "Jackpot अहिले सक्षम छ।",
|
||||||
"closedInfoDescription": "योग्य टिकटहरू स्वतः Jackpot मा सहभागी हुन्छन्। यो इश्यू बन्द भइसकेको छ, त्यसैले अर्को इश्यू पर्खनुहोस्।"
|
"closedInfoDescription": "pool threshold र admin नियम पूरा गरेका योग्य टिकटहरू मात्र स्वतः Jackpot मा सहभागी हुन्छन्। यो इश्यू बन्द भइसकेको छ, त्यसैले अर्को इश्यू पर्खनुहोस्।"
|
||||||
},
|
},
|
||||||
"jackpotParticipation": {
|
"jackpotParticipation": {
|
||||||
"title": "Jackpot स्वतः सहभागिता",
|
"title": "Jackpot स्वतः सहभागिता",
|
||||||
"previewDescription": "यदि यो टिकट सफलतापूर्वक पेश भयो र नियम पूरा गर्यो भने, यो स्वतः Jackpot पूलमा सहभागी हुनेछ। छुट्टै Jackpot बेट आवश्यक छैन।",
|
"previewDescription": "यदि यो टिकट सफलतापूर्वक पेश भयो र यसको लाइनहरूले pool threshold तथा कन्फिगर गरिएको नियम पूरा गरे भने, ती स्वतः Jackpot पूलमा सहभागी हुनेछन्। छुट्टै Jackpot बेट आवश्यक छैन।",
|
||||||
"resultDescription": "यस अर्डरका सफल टिकटहरू कन्फिगर गरिएको नियमअनुसार स्वतः Jackpot पूलमा सहभागी हुनेछन्। पछि पहिलो पुरस्कार र पूल ट्रिगर सर्त पूरा भएमा, नियमित payout सँगै सेटल हुनेछ।"
|
"resultDescription": "यस अर्डरका सफल टिकटहरूमध्ये pool threshold तथा कन्फिगर गरिएको नियम पूरा गरेका लाइनहरू मात्र स्वतः Jackpot पूलमा सहभागी हुनेछन्। पछि पहिलो पुरस्कार र पूल ट्रिगर सर्त पूरा भएमा, नियमित payout सँगै सेटल हुनेछ।"
|
||||||
},
|
},
|
||||||
"boxMode": {
|
"boxMode": {
|
||||||
"iboxTitle": "iBox",
|
"iboxTitle": "iBox",
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"title": "待对账提醒",
|
"title": "待对账提醒",
|
||||||
"subtitle": "以下划转尚未最终确认,不代表已成功到账或扣款。若长时间未更新,请联系客服。",
|
|
||||||
"empty": "暂无待对账记录",
|
"empty": "暂无待对账记录",
|
||||||
"pendingBadge": "待对账",
|
"pendingBadge": "待对账",
|
||||||
"amountLabel": "涉及金额:",
|
"amountLabel": "涉及金额:",
|
||||||
@@ -177,15 +176,15 @@
|
|||||||
"disabledDescription": "后台已关闭 Jackpot 功能,当前仅保留展示页签。",
|
"disabledDescription": "后台已关闭 Jackpot 功能,当前仅保留展示页签。",
|
||||||
"infoTitle": "奖池信息",
|
"infoTitle": "奖池信息",
|
||||||
"infoSubtitle": "Jackpot 当前已启用。",
|
"infoSubtitle": "Jackpot 当前已启用。",
|
||||||
"infoDescription": "Jackpot 不是单独下注玩法。玩家在开放玩法中提交有效注单后,将按后台规则自动参与奖池。",
|
"infoDescription": "Jackpot 不是单独下注玩法。玩家在开放玩法中提交成功且满足奖池门槛的有效注单后,才会按后台规则自动参与奖池。",
|
||||||
"closedInfoTitle": "奖池信息",
|
"closedInfoTitle": "奖池信息",
|
||||||
"closedInfoSubtitle": "Jackpot 当前已启用。",
|
"closedInfoSubtitle": "Jackpot 当前已启用。",
|
||||||
"closedInfoDescription": "Jackpot 采用有效注单自动参与机制。本期下注窗口已关闭,请等待下一期开放后参与。"
|
"closedInfoDescription": "Jackpot 采用满足奖池门槛的有效注单自动参与机制。本期下注窗口已关闭,请等待下一期开放后参与。"
|
||||||
},
|
},
|
||||||
"jackpotParticipation": {
|
"jackpotParticipation": {
|
||||||
"title": "Jackpot 自动参与",
|
"title": "Jackpot 自动参与",
|
||||||
"previewDescription": "若本单成功提交且满足后台规则,系统会自动按有效注单参与 Jackpot 奖池,无需单独再下一笔。",
|
"previewDescription": "若本单成功提交,且注项满足奖池门槛和后台规则,系统才会自动参与 Jackpot 奖池,无需单独再下一笔。",
|
||||||
"resultDescription": "本次成功注项将按后台规则自动参与 Jackpot 奖池,后续若命中头奖并满足触发条件,将与常规派彩一并结算。"
|
"resultDescription": "本次成功注项中,只有满足奖池门槛和后台规则的部分才会自动参与 Jackpot 奖池;后续若命中头奖并满足触发条件,将与常规派彩一并结算。"
|
||||||
},
|
},
|
||||||
"boxMode": {
|
"boxMode": {
|
||||||
"iboxTitle": "iBox",
|
"iboxTitle": "iBox",
|
||||||
|
|||||||
Reference in New Issue
Block a user