@devrongx/games 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @devrongx/games — games/prematch-bets/PreMatchBetsPopup.tsx
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
import { useState, useCallback, useMemo } from "react";
|
|
4
|
+
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
5
5
|
import { Loader2 } from "lucide-react";
|
|
6
6
|
import { useGamePopupStore } from "../../core/gamePopupStore";
|
|
7
7
|
import {
|
|
@@ -24,6 +24,7 @@ import { useTDPool, useTDPoolEntry, useTDLeaderboard } from "../../pools/hooks";
|
|
|
24
24
|
import { buildPMBConfig } from "../../pools/mapper";
|
|
25
25
|
import type { ITDMatch } from "../../matches/types";
|
|
26
26
|
import { joinTDPool, placeTDBets } from "../../pools/actions";
|
|
27
|
+
import { saveTDDraftBetsApi } from "../../pools/fetcher";
|
|
27
28
|
import type { ITDLeaderboardEntry } from "../../pools/types";
|
|
28
29
|
import { TDPoolStatus } from "../../pools/types";
|
|
29
30
|
import { calcRankPayout } from "./config";
|
|
@@ -122,6 +123,60 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
|
|
|
122
123
|
const [submitting, setSubmitting] = useState(false);
|
|
123
124
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
124
125
|
|
|
126
|
+
// ── Draft restore: when entry + config load, seed bets from draft_selections ─
|
|
127
|
+
const draftRestoredRef = useRef(false);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!poolId || !config || !entryData || draftRestoredRef.current) return;
|
|
130
|
+
const draft = entryData.entry.draft_selections;
|
|
131
|
+
if (!draft || draft.length === 0) return;
|
|
132
|
+
if (entryData.bets.length > 0) return; // real bets already placed — don't overwrite
|
|
133
|
+
const restored: IUserBets = {};
|
|
134
|
+
for (const d of draft) {
|
|
135
|
+
const mIdx = config.markets.findIndex((m) => m.backendChallengeId === d.challenge_id);
|
|
136
|
+
if (mIdx < 0) continue;
|
|
137
|
+
const optionIdx = config.markets[mIdx].options.findIndex(
|
|
138
|
+
(o) => o.label.toLowerCase().replace(/\s+/g, "_") === d.selected_option,
|
|
139
|
+
);
|
|
140
|
+
if (optionIdx < 0) continue;
|
|
141
|
+
restored[mIdx] = { optionIdx, amount: d.coin_amount, parlaySlot: null };
|
|
142
|
+
}
|
|
143
|
+
if (Object.keys(restored).length > 0) {
|
|
144
|
+
setBets(restored);
|
|
145
|
+
setUserFlowState("game");
|
|
146
|
+
}
|
|
147
|
+
draftRestoredRef.current = true;
|
|
148
|
+
}, [poolId, config, entryData]);
|
|
149
|
+
|
|
150
|
+
// ── Debounced draft save: persists bets to backend 1s after last change ───
|
|
151
|
+
const saveDraftTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!poolId || Object.keys(bets).length === 0 || !config) return;
|
|
154
|
+
if (saveDraftTimerRef.current) clearTimeout(saveDraftTimerRef.current);
|
|
155
|
+
saveDraftTimerRef.current = setTimeout(async () => {
|
|
156
|
+
try {
|
|
157
|
+
const selections = config.markets
|
|
158
|
+
.map((m, mIdx) => {
|
|
159
|
+
const b = bets[mIdx];
|
|
160
|
+
if (!b || !m.backendChallengeId) return null;
|
|
161
|
+
const option = m.options[b.optionIdx];
|
|
162
|
+
if (!option) return null;
|
|
163
|
+
return {
|
|
164
|
+
challenge_id: m.backendChallengeId,
|
|
165
|
+
selected_option: option.label.toLowerCase().replace(/\s+/g, "_"),
|
|
166
|
+
coin_amount: b.amount,
|
|
167
|
+
};
|
|
168
|
+
})
|
|
169
|
+
.filter((b): b is NonNullable<typeof b> => b !== null);
|
|
170
|
+
if (selections.length > 0) {
|
|
171
|
+
await saveTDDraftBetsApi(poolId, selections);
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
// Silent — draft save is best-effort
|
|
175
|
+
}
|
|
176
|
+
}, 1000);
|
|
177
|
+
return () => { if (saveDraftTimerRef.current) clearTimeout(saveDraftTimerRef.current); };
|
|
178
|
+
}, [bets, poolId, config]);
|
|
179
|
+
|
|
125
180
|
const betSummary = useMemo(
|
|
126
181
|
() => config ? calcBetSummary(bets, config) : { selectedCount: 0, compoundMultiplier: 0, totalEntry: 0, baseReward: 0, compoundedReward: 0, remainingBalance: 0, riskPercent: 0, potentialBalance: 0 },
|
|
127
182
|
[bets, config],
|
|
@@ -152,6 +207,7 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
|
|
|
152
207
|
pickers[Number(mIdxStr)] = entry.optionIdx;
|
|
153
208
|
}
|
|
154
209
|
setExpandedPicker(pickers);
|
|
210
|
+
setUserFlowState("game");
|
|
155
211
|
}, []);
|
|
156
212
|
|
|
157
213
|
const handleOptionClick = useCallback((mIdx: number, oIdx: number) => {
|
package/src/pools/fetcher.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ITDMyEntryResponse,
|
|
10
10
|
ITDLeaderboardResponse,
|
|
11
11
|
ITDBetInput,
|
|
12
|
+
ITDDraftSelection,
|
|
12
13
|
} from "./types";
|
|
13
14
|
|
|
14
15
|
// Shared fetch helper — adds auth header if token exists
|
|
@@ -78,6 +79,17 @@ export async function joinTDPoolApi(poolId: number): Promise<{ id: number }> {
|
|
|
78
79
|
return tdFetch<{ id: number }>(`/api/pools/${poolId}/join`, { method: "POST", body: "{}" });
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// PUT /api/pools/:id/draft-bets
|
|
83
|
+
export async function saveTDDraftBetsApi(
|
|
84
|
+
poolId: number,
|
|
85
|
+
selections: ITDDraftSelection[],
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
await tdFetch<unknown>(`/api/pools/${poolId}/draft-bets`, {
|
|
88
|
+
method: "PUT",
|
|
89
|
+
body: JSON.stringify({ selections }),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
// POST /api/pools/:id/bets
|
|
82
94
|
export async function placeTDBetsApi(
|
|
83
95
|
poolId: number,
|
package/src/pools/types.ts
CHANGED
|
@@ -163,6 +163,12 @@ export interface ITDLeaderboardResponse {
|
|
|
163
163
|
|
|
164
164
|
// ─── Pool entry (GET /api/pools/:id/my-entry) ─────────────────────────────────
|
|
165
165
|
|
|
166
|
+
export interface ITDDraftSelection {
|
|
167
|
+
challenge_id: number;
|
|
168
|
+
selected_option: string;
|
|
169
|
+
coin_amount: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
166
172
|
export interface ITDPoolEntryRecord {
|
|
167
173
|
id: number;
|
|
168
174
|
pool_id: number;
|
|
@@ -178,6 +184,7 @@ export interface ITDPoolEntryRecord {
|
|
|
178
184
|
questions_left: number;
|
|
179
185
|
questions_total: number;
|
|
180
186
|
active_parlays: number;
|
|
187
|
+
draft_selections: ITDDraftSelection[] | null;
|
|
181
188
|
created_at: string;
|
|
182
189
|
}
|
|
183
190
|
|