@devrongx/games 0.4.3 → 0.4.5
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
|
@@ -21,8 +21,8 @@ import { PreMatchLive } from "./PreMatchLive";
|
|
|
21
21
|
import { PreMatchResults } from "./PreMatchResults";
|
|
22
22
|
import { FullLeaderboard } from "./FullLeaderboard";
|
|
23
23
|
import { useTDPool, useTDPoolEntry, useTDLeaderboard } from "../../pools/hooks";
|
|
24
|
-
import { useTDMatches } from "../../matches/useTDMatches";
|
|
25
24
|
import { buildPMBConfig } from "../../pools/mapper";
|
|
25
|
+
import type { ITDMatch } from "../../matches/types";
|
|
26
26
|
import { joinTDPool, placeTDBets } from "../../pools/actions";
|
|
27
27
|
import type { ITDLeaderboardEntry } from "../../pools/types";
|
|
28
28
|
import { TDPoolStatus } from "../../pools/types";
|
|
@@ -89,27 +89,28 @@ interface PreMatchBetsPopupProps {
|
|
|
89
89
|
/** When provided, fetches real data. When absent, uses hardcoded CSK_VS_RCB demo. */
|
|
90
90
|
poolId?: number;
|
|
91
91
|
matchId?: number;
|
|
92
|
+
/** Pass the ITDMatch directly (from MatchCalendar's onPoolPress) to skip a redundant fetch. */
|
|
93
|
+
match?: ITDMatch;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
95
97
|
|
|
96
|
-
export const PreMatchBetsPopup = ({ poolId, matchId }: PreMatchBetsPopupProps) => {
|
|
98
|
+
export const PreMatchBetsPopup = ({ poolId, matchId: _matchId, match: matchProp }: PreMatchBetsPopupProps) => {
|
|
97
99
|
const { activeView: storeView } = useGamePopupStore();
|
|
98
100
|
|
|
99
101
|
// ── Real API data (only when poolId provided) ────────────────────────────
|
|
100
102
|
const { pool, loading: poolLoading, refetch: refetchPool } = useTDPool(poolId ?? 0);
|
|
101
103
|
const { data: entryData, refetch: refetchEntry } = useTDPoolEntry(poolId ?? 0);
|
|
102
104
|
const { rankings, refetch: refetchLB } = useTDLeaderboard(poolId ?? 0, { pollMs: poolId ? 30_000 : 0 });
|
|
103
|
-
const { matches } = useTDMatches(matchId !== undefined ? { tournamentId: undefined } : undefined);
|
|
104
|
-
const match = useMemo(() => matches.find((m) => m.id === matchId) ?? null, [matches, matchId]);
|
|
105
105
|
|
|
106
106
|
// ── Config: real or fallback ─────────────────────────────────────────────
|
|
107
|
+
// matchProp is passed directly from MatchCalendar's onPoolPress — no extra fetch needed
|
|
107
108
|
const config = useMemo(() => {
|
|
108
|
-
if (poolId && pool &&
|
|
109
|
-
try { return buildPMBConfig(pool,
|
|
109
|
+
if (poolId && pool && matchProp) {
|
|
110
|
+
try { return buildPMBConfig(pool, matchProp); } catch { /* fall through */ }
|
|
110
111
|
}
|
|
111
112
|
return poolId ? null : CSK_VS_RCB_CHALLENGE;
|
|
112
|
-
}, [poolId, pool,
|
|
113
|
+
}, [poolId, pool, matchProp]);
|
|
113
114
|
|
|
114
115
|
const fallbackPool = poolId ? null : CSK_VS_RCB_POOL;
|
|
115
116
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
import { useRef, useMemo, useEffect } from "react";
|
|
5
|
-
import { motion } from "framer-motion";
|
|
5
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
6
6
|
import { Calendar, MapPin, Loader2, Trophy, Zap, Target, Users } from "lucide-react";
|
|
7
7
|
import { useTDMatches } from "./useTDMatches";
|
|
8
8
|
import { useTDPools } from "../pools/hooks";
|
|
@@ -114,26 +114,38 @@ const GAME_TYPE_STYLE: Record<number, { icon: typeof Target; color: string; bg:
|
|
|
114
114
|
|
|
115
115
|
function PoolPill({ pool, onPress }: { pool: ITDPool; onPress: (pool: ITDPool) => void }) {
|
|
116
116
|
const style = GAME_TYPE_STYLE[pool.game_type] ?? { icon: Target, color: "#22E3E8", bg: "rgba(34,227,232,0.06)" };
|
|
117
|
-
const Icon = style.icon;
|
|
118
117
|
const isClosed = pool.status === TDPoolStatus.CLOSED || pool.status === TDPoolStatus.RESOLVING || pool.status === TDPoolStatus.COMPLETE;
|
|
118
|
+
const isOpen = pool.status === TDPoolStatus.OPEN;
|
|
119
119
|
|
|
120
120
|
return (
|
|
121
|
-
<button
|
|
121
|
+
<motion.button
|
|
122
122
|
onClick={() => onPress(pool)}
|
|
123
|
-
|
|
123
|
+
whileTap={{ scale: 0.93 }}
|
|
124
|
+
whileHover={{ scale: 1.04 }}
|
|
125
|
+
transition={{ type: "spring", stiffness: 500, damping: 25 }}
|
|
126
|
+
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg flex-shrink-0"
|
|
124
127
|
style={{
|
|
125
|
-
background: style.bg,
|
|
126
|
-
border: `1px solid ${style.color}
|
|
127
|
-
opacity: isClosed ? 0.
|
|
128
|
+
background: isOpen ? style.bg.replace("0.06", "0.10") : style.bg,
|
|
129
|
+
border: `1px solid ${style.color}${isOpen ? "50" : "25"}`,
|
|
130
|
+
opacity: isClosed ? 0.45 : 1,
|
|
131
|
+
boxShadow: isOpen ? `0 0 8px ${style.color}18` : undefined,
|
|
128
132
|
}}
|
|
129
133
|
>
|
|
130
|
-
<span className="text-[
|
|
134
|
+
<span className="text-[10px] font-bold" style={{ ...OUTFIT, color: "#fff" }}>
|
|
131
135
|
{pool.display_price}
|
|
132
136
|
</span>
|
|
133
137
|
{pool.entry_count > 0 && (
|
|
134
|
-
<span className="text-[
|
|
138
|
+
<span className="text-[8px] font-medium" style={{ ...OUTFIT, color: "rgba(255,255,255,0.65)" }}>
|
|
139
|
+
{pool.entry_count > 999 ? `${(pool.entry_count / 1000).toFixed(1)}k` : pool.entry_count}
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
{isOpen && (
|
|
143
|
+
<span className="relative flex h-1.5 w-1.5 shrink-0">
|
|
144
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full opacity-60" style={{ backgroundColor: style.color }} />
|
|
145
|
+
<span className="relative inline-flex rounded-full h-1.5 w-1.5" style={{ backgroundColor: style.color }} />
|
|
146
|
+
</span>
|
|
135
147
|
)}
|
|
136
|
-
</button>
|
|
148
|
+
</motion.button>
|
|
137
149
|
);
|
|
138
150
|
}
|
|
139
151
|
|
|
@@ -164,21 +176,28 @@ function MatchGameSection({
|
|
|
164
176
|
if (gameTypes.length === 0) return null;
|
|
165
177
|
|
|
166
178
|
return (
|
|
167
|
-
<div className="flex flex-col gap-1 mt-1 pt-
|
|
179
|
+
<div className="flex flex-col gap-1.5 mt-1.5 pt-2" style={{ borderTop: "1px solid rgba(255,255,255,0.08)" }}>
|
|
168
180
|
{gameTypes.map((gt) => {
|
|
169
181
|
const style = GAME_TYPE_STYLE[gt];
|
|
170
182
|
const Icon = style.icon;
|
|
171
183
|
return (
|
|
172
|
-
<div key={gt} className="flex flex-col gap-
|
|
173
|
-
<div className="flex items-center gap-1 px-
|
|
174
|
-
<Icon size={
|
|
175
|
-
<span className="text-[
|
|
184
|
+
<div key={gt} className="flex flex-col gap-1">
|
|
185
|
+
<div className="flex items-center gap-1 px-0.5">
|
|
186
|
+
<Icon size={9} style={{ color: style.color }} />
|
|
187
|
+
<span className="text-[8px] barlowcondensedBold tracking-wide" style={{ color: "rgba(255,255,255,0.85)" }}>
|
|
176
188
|
{style.label}
|
|
177
189
|
</span>
|
|
178
190
|
</div>
|
|
179
|
-
<div className="flex gap-1 flex-wrap">
|
|
180
|
-
{grouped[gt].map((pool) => (
|
|
181
|
-
<
|
|
191
|
+
<div className="flex gap-1.5 flex-wrap">
|
|
192
|
+
{grouped[gt].map((pool, idx) => (
|
|
193
|
+
<motion.div
|
|
194
|
+
key={pool.id}
|
|
195
|
+
initial={{ opacity: 0, y: 6 }}
|
|
196
|
+
animate={{ opacity: 1, y: 0 }}
|
|
197
|
+
transition={{ delay: idx * 0.04, duration: 0.25, ease: "easeOut" }}
|
|
198
|
+
>
|
|
199
|
+
<PoolPill pool={pool} onPress={onPoolPress ?? (() => {})} />
|
|
200
|
+
</motion.div>
|
|
182
201
|
))}
|
|
183
202
|
</div>
|
|
184
203
|
</div>
|
|
@@ -190,7 +209,7 @@ function MatchGameSection({
|
|
|
190
209
|
|
|
191
210
|
/* ── Match card with border shine animation ── */
|
|
192
211
|
|
|
193
|
-
function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onPoolPress?: (pool: ITDPool) => void; partnerSource?: string }) {
|
|
212
|
+
function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onPoolPress?: (pool: ITDPool, match: ITDMatch) => void; partnerSource?: string }) {
|
|
194
213
|
const isLive = match.status === MATCH_STATUS.LIVE;
|
|
195
214
|
const isCompleted = match.status === MATCH_STATUS.COMPLETED;
|
|
196
215
|
const time = match.scheduled_start_at ? formatTime(toLocalDate(match.scheduled_start_at)) : "";
|
|
@@ -225,19 +244,23 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
|
|
|
225
244
|
/>
|
|
226
245
|
)}
|
|
227
246
|
|
|
228
|
-
<div
|
|
229
|
-
className="relative flex flex-col gap-
|
|
247
|
+
<motion.div
|
|
248
|
+
className="relative flex flex-col gap-2 rounded-xl px-3.5 py-3"
|
|
230
249
|
style={{
|
|
231
|
-
background: isLive
|
|
250
|
+
background: isLive
|
|
251
|
+
? "linear-gradient(160deg, rgba(14,10,24,0.98) 0%, rgba(10,8,20,0.98) 100%)"
|
|
252
|
+
: "rgba(10,10,18,0.98)",
|
|
232
253
|
}}
|
|
254
|
+
whileHover={{ backgroundColor: "rgba(18,18,28,0.98)" }}
|
|
255
|
+
transition={{ duration: 0.2 }}
|
|
233
256
|
>
|
|
234
257
|
{/* Time + live indicator */}
|
|
235
|
-
<div className="flex items-center justify-between">
|
|
258
|
+
<div className="flex items-center justify-between gap-1">
|
|
236
259
|
<span
|
|
237
|
-
className="text-[
|
|
260
|
+
className="text-[11px] font-semibold"
|
|
238
261
|
style={{
|
|
239
262
|
...OUTFIT,
|
|
240
|
-
color: isLive ? "#f83cc5" : isCompleted ? "rgba(255,255,255,0.
|
|
263
|
+
color: isLive ? "#f83cc5" : isCompleted ? "rgba(255,255,255,0.45)" : "#fff",
|
|
241
264
|
}}
|
|
242
265
|
>
|
|
243
266
|
{time}
|
|
@@ -254,26 +277,26 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
|
|
|
254
277
|
</div>
|
|
255
278
|
)}
|
|
256
279
|
{isCompleted && match.winner && (
|
|
257
|
-
<span className="text-[8px]" style={{ ...OUTFIT, color: "rgba(255,255,255,0.
|
|
280
|
+
<span className="text-[8px]" style={{ ...OUTFIT, color: "rgba(255,255,255,0.5)" }}>
|
|
258
281
|
{match.winner.short_name || match.winner.name} won
|
|
259
282
|
</span>
|
|
260
283
|
)}
|
|
261
284
|
</div>
|
|
262
285
|
|
|
263
286
|
{/* Teams */}
|
|
264
|
-
<div className="flex items-center gap-
|
|
287
|
+
<div className="flex items-center gap-2 justify-center">
|
|
265
288
|
<span
|
|
266
|
-
className="barlowcondensedBold text-[
|
|
267
|
-
style={{ color: isCompleted ? "rgba(255,255,255,0.
|
|
289
|
+
className="barlowcondensedBold text-[24px] leading-none tracking-wide"
|
|
290
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.4)" : "#fff" }}
|
|
268
291
|
>
|
|
269
292
|
{getTeamShortName(match, "a")}
|
|
270
293
|
</span>
|
|
271
|
-
<span className="text-[
|
|
272
|
-
|
|
294
|
+
<span className="text-[10px] font-semibold" style={{ ...OUTFIT, color: "rgba(255,255,255,0.5)" }}>
|
|
295
|
+
vs
|
|
273
296
|
</span>
|
|
274
297
|
<span
|
|
275
|
-
className="barlowcondensedBold text-[
|
|
276
|
-
style={{ color: isCompleted ? "rgba(255,255,255,0.
|
|
298
|
+
className="barlowcondensedBold text-[24px] leading-none tracking-wide"
|
|
299
|
+
style={{ color: isCompleted ? "rgba(255,255,255,0.4)" : "#fff" }}
|
|
277
300
|
>
|
|
278
301
|
{getTeamShortName(match, "b")}
|
|
279
302
|
</span>
|
|
@@ -282,10 +305,10 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
|
|
|
282
305
|
{/* Venue */}
|
|
283
306
|
{venue && (
|
|
284
307
|
<div className="flex items-center gap-1 justify-center">
|
|
285
|
-
<MapPin size={
|
|
308
|
+
<MapPin size={9} style={{ color: "rgba(255,255,255,0.55)" }} />
|
|
286
309
|
<span
|
|
287
|
-
className="text-[
|
|
288
|
-
style={{ ...OUTFIT, color: "rgba(255,255,255,0.
|
|
310
|
+
className="text-[9px] truncate max-w-[130px]"
|
|
311
|
+
style={{ ...OUTFIT, color: "rgba(255,255,255,0.7)" }}
|
|
289
312
|
>
|
|
290
313
|
{venue}
|
|
291
314
|
</span>
|
|
@@ -298,9 +321,9 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
|
|
|
298
321
|
{Array.from({ length: Math.min(Math.ceil(match.rating_popularity / 2), 5) }).map((_, i) => (
|
|
299
322
|
<Zap
|
|
300
323
|
key={i}
|
|
301
|
-
size={
|
|
324
|
+
size={8}
|
|
302
325
|
fill={isLive ? "#f83cc5" : "#22E3E8"}
|
|
303
|
-
style={{ color: isLive ? "#f83cc5" : "#22E3E8", opacity: 0.
|
|
326
|
+
style={{ color: isLive ? "#f83cc5" : "#22E3E8", opacity: 0.45 + i * 0.13 }}
|
|
304
327
|
/>
|
|
305
328
|
))}
|
|
306
329
|
</div>
|
|
@@ -310,17 +333,17 @@ function MatchCard({ match, onPoolPress, partnerSource }: { match: ITDMatch; onP
|
|
|
310
333
|
<MatchGameSection
|
|
311
334
|
matchId={match.id}
|
|
312
335
|
isCompleted={isCompleted}
|
|
313
|
-
onPoolPress={onPoolPress}
|
|
336
|
+
onPoolPress={onPoolPress ? (pool) => onPoolPress(pool, match) : undefined}
|
|
314
337
|
partnerSource={partnerSource}
|
|
315
338
|
/>
|
|
316
|
-
</div>
|
|
339
|
+
</motion.div>
|
|
317
340
|
</div>
|
|
318
341
|
);
|
|
319
342
|
}
|
|
320
343
|
|
|
321
344
|
/* ── Day column ── */
|
|
322
345
|
|
|
323
|
-
function DayColumn({ day, index, onPoolPress, partnerSource }: { day: IMatchDay; index: number; onPoolPress?: (pool: ITDPool) => void; partnerSource?: string }) {
|
|
346
|
+
function DayColumn({ day, index, onPoolPress, partnerSource }: { day: IMatchDay; index: number; onPoolPress?: (pool: ITDPool, match: ITDMatch) => void; partnerSource?: string }) {
|
|
324
347
|
const hasToday = day.label === "TODAY";
|
|
325
348
|
const hasLive = day.matches.some((m) => m.status === MATCH_STATUS.LIVE);
|
|
326
349
|
const allCompleted = day.matches.every((m) => m.status === MATCH_STATUS.COMPLETED);
|
|
@@ -329,32 +352,32 @@ function DayColumn({ day, index, onPoolPress, partnerSource }: { day: IMatchDay;
|
|
|
329
352
|
<motion.div
|
|
330
353
|
initial={{ opacity: 0, y: 10 }}
|
|
331
354
|
animate={{ opacity: 1, y: 0 }}
|
|
332
|
-
transition={{ duration: 0.
|
|
333
|
-
className="shrink-0 flex flex-col gap-
|
|
334
|
-
style={{ scrollSnapAlign: "start", width: "
|
|
355
|
+
transition={{ duration: 0.35, delay: index * 0.04, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
356
|
+
className="shrink-0 flex flex-col gap-2"
|
|
357
|
+
style={{ scrollSnapAlign: "start", width: "180px" }}
|
|
335
358
|
>
|
|
336
359
|
{/* Day header */}
|
|
337
|
-
<div className="flex flex-col items-center gap-0.5 pb-1">
|
|
360
|
+
<div className="flex flex-col items-center gap-0.5 pb-1.5">
|
|
338
361
|
<span
|
|
339
|
-
className="text-[
|
|
362
|
+
className="text-[11px] barlowcondensedBold tracking-widest"
|
|
340
363
|
style={{
|
|
341
|
-
color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.
|
|
364
|
+
color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.35)" : "rgba(255,255,255,0.9)",
|
|
342
365
|
}}
|
|
343
366
|
>
|
|
344
367
|
{day.label}
|
|
345
368
|
</span>
|
|
346
369
|
<span
|
|
347
|
-
className="text-[
|
|
370
|
+
className="text-[13px] font-semibold"
|
|
348
371
|
style={{
|
|
349
372
|
...OUTFIT,
|
|
350
|
-
color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.
|
|
373
|
+
color: hasLive ? "#f83cc5" : hasToday ? "#22E3E8" : allCompleted ? "rgba(255,255,255,0.35)" : "#fff",
|
|
351
374
|
}}
|
|
352
375
|
>
|
|
353
376
|
{day.dayMonth}
|
|
354
377
|
</span>
|
|
355
378
|
{(hasToday || hasLive) && (
|
|
356
379
|
<div
|
|
357
|
-
className="w-
|
|
380
|
+
className="w-6 h-[2px] rounded-full mt-0.5"
|
|
358
381
|
style={{ background: hasLive ? "#f83cc5" : "#22E3E8" }}
|
|
359
382
|
/>
|
|
360
383
|
)}
|
|
@@ -372,8 +395,8 @@ function DayColumn({ day, index, onPoolPress, partnerSource }: { day: IMatchDay;
|
|
|
372
395
|
|
|
373
396
|
interface MatchCalendarProps {
|
|
374
397
|
tournamentId?: number;
|
|
375
|
-
/** Called when user taps a pool pill.
|
|
376
|
-
onPoolPress?: (pool: ITDPool) => void;
|
|
398
|
+
/** Called when user taps a pool pill. Receives both the pool and its parent match. */
|
|
399
|
+
onPoolPress?: (pool: ITDPool, match: ITDMatch) => void;
|
|
377
400
|
/** Filter pools to a specific partner source (e.g. "iamgame") */
|
|
378
401
|
partnerSource?: string;
|
|
379
402
|
}
|
|
@@ -467,18 +490,18 @@ export function MatchCalendar({ tournamentId, onPoolPress, partnerSource }: Matc
|
|
|
467
490
|
)}
|
|
468
491
|
|
|
469
492
|
{/* Section header */}
|
|
470
|
-
<div className="flex items-center gap-2 px-5 mb-
|
|
471
|
-
<Calendar size={
|
|
472
|
-
<span className="barlowCondensedSemiBold text-[
|
|
493
|
+
<div className="flex items-center gap-2 px-5 mb-3">
|
|
494
|
+
<Calendar size={13} style={{ color: "#22E3E8" }} />
|
|
495
|
+
<span className="barlowCondensedSemiBold text-[14px] tracking-wider text-white">
|
|
473
496
|
SCHEDULE
|
|
474
497
|
</span>
|
|
475
|
-
<div className="flex-1 h-px" style={{ background: "rgba(255,255,255,0.
|
|
498
|
+
<div className="flex-1 h-px" style={{ background: "rgba(255,255,255,0.08)" }} />
|
|
476
499
|
</div>
|
|
477
500
|
|
|
478
501
|
{/* Scrollable day columns */}
|
|
479
502
|
<div
|
|
480
503
|
ref={scrollRef}
|
|
481
|
-
className="flex gap-
|
|
504
|
+
className="flex gap-3 overflow-x-auto px-5 pb-4 scrollbar-hide"
|
|
482
505
|
style={{ scrollSnapType: "x mandatory", WebkitOverflowScrolling: "touch" }}
|
|
483
506
|
>
|
|
484
507
|
{days.map((day, i) => (
|