@devrongx/games 0.4.23 → 0.4.25

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.23",
3
+ "version": "0.4.25",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -8,6 +8,7 @@ import {
8
8
  CSK_VS_RCB_CHALLENGE,
9
9
  CSK_VS_RCB_POOL,
10
10
  IUserBets,
11
+ IBetEntry,
11
12
  calcBetSummary,
12
13
  buildPoolLeaderboard,
13
14
  ILeaderboardEntry,
@@ -121,6 +122,12 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
121
122
  const [submitError, setSubmitError] = useState<string | null>(null);
122
123
  const [submittedBets, setSubmittedBets] = useState<IUserBets | null>(null);
123
124
 
125
+ // ── Per-market editing state ────────────────────────────────────────────────
126
+ const [ignoredMarkets, setIgnoredMarkets] = useState<Set<number>>(new Set());
127
+ const [editingMarkets, setEditingMarkets] = useState<Set<number>>(new Set());
128
+ const [editingSnapshots, setEditingSnapshots] = useState<Record<number, IBetEntry | undefined>>({});
129
+ const [editSubmitting, setEditSubmitting] = useState<number | null>(null);
130
+
124
131
  // ── Real bets restore: when user has submitted bets, load them into game state ─
125
132
  const realBetsRestoredRef = useRef(false);
126
133
  useEffect(() => {
@@ -300,6 +307,95 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
300
307
  }
301
308
  }, [poolId, config, bets, hasEntry, refetchEntry, refetchLB, refetchPool]);
302
309
 
310
+ // ── Per-market editing handlers ─────────────────────────────────────────────
311
+ const handleToggleIgnore = useCallback((mIdx: number) => {
312
+ setIgnoredMarkets(prev => {
313
+ const next = new Set(prev);
314
+ if (next.has(mIdx)) next.delete(mIdx); else next.add(mIdx);
315
+ return next;
316
+ });
317
+ }, []);
318
+
319
+ const handleEditMarket = useCallback((mIdx: number) => {
320
+ setEditingMarkets(prev => new Set(prev).add(mIdx));
321
+ setEditingSnapshots(prev => ({ ...prev, [mIdx]: bets[mIdx] ? { ...bets[mIdx] } : undefined }));
322
+ }, [bets]);
323
+
324
+ const handleConfirmMarketEdit = useCallback(async (mIdx: number) => {
325
+ if (!poolId || !config) return;
326
+ const bet = bets[mIdx];
327
+ if (!bet || bet.amount <= 0) return;
328
+
329
+ setEditSubmitting(mIdx);
330
+ setSubmitError(null);
331
+ try {
332
+ const market = config.markets[mIdx];
333
+ const betInput = {
334
+ challenge_id: market.backendChallengeId!,
335
+ selected_option: market.options[bet.optionIdx].key ?? market.options[bet.optionIdx].label.toLowerCase().replace(/\s+/g, "_"),
336
+ coin_amount: bet.amount,
337
+ };
338
+
339
+ if (!hasEntry) await joinTDPool(poolId);
340
+ await placeTDBets(poolId, [betInput]);
341
+
342
+ // Update only this market in submittedBets
343
+ setSubmittedBets(prev => prev ? { ...prev, [mIdx]: { ...bet } } : { [mIdx]: { ...bet } });
344
+ realBetsRestoredRef.current = true;
345
+
346
+ // Clear editing state for this market
347
+ setEditingMarkets(prev => { const next = new Set(prev); next.delete(mIdx); return next; });
348
+ setEditingSnapshots(prev => { const next = { ...prev }; delete next[mIdx]; return next; });
349
+
350
+ await Promise.all([refetchEntry(), refetchLB(), refetchPool()]);
351
+ } catch (err: unknown) {
352
+ setSubmitError(err instanceof Error ? err.message : "Failed to save changes");
353
+ } finally {
354
+ setEditSubmitting(null);
355
+ }
356
+ }, [poolId, config, bets, hasEntry, refetchEntry, refetchLB, refetchPool]);
357
+
358
+ const handleCancelMarketEdit = useCallback((mIdx: number) => {
359
+ setBets(prev => {
360
+ const next = { ...prev };
361
+ const snapshot = editingSnapshots[mIdx];
362
+ if (snapshot) { next[mIdx] = snapshot; } else { delete next[mIdx]; }
363
+ return next;
364
+ });
365
+ setEditingMarkets(prev => { const next = new Set(prev); next.delete(mIdx); return next; });
366
+ setEditingSnapshots(prev => { const next = { ...prev }; delete next[mIdx]; return next; });
367
+ }, [editingSnapshots]);
368
+
369
+ // ── Computed: saved summary, dirty tracking ─────────────────────────────────
370
+ const savedBetSummary = useMemo(
371
+ () => submittedBets && config ? calcBetSummary(submittedBets, config) : null,
372
+ [submittedBets, config],
373
+ );
374
+
375
+ const hasUnsavedEdits = useMemo(() => {
376
+ if (!submittedBets) return false;
377
+ const allKeys = new Set([...Object.keys(bets), ...Object.keys(submittedBets)].map(Number));
378
+ for (const k of allKeys) {
379
+ const cur = bets[k];
380
+ const sub = submittedBets[k];
381
+ if (!cur && !sub) continue;
382
+ if (!cur || !sub) return true;
383
+ if (cur.optionIdx !== sub.optionIdx || cur.amount !== sub.amount) return true;
384
+ }
385
+ return false;
386
+ }, [bets, submittedBets]);
387
+
388
+ const dirtyMarkets = useMemo(() => {
389
+ const dirty = new Set<number>();
390
+ for (const mIdx of editingMarkets) {
391
+ const cur = bets[mIdx];
392
+ const snap = editingSnapshots[mIdx];
393
+ if (!cur && !snap) continue;
394
+ if (!cur || !snap) { dirty.add(mIdx); continue; }
395
+ if (cur.optionIdx !== snap.optionIdx || cur.amount !== snap.amount) dirty.add(mIdx);
396
+ }
397
+ return dirty;
398
+ }, [editingMarkets, editingSnapshots, bets]);
303
399
 
304
400
  // ── Loading ────────────────────────────────────────────────────────────────
305
401
  if (poolId && (poolLoading || (!config && !poolLoading))) {
@@ -356,6 +452,16 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
356
452
  submittedBets={poolId ? submittedBets : null}
357
453
  onViewLeaderboard={poolId ? () => setShowFullLeaderboard(true) : undefined}
358
454
  submitting={submitting}
455
+ ignoredMarkets={ignoredMarkets}
456
+ onToggleIgnore={handleToggleIgnore}
457
+ editingMarkets={editingMarkets}
458
+ dirtyMarkets={dirtyMarkets}
459
+ onEditMarket={handleEditMarket}
460
+ onConfirmMarketEdit={handleConfirmMarketEdit}
461
+ onCancelMarketEdit={handleCancelMarketEdit}
462
+ editSubmitting={editSubmitting}
463
+ savedBetSummary={savedBetSummary}
464
+ hasUnsavedEdits={hasUnsavedEdits}
359
465
  />
360
466
  )}
361
467