@devrongx/games 0.4.7 → 0.4.9

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrongx/games",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -125,6 +125,8 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
125
125
 
126
126
  // ── Draft restore: when entry + config load, seed bets from draft_selections ─
127
127
  const draftRestoredRef = useRef(false);
128
+ const [restoredSelections, setRestoredSelections] = useState<IUserBets | undefined>(undefined);
129
+ const [restoredStep, setRestoredStep] = useState<number | undefined>(undefined);
128
130
  useEffect(() => {
129
131
  if (!poolId || !config || !entryData || draftRestoredRef.current) return;
130
132
  const draft = entryData.entry.draft_selections;
@@ -140,9 +142,18 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
140
142
  if (optionIdx < 0) continue;
141
143
  restored[mIdx] = { optionIdx, amount: d.coin_amount, parlaySlot: null };
142
144
  }
143
- if (Object.keys(restored).length > 0) {
144
- setBets(restored);
145
+ const answeredCount = Object.keys(restored).length;
146
+ if (answeredCount === 0) return;
147
+ const allAnswered = answeredCount === config.markets.length;
148
+ setBets(restored);
149
+ if (allAnswered) {
150
+ // All questions done — go straight to game screen
145
151
  setUserFlowState("game");
152
+ } else {
153
+ // Partial — restore into questions at the first unanswered question
154
+ setRestoredSelections(restored);
155
+ setRestoredStep(answeredCount); // first unanswered index
156
+ setUserFlowState("questions");
146
157
  }
147
158
  draftRestoredRef.current = true;
148
159
  }, [poolId, config, entryData]);
@@ -177,6 +188,30 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
177
188
  return () => { if (saveDraftTimerRef.current) clearTimeout(saveDraftTimerRef.current); };
178
189
  }, [bets, poolId, config]);
179
190
 
191
+ // Debounced save triggered from PreMatchQuestions on each option pick
192
+ const handleQuestionSelectionChange = useCallback((selections: IUserBets) => {
193
+ if (!poolId || !config) return;
194
+ if (saveDraftTimerRef.current) clearTimeout(saveDraftTimerRef.current);
195
+ saveDraftTimerRef.current = setTimeout(async () => {
196
+ try {
197
+ const draft = config.markets
198
+ .map((m, mIdx) => {
199
+ const b = selections[mIdx];
200
+ if (!b || !m.backendChallengeId) return null;
201
+ const option = m.options[b.optionIdx];
202
+ if (!option) return null;
203
+ return {
204
+ challenge_id: m.backendChallengeId,
205
+ selected_option: option.label.toLowerCase().replace(/\s+/g, "_"),
206
+ coin_amount: 0,
207
+ };
208
+ })
209
+ .filter((b): b is NonNullable<typeof b> => b !== null);
210
+ if (draft.length > 0) await saveTDDraftBetsApi(poolId, draft);
211
+ } catch { /* best-effort */ }
212
+ }, 1000);
213
+ }, [poolId, config]);
214
+
180
215
  const betSummary = useMemo(
181
216
  () => config ? calcBetSummary(bets, config) : { selectedCount: 0, compoundMultiplier: 0, totalEntry: 0, baseReward: 0, compoundedReward: 0, remainingBalance: 0, riskPercent: 0, potentialBalance: 0 },
182
217
  [bets, config],
@@ -328,7 +363,7 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
328
363
  )}
329
364
 
330
365
  {activeView === "questions" && (
331
- <PreMatchQuestions config={config} onComplete={handleQuestionsComplete} />
366
+ <PreMatchQuestions config={config} onComplete={handleQuestionsComplete} onSelectionChange={handleQuestionSelectionChange} initialSelections={restoredSelections} initialStep={restoredStep} />
332
367
  )}
333
368
 
334
369
  {activeView === "game" && (
@@ -11,6 +11,9 @@ import { useGamePopupStore } from "../../core/gamePopupStore";
11
11
  interface PreMatchQuestionsProps {
12
12
  config: IChallengeConfig;
13
13
  onComplete: (selections: IUserBets) => void;
14
+ onSelectionChange?: (selections: IUserBets) => void;
15
+ initialSelections?: IUserBets;
16
+ initialStep?: number; // question index (0-based) to start at; -1 = start slide
14
17
  }
15
18
 
16
19
  // ── Smooth typewriter — constant speed, clean and simple ──
@@ -263,13 +266,13 @@ const QuestionSlide = ({
263
266
  };
264
267
 
265
268
  // ── Main component ──
266
- export const PreMatchQuestions = ({ config, onComplete }: PreMatchQuestionsProps) => {
269
+ export const PreMatchQuestions = ({ config, onComplete, onSelectionChange, initialSelections, initialStep }: PreMatchQuestionsProps) => {
267
270
  const goTo = useGamePopupStore(s => s.goTo);
268
271
 
269
272
  // -1 = start slide, 0..N-1 = question slides
270
- const [step, setStep] = useState(-1);
273
+ const [step, setStep] = useState<number>(initialStep ?? -1);
271
274
  const [direction, setDirection] = useState(1);
272
- const [selections, setSelections] = useState<IUserBets>({});
275
+ const [selections, setSelections] = useState<IUserBets>(initialSelections ?? {});
273
276
 
274
277
  const totalQuestions = config.markets.length;
275
278
  // Total steps for progress bar: start + questions
@@ -283,19 +286,21 @@ export const PreMatchQuestions = ({ config, onComplete }: PreMatchQuestionsProps
283
286
  }, []);
284
287
 
285
288
  const handleOptionSelect = useCallback((mIdx: number, optionIdx: number) => {
289
+ const isDeselect = selections[mIdx]?.optionIdx === optionIdx;
290
+
286
291
  setSelections(prev => {
287
- // Deselect if same option tapped again
288
- if (prev[mIdx]?.optionIdx === optionIdx) {
292
+ if (isDeselect) {
289
293
  const next = { ...prev };
290
294
  delete next[mIdx];
295
+ onSelectionChange?.(next);
291
296
  return next;
292
297
  }
293
- // Only record the option pick no points spent yet. User sets bet amounts on game screen.
294
- return { ...prev, [mIdx]: { optionIdx, amount: 0, parlaySlot: null } };
298
+ const next = { ...prev, [mIdx]: { optionIdx, amount: 0, parlaySlot: null } };
299
+ onSelectionChange?.(next);
300
+ return next;
295
301
  });
296
302
 
297
303
  // Auto-advance after a brief pause (only if selecting, not deselecting)
298
- const isDeselect = selections[mIdx]?.optionIdx === optionIdx;
299
304
  if (!isDeselect) {
300
305
  setTimeout(() => {
301
306
  if (mIdx < totalQuestions - 1) {
@@ -311,7 +316,7 @@ export const PreMatchQuestions = ({ config, onComplete }: PreMatchQuestionsProps
311
316
  }
312
317
  }, 500);
313
318
  }
314
- }, [config.markets, selections, totalQuestions, onComplete, goTo]);
319
+ }, [config.markets, selections, totalQuestions, onComplete, onSelectionChange, goTo]);
315
320
 
316
321
  const seekNext = useCallback(() => {
317
322
  if (step === -1) {