@devrongx/games 0.4.12 → 0.4.13
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
|
@@ -120,6 +120,7 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
|
|
|
120
120
|
const [showFullLeaderboard, setShowFullLeaderboard] = useState(false);
|
|
121
121
|
const [submitting, setSubmitting] = useState(false);
|
|
122
122
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
123
|
+
const [submittedBets, setSubmittedBets] = useState<IUserBets | null>(null);
|
|
123
124
|
|
|
124
125
|
// ── Real bets restore: when user has submitted bets, load them into game state ─
|
|
125
126
|
const realBetsRestoredRef = useRef(false);
|
|
@@ -141,6 +142,7 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
|
|
|
141
142
|
}
|
|
142
143
|
if (Object.keys(loaded).length === 0) return;
|
|
143
144
|
setBets(loaded);
|
|
145
|
+
setSubmittedBets(loaded);
|
|
144
146
|
setExpandedPicker(pickers);
|
|
145
147
|
setUserFlowState("game");
|
|
146
148
|
realBetsRestoredRef.current = true;
|
|
@@ -290,6 +292,9 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
|
|
|
290
292
|
|
|
291
293
|
await placeTDBets(poolId, betsToSubmit);
|
|
292
294
|
|
|
295
|
+
// Snapshot submitted state for change detection
|
|
296
|
+
setSubmittedBets({ ...bets });
|
|
297
|
+
|
|
293
298
|
// Refresh data
|
|
294
299
|
await Promise.all([refetchEntry(), refetchLB(), refetchPool()]);
|
|
295
300
|
} catch (err: unknown) {
|
|
@@ -353,6 +358,7 @@ export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp
|
|
|
353
358
|
betSummary={betSummary}
|
|
354
359
|
leaderboardRows={leaderboardRows}
|
|
355
360
|
onSubmit={poolId ? handleSubmitBets : undefined}
|
|
361
|
+
submittedBets={poolId ? submittedBets : null}
|
|
356
362
|
onViewLeaderboard={poolId ? () => setShowFullLeaderboard(true) : undefined}
|
|
357
363
|
submitting={submitting}
|
|
358
364
|
/>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
5
5
|
import Image from "next/image";
|
|
6
6
|
import { motion, AnimatePresence, useSpring, useTransform, useMotionValue } from "framer-motion";
|
|
7
|
-
import { ChevronDown, Info, X, Play,
|
|
7
|
+
import { ChevronDown, Info, X, Play, Check, Loader2 } from "lucide-react";
|
|
8
8
|
import { IBetSummary, ILeaderboardEntry, IChallengeConfig, IUserBets, deriveParlayGroups, deriveMarketToParlay, calcDisplayReward, optionReward, calcParlayMultiplier } from "./config";
|
|
9
9
|
import { OUTFIT, MARKET_ICONS, PointsIcon, SelectedCheck, AiInsightButton } from "./constants";
|
|
10
10
|
import { useGamePopupStore } from "../../core/gamePopupStore";
|
|
@@ -37,8 +37,10 @@ interface PreMatchGameProps {
|
|
|
37
37
|
leaderboardRows: ILeaderboardEntry[];
|
|
38
38
|
/** Render only the markets section (no header/balance/leaderboard) — used for card preview */
|
|
39
39
|
marketsOnly?: boolean;
|
|
40
|
-
/** Called when user taps
|
|
40
|
+
/** Called when user taps the action button to submit/re-submit bets. */
|
|
41
41
|
onSubmit?: () => Promise<void> | void;
|
|
42
|
+
/** Snapshot of bets as last saved to the server — drives saved/changed button states. */
|
|
43
|
+
submittedBets?: IUserBets | null;
|
|
42
44
|
/** Called when user taps "See Full Leaderboard" */
|
|
43
45
|
onViewLeaderboard?: () => void;
|
|
44
46
|
/** v1: parlays are cosmetic, shows "Coming in v2". Default = false (hides section) */
|
|
@@ -48,9 +50,23 @@ interface PreMatchGameProps {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
export const PreMatchGame = ({ config, bets, onBetsChange, expandedPicker, onOptionClick, onAmountSelect, betSummary, leaderboardRows, marketsOnly, onSubmit, onViewLeaderboard, parlayEnabled = false, submitting = false }: PreMatchGameProps) => {
|
|
53
|
+
export const PreMatchGame = ({ config, bets, onBetsChange, expandedPicker, onOptionClick, onAmountSelect, betSummary, leaderboardRows, marketsOnly, onSubmit, submittedBets, onViewLeaderboard, parlayEnabled = false, submitting = false }: PreMatchGameProps) => {
|
|
52
54
|
const { selectedCount, compoundMultiplier, totalEntry, compoundedReward, remainingBalance, riskPercent } = betSummary;
|
|
53
55
|
|
|
56
|
+
// Button state: fresh (no prior submit), saved (submitted, no changes), changed (submitted, user modified)
|
|
57
|
+
const buttonState = useMemo<"fresh" | "saved" | "changed">(() => {
|
|
58
|
+
if (!submittedBets) return "fresh";
|
|
59
|
+
const subKeys = Object.keys(submittedBets);
|
|
60
|
+
const curKeys = Object.keys(bets);
|
|
61
|
+
if (subKeys.length !== curKeys.length) return "changed";
|
|
62
|
+
for (const k of curKeys) {
|
|
63
|
+
const cur = bets[Number(k)];
|
|
64
|
+
const sub = submittedBets[Number(k)];
|
|
65
|
+
if (!sub || cur.optionIdx !== sub.optionIdx || cur.amount !== sub.amount) return "changed";
|
|
66
|
+
}
|
|
67
|
+
return "saved";
|
|
68
|
+
}, [bets, submittedBets]);
|
|
69
|
+
|
|
54
70
|
const goTo = useGamePopupStore(s => s.goTo);
|
|
55
71
|
|
|
56
72
|
// Onboarding — visible whenever no points have been bet yet
|
|
@@ -583,49 +599,48 @@ export const PreMatchGame = ({ config, bets, onBetsChange, expandedPicker, onOpt
|
|
|
583
599
|
<AnimatePresence>
|
|
584
600
|
{totalEntry > 0 && (() => {
|
|
585
601
|
const spentPercent = totalEntry / config.startingBalance;
|
|
586
|
-
const fillPercent = 5 + spentPercent * 95;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
className="relative rounded-xl overflow-hidden flex-shrink-0 flex items-center justify-center gap-1.5 px-3"
|
|
594
|
-
style={{ background: submitting ? "rgba(34,227,232,0.3)" : "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)", minWidth: 90, height: "100%" }}
|
|
595
|
-
initial={{ opacity: 0, width: 0 }}
|
|
596
|
-
animate={{ opacity: 1, width: "auto" }}
|
|
597
|
-
exit={{ opacity: 0, width: 0 }}
|
|
598
|
-
transition={{ width: { duration: 0.4, ease: "easeOut", delay: 0.8 }, opacity: { duration: 0.3, delay: 1 } }}
|
|
599
|
-
>
|
|
600
|
-
{submitting ? (
|
|
601
|
-
<span className="text-[10px] font-bold text-white/70" style={OUTFIT}>Submitting…</span>
|
|
602
|
-
) : (
|
|
603
|
-
<>
|
|
604
|
-
<Send size={14} className="text-white" />
|
|
605
|
-
<span className="text-[11px] font-bold text-white" style={OUTFIT}>Submit Bets</span>
|
|
606
|
-
</>
|
|
607
|
-
)}
|
|
608
|
-
</motion.button>
|
|
609
|
-
);
|
|
610
|
-
}
|
|
602
|
+
const fillPercent = buttonState === "saved" ? 100 : 5 + spentPercent * 95;
|
|
603
|
+
const fillColor = buttonState === "saved"
|
|
604
|
+
? "linear-gradient(135deg, #22c55e, #16a34a)"
|
|
605
|
+
: "linear-gradient(135deg, #22E3E8, #9945FF, #f83cc5)";
|
|
606
|
+
const bgColor = buttonState === "saved"
|
|
607
|
+
? "linear-gradient(135deg, rgba(34,197,94,0.2), rgba(22,163,74,0.2))"
|
|
608
|
+
: "linear-gradient(135deg, rgba(34,227,232,0.2), rgba(153,69,255,0.2), rgba(248,60,197,0.2))";
|
|
611
609
|
|
|
612
610
|
return (
|
|
613
|
-
<motion.
|
|
611
|
+
<motion.button
|
|
612
|
+
onClick={onSubmit && buttonState !== "saved" ? onSubmit : undefined}
|
|
613
|
+
disabled={submitting || buttonState === "saved"}
|
|
614
614
|
className="relative w-[65px] rounded-xl overflow-hidden flex-shrink-0 flex items-center justify-center"
|
|
615
|
-
style={{ background:
|
|
615
|
+
style={{ background: bgColor, cursor: buttonState === "saved" ? "default" : "pointer" }}
|
|
616
616
|
initial={{ opacity: 0, width: 0 }}
|
|
617
617
|
animate={{ opacity: 1, width: 65 }}
|
|
618
618
|
exit={{ opacity: 0, width: 0 }}
|
|
619
619
|
transition={{ width: { duration: 0.4, ease: "easeOut", delay: 0.8 }, opacity: { duration: 0.3, delay: 1 } }}
|
|
620
620
|
>
|
|
621
|
+
{/* Fill bar */}
|
|
621
622
|
<motion.div
|
|
622
623
|
className="absolute inset-y-0 left-0"
|
|
623
624
|
animate={{ width: `${fillPercent}%` }}
|
|
624
625
|
transition={{ type: "spring", stiffness: 120, damping: 20 }}
|
|
625
|
-
style={{ background:
|
|
626
|
+
style={{ background: fillColor }}
|
|
626
627
|
/>
|
|
627
|
-
|
|
628
|
-
|
|
628
|
+
{/* Pulsing ring when there are unsaved changes */}
|
|
629
|
+
{buttonState === "changed" && (
|
|
630
|
+
<motion.div
|
|
631
|
+
className="absolute inset-0 rounded-xl"
|
|
632
|
+
animate={{ boxShadow: ["0 0 0px rgba(34,227,232,0)", "0 0 10px rgba(34,227,232,0.7)", "0 0 0px rgba(34,227,232,0)"] }}
|
|
633
|
+
transition={{ duration: 1.4, repeat: Infinity, ease: "easeInOut" }}
|
|
634
|
+
/>
|
|
635
|
+
)}
|
|
636
|
+
{/* Icon */}
|
|
637
|
+
{submitting
|
|
638
|
+
? <Loader2 size={22} strokeWidth={2.5} className="relative z-10 animate-spin text-white/70" />
|
|
639
|
+
: buttonState === "saved"
|
|
640
|
+
? <Check size={24} strokeWidth={2.5} className="relative z-10 text-white" />
|
|
641
|
+
: <Play size={26} fill="white" strokeWidth={0} className="relative z-10" />
|
|
642
|
+
}
|
|
643
|
+
</motion.button>
|
|
629
644
|
);
|
|
630
645
|
})()}
|
|
631
646
|
</AnimatePresence>
|