feat(game): 添加游戏历史记录实时同步功能
- 实现了游戏历史记录的实时推送和展示功能 - 添加了总奖池金额显示在历史记录界面中 - 新增 PeriodPayoutHistoryEntry 和 PeriodPayoutEventData 类型定义 - 在游戏状态管理中添加了待处理赔付历史记录状态 - 实现了 WebSocket 消息解析器来处理赔付数据 - 添加了移动端和桌面端历史记录组件的实时数据展示 - 集成了注册邀请码查询参数功能并优化了注册表单逻辑 - 重构了历史记录视图模型以支持实时数据流处理
This commit is contained in:
@@ -50,11 +50,8 @@ export const SMS_CODE_COOLDOWN_FALLBACK_SECONDS = 60
|
||||
/** @description 发送短信验证码的业务事件类型,当前固定为注册场景。 */
|
||||
export const SMS_SEND_EVENT_REGISTER = 'user_register'
|
||||
|
||||
/** @description 注册邀请码默认值,URL 未携带邀请码参数时兜底。 */
|
||||
export const DEFAULT_REGISTER_INVITE_CODE = 'D97DBC16'
|
||||
|
||||
/** @description 注册邀请码在 URL 查询参数中的字段名。 */
|
||||
export const REGISTER_INVITE_CODE_QUERY_PARAM = 'registerInviteCode'
|
||||
export const REGISTER_INVITE_CODE_QUERY_PARAM = 'invit_code'
|
||||
|
||||
/** @description 触发登录提示(弹窗/Toast)的最小去重间隔,单位为毫秒。 */
|
||||
export const LOGIN_PROMPT_DEDUP_MS = 1200
|
||||
|
||||
@@ -231,6 +231,14 @@ export function DesktopGameHistory() {
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-[1fr_1fr] gap-design-5">
|
||||
<div className="flex min-h-design-28 items-center justify-between gap-design-6 rounded-[calc(var(--design-unit)*6)] border border-[rgba(94,212,230,0.14)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(12,78,94,0.14))] px-design-7 py-design-4">
|
||||
<span className="shrink-0 text-[#84A2A2]">
|
||||
{t('gameDesktop.history.totalPoolAmount')}
|
||||
</span>
|
||||
<span className="min-w-0 truncate text-right text-design-14 font-bold text-[#D5FBFF] [text-shadow:0_0_calc(var(--design-unit)*8)_rgba(95,233,255,0.18)]">
|
||||
{item.betAmountLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex min-h-design-28 items-center justify-between gap-design-6 rounded-[calc(var(--design-unit)*6)] border border-[rgba(255,227,117,0.14)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(84,57,8,0.14))] px-design-7 py-design-4">
|
||||
<span className="shrink-0 text-[#84A2A2]">
|
||||
{t('gameDesktop.history.payout')}
|
||||
@@ -239,7 +247,7 @@ export function DesktopGameHistory() {
|
||||
{item.winAmountLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex min-h-design-28 items-center justify-between gap-design-6 rounded-[calc(var(--design-unit)*6)] border border-[rgba(255,117,117,0.16)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(88,20,28,0.14))] px-design-7 py-design-4">
|
||||
<div className="col-span-2 flex min-h-design-28 items-center justify-between gap-design-6 rounded-[calc(var(--design-unit)*6)] border border-[rgba(255,117,117,0.16)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(88,20,28,0.14))] px-design-7 py-design-4">
|
||||
<span className={'shrink-0 text-[#84A2A2]'}>
|
||||
{t('gameDesktop.history.winningResult')}
|
||||
</span>
|
||||
|
||||
@@ -26,7 +26,7 @@ function HistoryRewardNumber({
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex h-design-24 w-design-24 shrink-0 items-center justify-center rounded-[calc(var(--design-unit)*4)] border border-[rgba(95,233,255,0.16)] bg-[rgba(6,28,39,0.38)] p-design-1 align-middle ${className ?? ''}`}
|
||||
className={`inline-flex h-design-24 w-design-24 shrink-0 items-center justify-center rounded-[calc(var(--design-unit)*4)]bg-[rgba(6,28,39,0.38)] p-design-1 align-middle ${className ?? ''}`}
|
||||
title={label}
|
||||
>
|
||||
<RewardSpriteImage
|
||||
@@ -185,6 +185,15 @@ export function MobileGameHistory() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-design-4">
|
||||
<div className="flex min-h-design-22 items-center justify-between gap-design-4 rounded-[calc(var(--design-unit)*5)] border border-[rgba(94,212,230,0.14)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(12,78,94,0.14))] px-design-6 py-design-4">
|
||||
<span className="shrink-0 text-[#84A2A2]">
|
||||
{t('gameDesktop.history.totalPoolAmount')}
|
||||
</span>
|
||||
<span className="min-w-0 truncate text-right text-design-11 font-bold text-[#D5FBFF]">
|
||||
{item.betAmountLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-design-22 items-center justify-between gap-design-4 rounded-[calc(var(--design-unit)*5)] border border-[rgba(255,227,117,0.14)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(84,57,8,0.14))] px-design-6 py-design-4">
|
||||
<span className="shrink-0 text-[#84A2A2]">
|
||||
{t('gameDesktop.history.payout')}
|
||||
@@ -194,7 +203,7 @@ export function MobileGameHistory() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-design-22 items-center justify-between gap-design-4 rounded-[calc(var(--design-unit)*5)] border border-[rgba(255,117,117,0.16)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(88,20,28,0.14))] px-design-6 py-design-4">
|
||||
<div className="col-span-2 flex min-h-design-22 items-center justify-between gap-design-4 rounded-[calc(var(--design-unit)*5)] border border-[rgba(255,117,117,0.16)] bg-[linear-gradient(90deg,rgba(5,23,33,0.54),rgba(88,20,28,0.14))] px-design-6 py-design-4">
|
||||
<span className="shrink-0 text-[#84A2A2]">
|
||||
{t('gameDesktop.history.winningResult')}
|
||||
</span>
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getGameBetMyOrders } from '@/api'
|
||||
import { GAME_HISTORY_PAGE_SIZE } from '@/constants'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { useGameRoundStore } from '@/store/game'
|
||||
import type { HistoryResultState } from '@/type'
|
||||
import type { HistoryResultState, PeriodPayoutHistoryEntry } from '@/type'
|
||||
|
||||
interface GameHistoryDisplayItem {
|
||||
betAmountLabel: string
|
||||
createdAtLabel: string
|
||||
id: string
|
||||
resultState: HistoryResultState
|
||||
numbersLabel: string
|
||||
numbers: number[]
|
||||
orderNo: string
|
||||
periodNo: string
|
||||
resultNumber: number | null
|
||||
resultNumberLabel: string
|
||||
winAmountLabel: string
|
||||
}
|
||||
|
||||
function formatCreatedTime(timestamp: number, locale: string) {
|
||||
const date = new Date(timestamp * 1000)
|
||||
@@ -32,15 +46,45 @@ function formatNumbers(numbers: number[]) {
|
||||
return numbers.map((number) => String(number).padStart(2, '0')).join(', ')
|
||||
}
|
||||
|
||||
function toPayoutHistoryDisplayItem(
|
||||
entry: PeriodPayoutHistoryEntry,
|
||||
locale: string,
|
||||
): GameHistoryDisplayItem {
|
||||
return {
|
||||
betAmountLabel: entry.betAmount,
|
||||
createdAtLabel: formatCreatedTime(entry.serverTime, locale),
|
||||
id: entry.id,
|
||||
resultState: entry.betNumbers.includes(entry.resultNumber)
|
||||
? ('win' satisfies HistoryResultState)
|
||||
: ('lost' satisfies HistoryResultState),
|
||||
numbersLabel: formatNumbers(entry.betNumbers),
|
||||
numbers: entry.betNumbers,
|
||||
orderNo: entry.id,
|
||||
periodNo: entry.periodNo,
|
||||
resultNumber: entry.resultNumber,
|
||||
resultNumberLabel: String(entry.resultNumber).padStart(2, '0'),
|
||||
winAmountLabel: entry.winAmount,
|
||||
}
|
||||
}
|
||||
|
||||
export function useGameHistoryVm() {
|
||||
const { i18n, t } = useTranslation()
|
||||
const locale = i18n.resolvedLanguage ?? 'en-US'
|
||||
const accessToken = useAuthStore((state) => state.accessToken)
|
||||
const authStatus = useAuthStore((state) => state.status)
|
||||
const revealPhase = useGameRoundStore((state) => state.revealAnimation.phase)
|
||||
const revealRoundId = useGameRoundStore(
|
||||
(state) => state.revealAnimation.roundId,
|
||||
)
|
||||
const lastRevealedRoundRef = useRef<string | null>(null)
|
||||
const pendingPayoutHistoryEntryId = useGameRoundStore(
|
||||
(state) => state.pendingPayoutHistoryEntry?.id ?? null,
|
||||
)
|
||||
const consumePendingPayoutHistoryEntry = useGameRoundStore(
|
||||
(state) => state.consumePendingPayoutHistoryEntry,
|
||||
)
|
||||
const [realtimeEntries, setRealtimeEntries] = useState<
|
||||
PeriodPayoutHistoryEntry[]
|
||||
>([])
|
||||
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: ['game', 'bet-my-orders', accessToken],
|
||||
@@ -62,7 +106,7 @@ export function useGameHistoryVm() {
|
||||
},
|
||||
})
|
||||
|
||||
const items = useMemo(
|
||||
const apiItems = useMemo(
|
||||
() =>
|
||||
(query.data?.pages ?? []).flatMap((page) =>
|
||||
(page.list ?? []).map((entry) => {
|
||||
@@ -71,11 +115,8 @@ export function useGameHistoryVm() {
|
||||
const resultNumber = shouldHideResult ? null : entry.result_number
|
||||
|
||||
return {
|
||||
amountLabel: entry.total_amount,
|
||||
createdAtLabel: formatCreatedTime(
|
||||
entry.create_time,
|
||||
i18n.resolvedLanguage ?? 'en-US',
|
||||
),
|
||||
betAmountLabel: entry.bet_amount,
|
||||
createdAtLabel: formatCreatedTime(entry.create_time, locale),
|
||||
id: entry.order_no,
|
||||
resultState:
|
||||
resultNumber === null
|
||||
@@ -93,33 +134,52 @@ export function useGameHistoryVm() {
|
||||
? '--'
|
||||
: String(resultNumber).padStart(2, '0'),
|
||||
winAmountLabel: entry.win_amount,
|
||||
}
|
||||
} satisfies GameHistoryDisplayItem
|
||||
}),
|
||||
),
|
||||
[i18n.resolvedLanguage, query.data?.pages, revealPhase, revealRoundId],
|
||||
[locale, query.data?.pages, revealPhase, revealRoundId],
|
||||
)
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
...realtimeEntries.map((entry) =>
|
||||
toPayoutHistoryDisplayItem(entry, locale),
|
||||
),
|
||||
...apiItems.filter(
|
||||
(item) =>
|
||||
!realtimeEntries.some((entry) => entry.periodNo === item.periodNo),
|
||||
),
|
||||
],
|
||||
[apiItems, locale, realtimeEntries],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (revealPhase !== 'result' || !revealRoundId) {
|
||||
if (
|
||||
revealPhase !== 'result' ||
|
||||
!revealRoundId ||
|
||||
!pendingPayoutHistoryEntryId
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (lastRevealedRoundRef.current === revealRoundId) {
|
||||
if (authStatus !== 'authenticated') {
|
||||
return
|
||||
}
|
||||
|
||||
if (authStatus !== 'authenticated' || query.isFetching || query.isLoading) {
|
||||
const entry = consumePendingPayoutHistoryEntry(revealRoundId)
|
||||
|
||||
if (!entry) {
|
||||
return
|
||||
}
|
||||
|
||||
lastRevealedRoundRef.current = revealRoundId
|
||||
|
||||
void query.refetch()
|
||||
setRealtimeEntries((entries) => [
|
||||
entry,
|
||||
...entries.filter((item) => item.periodNo !== entry.periodNo),
|
||||
])
|
||||
}, [
|
||||
authStatus,
|
||||
query.isFetching,
|
||||
query.isLoading,
|
||||
query.refetch,
|
||||
consumePendingPayoutHistoryEntry,
|
||||
pendingPayoutHistoryEntryId,
|
||||
revealPhase,
|
||||
revealRoundId,
|
||||
])
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
extractBetWinData,
|
||||
extractJackpotHitData,
|
||||
extractPeriodEventData,
|
||||
extractPeriodPayoutData,
|
||||
extractPeriodTick,
|
||||
extractServerTime,
|
||||
extractUserStreakMessageData,
|
||||
@@ -180,7 +181,8 @@ function applyPeriodPayoutMessage(
|
||||
) {
|
||||
applyPeriodMessage(message, serverTime)
|
||||
|
||||
const period = extractPeriodEventData(message)
|
||||
const payout = extractPeriodPayoutData(message)
|
||||
const period = payout ?? extractPeriodEventData(message)
|
||||
|
||||
if (period?.resultNumber !== null && period?.resultNumber !== undefined) {
|
||||
const roundState = useGameRoundStore.getState()
|
||||
@@ -195,6 +197,21 @@ function applyPeriodPayoutMessage(
|
||||
roundId: period.periodNo,
|
||||
winningCellId: period.resultNumber,
|
||||
})
|
||||
|
||||
if (payout && payout.betNumbers.length > 0) {
|
||||
roundState.setPendingPayoutHistoryEntry({
|
||||
betAmount: payout.betAmount ?? '0.00',
|
||||
betFlowerNames: payout.betFlowerNames,
|
||||
betNumbers: payout.betNumbers,
|
||||
id: `period-payout:${payout.periodNo}`,
|
||||
periodNo: payout.periodNo,
|
||||
resultFlowerName: payout.resultFlowerName,
|
||||
resultNumber: period.resultNumber,
|
||||
serverTime:
|
||||
payout.serverTime ?? serverTime ?? Math.floor(Date.now() / 1000),
|
||||
winAmount: payout.winAmount ?? '0.00',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const roundId = period?.periodNo ?? useGameRoundStore.getState().round.id
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { registerWithPassword } from '@/api'
|
||||
import {
|
||||
DEFAULT_REGISTER_INVITE_CODE,
|
||||
REGISTER_INVITE_CODE_QUERY_PARAM,
|
||||
} from '@/constants'
|
||||
import i18n from '@/i18n'
|
||||
import { notify } from '@/lib/notify'
|
||||
import { registerFormSchema } from '@/schema/auth-schema'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import type { RegisterFormValues, UseRegisterFormOptions } from '@/type'
|
||||
import { toAuthSubmitErrorKey } from './auth-error-key'
|
||||
import { getRegisterInviteCodeFromSearch } from './use-register-invite-code'
|
||||
import { createZodResolver } from './zod-form-resolver'
|
||||
|
||||
function getInitialRegisterInviteCode() {
|
||||
if (typeof window === 'undefined') {
|
||||
return DEFAULT_REGISTER_INVITE_CODE
|
||||
}
|
||||
|
||||
return (
|
||||
new URLSearchParams(window.location.search)
|
||||
.get(REGISTER_INVITE_CODE_QUERY_PARAM)
|
||||
?.trim() || DEFAULT_REGISTER_INVITE_CODE
|
||||
)
|
||||
return getRegisterInviteCodeFromSearch()
|
||||
}
|
||||
|
||||
export function useRegisterForm({ onSuccess }: UseRegisterFormOptions = {}) {
|
||||
|
||||
41
src/hooks/use-register-invite-code.ts
Normal file
41
src/hooks/use-register-invite-code.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { REGISTER_INVITE_CODE_QUERY_PARAM } from '@/constants'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { useModalStore } from '@/store/modal'
|
||||
|
||||
export function getRegisterInviteCodeFromSearch(search?: string) {
|
||||
const sourceSearch =
|
||||
search ?? (typeof window === 'undefined' ? '' : window.location.search)
|
||||
|
||||
return (
|
||||
new URLSearchParams(sourceSearch)
|
||||
.get(REGISTER_INVITE_CODE_QUERY_PARAM)
|
||||
?.trim() ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
export function useAutoOpenRegisterByInviteCode() {
|
||||
const handledInviteCodeRef = useRef<string | null>(null)
|
||||
const authIsHydrated = useAuthStore((state) => state.isHydrated)
|
||||
const authStatus = useAuthStore((state) => state.status)
|
||||
const openExclusiveModal = useModalStore((state) => state.openExclusiveModal)
|
||||
const inviteCode = useMemo(() => getRegisterInviteCodeFromSearch(), [])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!authIsHydrated ||
|
||||
!inviteCode ||
|
||||
handledInviteCodeRef.current === inviteCode
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
handledInviteCodeRef.current = inviteCode
|
||||
|
||||
if (authStatus === 'authenticated') {
|
||||
return
|
||||
}
|
||||
|
||||
openExclusiveModal('desktopRegister')
|
||||
}, [authIsHydrated, authStatus, inviteCode, openExclusiveModal])
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
JackpotHitEventDataDto,
|
||||
JackpotHitItemDto,
|
||||
PeriodEventData,
|
||||
PeriodPayoutEventData,
|
||||
UserStreakMessageData,
|
||||
WalletChangedData,
|
||||
} from '@/type'
|
||||
@@ -187,6 +188,58 @@ export function extractPeriodEventData(
|
||||
}
|
||||
}
|
||||
|
||||
function toNumberArray(value: unknown) {
|
||||
if (!Array.isArray(value)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return value
|
||||
.map((item) => toOptionalNumber(item))
|
||||
.filter(
|
||||
(item): item is number =>
|
||||
typeof item === 'number' && Number.isInteger(item),
|
||||
)
|
||||
}
|
||||
|
||||
function toStringArray(value: unknown) {
|
||||
if (!Array.isArray(value)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return value
|
||||
.map((item) => toOptionalString(item))
|
||||
.filter((item): item is string => typeof item === 'string')
|
||||
}
|
||||
|
||||
export function extractPeriodPayoutData(
|
||||
message: GameSocketMessage,
|
||||
): PeriodPayoutEventData | null {
|
||||
const period = extractPeriodEventData(message)
|
||||
|
||||
if (!period) {
|
||||
return null
|
||||
}
|
||||
|
||||
const data = getNestedRecord(message, 'data')
|
||||
const source = data ?? (message as Record<string, unknown>)
|
||||
const root = message as Record<string, unknown>
|
||||
|
||||
return {
|
||||
...period,
|
||||
betAmount: toOptionalString(source.bet_amount) ?? null,
|
||||
betFlowerNames: toStringArray(source.bet_flower_names),
|
||||
betNumbers: toNumberArray(source.bet_numbers),
|
||||
payoutRemainingSeconds:
|
||||
toOptionalNumber(source.payout_remaining_seconds) ?? null,
|
||||
payoutSeconds: toOptionalNumber(source.payout_seconds) ?? null,
|
||||
payoutUntil: toOptionalNumber(source.payout_until) ?? null,
|
||||
resultFlowerName: toOptionalString(source.result_flower_name) ?? null,
|
||||
serverTime:
|
||||
toOptionalNumber(source.server_time ?? root.server_time) ?? null,
|
||||
winAmount: toOptionalString(source.win_amount) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
export function extractWalletChangedData(
|
||||
message: GameSocketMessage,
|
||||
): WalletChangedData | null {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { getGameLobbyInit } from '@/api'
|
||||
import { MOBILE_LAYOUT_BREAKPOINT_PX } from '@/constants'
|
||||
import { useGameRealtimeSync } from '@/hooks/use-game-realtime-sync.ts'
|
||||
import { useAutoOpenRegisterByInviteCode } from '@/hooks/use-register-invite-code'
|
||||
import { useDocumentMetadata } from '@/lib/head/document-metadata.ts'
|
||||
import { notify } from '@/lib/notify.ts'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
@@ -24,6 +25,7 @@ const PcEntry = lazy(async () => {
|
||||
export function MainEntryPage() {
|
||||
const { t } = useTranslation()
|
||||
useGameRealtimeSync()
|
||||
useAutoOpenRegisterByInviteCode()
|
||||
const hydrateRound = useGameRoundStore((state) => state.hydrateRound)
|
||||
const selectChip = useGameRoundStore((state) => state.selectChip)
|
||||
const hydrateSession = useGameSessionStore((state) => state.hydrateSession)
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
GameRoundSlice,
|
||||
GameRoundStoreState,
|
||||
HistoryEntry,
|
||||
PeriodPayoutHistoryEntry,
|
||||
RevealAnimationState,
|
||||
RoundSnapshot,
|
||||
TrendEntry,
|
||||
@@ -119,6 +120,7 @@ function resolveSelectionQuantity(
|
||||
function createInitialRoundState(): GameRoundSlice & {
|
||||
activeChipId: string
|
||||
activeBetQuantity: number
|
||||
pendingPayoutHistoryEntry: PeriodPayoutHistoryEntry | null
|
||||
recentSuccessfulSelections: BetSelection[]
|
||||
revealAnimation: RevealAnimationState
|
||||
} {
|
||||
@@ -131,6 +133,7 @@ function createInitialRoundState(): GameRoundSlice & {
|
||||
chips: snapshot.chips,
|
||||
history: snapshot.history,
|
||||
maxSelectionCount: snapshot.maxSelectionCount,
|
||||
pendingPayoutHistoryEntry: null,
|
||||
recentSuccessfulSelections: [],
|
||||
revealAnimation: createIdleRevealAnimation(),
|
||||
round: snapshot.round,
|
||||
@@ -139,7 +142,7 @@ function createInitialRoundState(): GameRoundSlice & {
|
||||
}
|
||||
}
|
||||
|
||||
export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
|
||||
export const useGameRoundStore = create<GameRoundStoreState>()((set, get) => ({
|
||||
...createInitialRoundState(),
|
||||
adjustBetQuantity: (delta) => {
|
||||
set((state) => {
|
||||
@@ -181,6 +184,18 @@ export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
|
||||
},
|
||||
}))
|
||||
},
|
||||
consumePendingPayoutHistoryEntry: (roundId) => {
|
||||
const state = get()
|
||||
const entry = state.pendingPayoutHistoryEntry
|
||||
|
||||
if (!entry || entry.periodNo !== roundId) {
|
||||
return null
|
||||
}
|
||||
|
||||
set({ pendingPayoutHistoryEntry: null })
|
||||
|
||||
return entry
|
||||
},
|
||||
finishRevealAnimation: () => {
|
||||
set((state) => {
|
||||
if (
|
||||
@@ -244,6 +259,7 @@ export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
|
||||
}
|
||||
})
|
||||
},
|
||||
pendingPayoutHistoryEntry: null,
|
||||
placeBet: (cellId) => {
|
||||
set((state) => {
|
||||
const activeChip =
|
||||
@@ -328,7 +344,7 @@ export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
|
||||
}))
|
||||
},
|
||||
restoreRecentSuccessfulSelections: () => {
|
||||
const state = useGameRoundStore.getState()
|
||||
const state = get()
|
||||
|
||||
if (
|
||||
state.round.phase !== 'betting' ||
|
||||
@@ -468,6 +484,9 @@ export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
|
||||
}
|
||||
})
|
||||
},
|
||||
setPendingPayoutHistoryEntry: (entry) => {
|
||||
set({ pendingPayoutHistoryEntry: entry })
|
||||
},
|
||||
syncRound: (round) => {
|
||||
set((state) => {
|
||||
const previousRound = state.round
|
||||
|
||||
@@ -212,6 +212,30 @@ export interface PeriodEventData {
|
||||
resultNumber: number | null
|
||||
}
|
||||
|
||||
export interface PeriodPayoutHistoryEntry {
|
||||
betAmount: string
|
||||
betFlowerNames: string[]
|
||||
betNumbers: number[]
|
||||
id: string
|
||||
periodNo: string
|
||||
resultFlowerName: string | null
|
||||
resultNumber: number
|
||||
serverTime: number
|
||||
winAmount: string
|
||||
}
|
||||
|
||||
export interface PeriodPayoutEventData extends PeriodEventData {
|
||||
betAmount: string | null
|
||||
betFlowerNames: string[]
|
||||
betNumbers: number[]
|
||||
payoutRemainingSeconds: number | null
|
||||
payoutSeconds: number | null
|
||||
payoutUntil: number | null
|
||||
resultFlowerName: string | null
|
||||
serverTime: number | null
|
||||
winAmount: string | null
|
||||
}
|
||||
|
||||
export interface WalletChangedData {
|
||||
coin: string
|
||||
}
|
||||
@@ -550,6 +574,7 @@ export interface GameRoundStoreState extends GameRoundSlice {
|
||||
hydrateRound: (snapshot: GameRoundSlice) => void
|
||||
placeBet: (cellId: number) => void
|
||||
playPreparedRevealAnimation: (roundId?: string | null) => void
|
||||
pendingPayoutHistoryEntry: PeriodPayoutHistoryEntry | null
|
||||
prepareRevealAnimation: (input: {
|
||||
revealKey: string
|
||||
roundId: string
|
||||
@@ -559,6 +584,9 @@ export interface GameRoundStoreState extends GameRoundSlice {
|
||||
revealAnimation: RevealAnimationState
|
||||
removeSelectionsForCell: (cellId: number) => void
|
||||
restoreRecentSuccessfulSelections: () => boolean
|
||||
consumePendingPayoutHistoryEntry: (
|
||||
roundId: string,
|
||||
) => PeriodPayoutHistoryEntry | null
|
||||
setRecentSuccessfulSelections: (selections: BetSelection[]) => void
|
||||
selectChip: (chipId: string) => void
|
||||
setPhase: (phase: RoundPhase) => void
|
||||
@@ -569,6 +597,7 @@ export interface GameRoundStoreState extends GameRoundSlice {
|
||||
totalWin: string
|
||||
winningCellId?: number | null
|
||||
}) => void
|
||||
setPendingPayoutHistoryEntry: (entry: PeriodPayoutHistoryEntry | null) => void
|
||||
syncRound: (round: Partial<RoundSnapshot>) => void
|
||||
upsertSelections: (selections: BetSelection[]) => void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user