@devrongx/games 0.3.5 → 0.4.1
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 +1 -1
- package/src/games/prematch-bets/FullLeaderboard.tsx +149 -0
- package/src/games/prematch-bets/PreMatchBetsPopup.tsx +307 -76
- package/src/games/prematch-bets/PreMatchGame.tsx +55 -7
- package/src/games/prematch-bets/PreMatchLive.tsx +193 -0
- package/src/games/prematch-bets/PreMatchResults.tsx +212 -0
- package/src/games/prematch-bets/PreMatchSubmitted.tsx +183 -0
- package/src/games/prematch-bets/config.ts +1 -0
- package/src/index.ts +16 -0
- package/src/matches/MatchCalendar.tsx +104 -50
- package/src/pools/actions.ts +19 -0
- package/src/pools/fetcher.ts +90 -0
- package/src/pools/hooks.ts +173 -0
- package/src/pools/index.ts +44 -0
- package/src/pools/mapper.ts +85 -0
- package/src/pools/types.ts +229 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// @devrongx/games — pools/hooks.ts
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
5
|
+
import {
|
|
6
|
+
fetchTDPools,
|
|
7
|
+
fetchTDPoolDetail,
|
|
8
|
+
fetchTDLeaderboard,
|
|
9
|
+
fetchTDMyEntry,
|
|
10
|
+
} from "./fetcher";
|
|
11
|
+
import type {
|
|
12
|
+
ITDPool,
|
|
13
|
+
ITDPoolDetail,
|
|
14
|
+
ITDMyEntryResponse,
|
|
15
|
+
ITDLeaderboardResponse,
|
|
16
|
+
ITDLeaderboardEntry,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
// ─── useTDPools ────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface UseTDPoolsResult {
|
|
22
|
+
pools: ITDPool[];
|
|
23
|
+
loading: boolean;
|
|
24
|
+
error: string | null;
|
|
25
|
+
refetch: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useTDPools(matchId: number, partnerSource?: string): UseTDPoolsResult {
|
|
29
|
+
const [pools, setPools] = useState<ITDPool[]>([]);
|
|
30
|
+
const [loading, setLoading] = useState(true);
|
|
31
|
+
const [error, setError] = useState<string | null>(null);
|
|
32
|
+
|
|
33
|
+
const load = useCallback(async () => {
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setError(null);
|
|
36
|
+
try {
|
|
37
|
+
const data = await fetchTDPools(matchId, partnerSource);
|
|
38
|
+
setPools(data);
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
setError(err instanceof Error ? err.message : "Failed to fetch pools");
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}, [matchId, partnerSource]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => { load(); }, [load]);
|
|
47
|
+
|
|
48
|
+
return { pools, loading, error, refetch: load };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── useTDPool ─────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export interface UseTDPoolResult {
|
|
54
|
+
pool: ITDPoolDetail | null;
|
|
55
|
+
loading: boolean;
|
|
56
|
+
error: string | null;
|
|
57
|
+
refetch: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useTDPool(poolId: number): UseTDPoolResult {
|
|
61
|
+
const [pool, setPool] = useState<ITDPoolDetail | null>(null);
|
|
62
|
+
const [loading, setLoading] = useState(true);
|
|
63
|
+
const [error, setError] = useState<string | null>(null);
|
|
64
|
+
|
|
65
|
+
const load = useCallback(async () => {
|
|
66
|
+
setLoading(true);
|
|
67
|
+
setError(null);
|
|
68
|
+
try {
|
|
69
|
+
const data = await fetchTDPoolDetail(poolId);
|
|
70
|
+
setPool(data);
|
|
71
|
+
} catch (err: unknown) {
|
|
72
|
+
setError(err instanceof Error ? err.message : "Failed to fetch pool");
|
|
73
|
+
} finally {
|
|
74
|
+
setLoading(false);
|
|
75
|
+
}
|
|
76
|
+
}, [poolId]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => { load(); }, [load]);
|
|
79
|
+
|
|
80
|
+
return { pool, loading, error, refetch: load };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── useTDPoolEntry ─────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export interface UseTDPoolEntryResult {
|
|
86
|
+
data: ITDMyEntryResponse | null;
|
|
87
|
+
loading: boolean;
|
|
88
|
+
error: string | null;
|
|
89
|
+
refetch: () => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function useTDPoolEntry(poolId: number): UseTDPoolEntryResult {
|
|
93
|
+
const [data, setData] = useState<ITDMyEntryResponse | null>(null);
|
|
94
|
+
const [loading, setLoading] = useState(true);
|
|
95
|
+
const [error, setError] = useState<string | null>(null);
|
|
96
|
+
|
|
97
|
+
const load = useCallback(async () => {
|
|
98
|
+
setLoading(true);
|
|
99
|
+
setError(null);
|
|
100
|
+
try {
|
|
101
|
+
const res = await fetchTDMyEntry(poolId);
|
|
102
|
+
setData(res);
|
|
103
|
+
} catch (err: unknown) {
|
|
104
|
+
setError(err instanceof Error ? err.message : "Failed to fetch entry");
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
}
|
|
108
|
+
}, [poolId]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => { load(); }, [load]);
|
|
111
|
+
|
|
112
|
+
return { data, loading, error, refetch: load };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── useTDLeaderboard ──────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
export interface UseTDLeaderboardOptions {
|
|
118
|
+
sort?: string;
|
|
119
|
+
order?: string;
|
|
120
|
+
limit?: number;
|
|
121
|
+
offset?: number;
|
|
122
|
+
pollMs?: number; // Polling interval in ms (0 = disabled)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface UseTDLeaderboardResult {
|
|
126
|
+
rankings: ITDLeaderboardEntry[];
|
|
127
|
+
total: number;
|
|
128
|
+
loading: boolean;
|
|
129
|
+
error: string | null;
|
|
130
|
+
refetch: () => void;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function useTDLeaderboard(
|
|
134
|
+
poolId: number,
|
|
135
|
+
options?: UseTDLeaderboardOptions,
|
|
136
|
+
): UseTDLeaderboardResult {
|
|
137
|
+
const [result, setResult] = useState<ITDLeaderboardResponse | null>(null);
|
|
138
|
+
const [loading, setLoading] = useState(true);
|
|
139
|
+
const [error, setError] = useState<string | null>(null);
|
|
140
|
+
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
141
|
+
|
|
142
|
+
const { sort, order, limit, offset, pollMs } = options ?? {};
|
|
143
|
+
|
|
144
|
+
const load = useCallback(async () => {
|
|
145
|
+
setError(null);
|
|
146
|
+
try {
|
|
147
|
+
const data = await fetchTDLeaderboard(poolId, { sort, order, limit, offset });
|
|
148
|
+
setResult(data);
|
|
149
|
+
} catch (err: unknown) {
|
|
150
|
+
setError(err instanceof Error ? err.message : "Failed to fetch leaderboard");
|
|
151
|
+
} finally {
|
|
152
|
+
setLoading(false);
|
|
153
|
+
}
|
|
154
|
+
}, [poolId, sort, order, limit, offset]);
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
setLoading(true);
|
|
158
|
+
load();
|
|
159
|
+
|
|
160
|
+
if (pollMs && pollMs > 0) {
|
|
161
|
+
pollRef.current = setInterval(load, pollMs);
|
|
162
|
+
return () => { if (pollRef.current) clearInterval(pollRef.current); };
|
|
163
|
+
}
|
|
164
|
+
}, [load, pollMs]);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
rankings: result?.leaderboard ?? [],
|
|
168
|
+
total: result?.total_entries ?? 0,
|
|
169
|
+
loading,
|
|
170
|
+
error,
|
|
171
|
+
refetch: load,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @devrongx/games — pools/index.ts
|
|
2
|
+
export type {
|
|
3
|
+
ITDCurrency,
|
|
4
|
+
ITDPool,
|
|
5
|
+
ITDPoolDetail,
|
|
6
|
+
ITDPMBConfig,
|
|
7
|
+
ITDPoolContentConfig,
|
|
8
|
+
ITDChallenge,
|
|
9
|
+
ITDChallengeOption,
|
|
10
|
+
ITDPayoutTier,
|
|
11
|
+
ITDLeaderboardEntry,
|
|
12
|
+
ITDLeaderboardResponse,
|
|
13
|
+
ITDPoolEntryRecord,
|
|
14
|
+
ITDPoolBet,
|
|
15
|
+
ITDPoolParlay,
|
|
16
|
+
ITDMyEntryResponse,
|
|
17
|
+
ITDBetInput,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
fetchTDPools,
|
|
22
|
+
fetchTDPoolDetail,
|
|
23
|
+
fetchTDLeaderboard,
|
|
24
|
+
fetchTDMyEntry,
|
|
25
|
+
} from "./fetcher";
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
useTDPools,
|
|
29
|
+
useTDPool,
|
|
30
|
+
useTDPoolEntry,
|
|
31
|
+
useTDLeaderboard,
|
|
32
|
+
} from "./hooks";
|
|
33
|
+
|
|
34
|
+
export type {
|
|
35
|
+
UseTDPoolsResult,
|
|
36
|
+
UseTDPoolResult,
|
|
37
|
+
UseTDPoolEntryResult,
|
|
38
|
+
UseTDLeaderboardResult,
|
|
39
|
+
UseTDLeaderboardOptions,
|
|
40
|
+
} from "./hooks";
|
|
41
|
+
|
|
42
|
+
export { joinTDPool, placeTDBets } from "./actions";
|
|
43
|
+
|
|
44
|
+
export { buildPMBConfig } from "./mapper";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @devrongx/games — pools/mapper.ts
|
|
2
|
+
// Converts ITDPoolDetail + ITDMatch → IChallengeConfig for the PMB UI.
|
|
3
|
+
// This lets all existing PreMatchGame/Questions/Intro components consume real API data.
|
|
4
|
+
|
|
5
|
+
import type { IChallengeConfig, IChallengeMarket, IChallengeSection } from "../games/prematch-bets/config";
|
|
6
|
+
import type { ITDPoolDetail, ITDPMBConfig } from "./types";
|
|
7
|
+
import type { ITDMatch } from "../matches/types";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_PARLAY_COLORS = ["#22E3E8", "#9945FF", "#f83cc5", "#f59e0b", "#22c55e", "#f97316"];
|
|
10
|
+
const DEFAULT_BANNER = "/ipl/default_banner.jpg";
|
|
11
|
+
const DEFAULT_PLAYER_IMAGES: [string, string] = ["/ipl/player1.jpg", "/ipl/player2.jpg"];
|
|
12
|
+
|
|
13
|
+
function buildTeam(team: ITDMatch["team_a"]) {
|
|
14
|
+
return {
|
|
15
|
+
name: team.name,
|
|
16
|
+
shortName: team.short_name || team.name.slice(0, 3).toUpperCase(),
|
|
17
|
+
logo: `/ipl/${team.short_name?.toLowerCase() ?? "team"}.png`,
|
|
18
|
+
gradient: "linear-gradient(180deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0.05) 100%)",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildPMBConfig(pool: ITDPoolDetail, match: ITDMatch): IChallengeConfig {
|
|
23
|
+
const config = pool.config as ITDPMBConfig;
|
|
24
|
+
const contentConfig = pool.content_config;
|
|
25
|
+
|
|
26
|
+
const markets: IChallengeMarket[] = pool.challenges
|
|
27
|
+
.sort((a, b) => a.sort_order - b.sort_order)
|
|
28
|
+
.map((c) => ({
|
|
29
|
+
id: c.category,
|
|
30
|
+
question: c.question,
|
|
31
|
+
icon: c.icon,
|
|
32
|
+
accent: c.accent_color,
|
|
33
|
+
category: c.category,
|
|
34
|
+
backendChallengeId: c.id,
|
|
35
|
+
options: c.options.map((o) => ({
|
|
36
|
+
label: o.label,
|
|
37
|
+
odds: o.odds,
|
|
38
|
+
entry: o.entry,
|
|
39
|
+
risk: o.risk,
|
|
40
|
+
})),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
const sections: IChallengeSection[] = (contentConfig?.sections ?? []).map((s) => ({
|
|
44
|
+
type: s.type as "info" | "challenge",
|
|
45
|
+
title: s.title,
|
|
46
|
+
content: s.content,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
id: pool.machine_name ?? String(pool.id),
|
|
51
|
+
title: pool.name,
|
|
52
|
+
matchTitle: `${match.team_a.short_name ?? match.team_a.name} vs ${match.team_b.short_name ?? match.team_b.name}`,
|
|
53
|
+
tag: "STRATEGY",
|
|
54
|
+
teamA: buildTeam(match.team_a),
|
|
55
|
+
teamB: buildTeam(match.team_b),
|
|
56
|
+
venue: match.venue?.name ?? "",
|
|
57
|
+
bannerImage: contentConfig?.banner_image ?? DEFAULT_BANNER,
|
|
58
|
+
playerImages: (contentConfig?.player_images as [string, string] | undefined) ?? DEFAULT_PLAYER_IMAGES,
|
|
59
|
+
opponents: [buildTeam(match.team_a), buildTeam(match.team_b)],
|
|
60
|
+
matchStartTime: match.scheduled_start_at ?? "",
|
|
61
|
+
startingBalance: pool.starting_coins,
|
|
62
|
+
entryFee: pool.entry_fee,
|
|
63
|
+
totalEntries: pool.entry_count,
|
|
64
|
+
rakePercent: pool.rake_percentage / 100,
|
|
65
|
+
rankBrackets: (config.payout_tiers ?? []).map((t) => ({
|
|
66
|
+
from: t.rank_from,
|
|
67
|
+
to: t.rank_to,
|
|
68
|
+
poolPercent: t.payout_type === "percentage" ? t.value / 100 : 0,
|
|
69
|
+
})),
|
|
70
|
+
leaderboardSeed: `pool-${pool.id}`,
|
|
71
|
+
compoundMultipliers: config.compound_multipliers ?? [],
|
|
72
|
+
parlayConfig: {
|
|
73
|
+
maxSlots: config.parlay?.max_slots ?? 6,
|
|
74
|
+
minLegs: config.parlay?.min_legs ?? 2,
|
|
75
|
+
maxLegs: config.parlay?.max_legs ?? 14,
|
|
76
|
+
defaultStake: config.parlay?.default_stake ?? 100,
|
|
77
|
+
stakeIncrements: config.parlay?.stake_increments ?? 50,
|
|
78
|
+
colors: contentConfig?.colors?.parlay_slot_colors ?? DEFAULT_PARLAY_COLORS,
|
|
79
|
+
},
|
|
80
|
+
categoryLabels: config.category_labels ?? {},
|
|
81
|
+
games: [],
|
|
82
|
+
markets,
|
|
83
|
+
sections,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// @devrongx/games — pools/types.ts
|
|
2
|
+
// Types matching TD backend pool API responses (pool.repo.ts).
|
|
3
|
+
// Status and game_type fields are integers matching DBEnum values in the TD backend.
|
|
4
|
+
|
|
5
|
+
// ─── Game type constants (mirrors DBEnumGameType in TD backend) ────────────────
|
|
6
|
+
export const TDGameType = {
|
|
7
|
+
PRE_MATCH_BETS: 1,
|
|
8
|
+
FANTASY_11: 2,
|
|
9
|
+
BALL_SEQUENCE: 3,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
// ─── Pool status constants (mirrors DBEnumPoolStatus) ─────────────────────────
|
|
13
|
+
export const TDPoolStatus = {
|
|
14
|
+
DRAFT: 1,
|
|
15
|
+
OPEN: 2,
|
|
16
|
+
CLOSED: 3,
|
|
17
|
+
RESOLVING: 4,
|
|
18
|
+
COMPLETE: 5,
|
|
19
|
+
CANCELLED: 6,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
// ─── Challenge status constants (mirrors DBEnumChallengeStatus) ────────────────
|
|
23
|
+
export const TDChallengeStatus = {
|
|
24
|
+
DRAFT: 1,
|
|
25
|
+
OPEN: 2,
|
|
26
|
+
CLOSED: 3,
|
|
27
|
+
RESOLVED: 4,
|
|
28
|
+
CANCELLED: 5,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
// ─── Bet status constants (mirrors DBEnumBetStatus) ───────────────────────────
|
|
32
|
+
export const TDBetStatus = {
|
|
33
|
+
PENDING: 1,
|
|
34
|
+
WON: 2,
|
|
35
|
+
LOST: 3,
|
|
36
|
+
CANCELLED: 4,
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
// ─── Currency ───────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export interface ITDCurrency {
|
|
42
|
+
id: number;
|
|
43
|
+
name: string; // "Game Coins" | "Match Credits"
|
|
44
|
+
symbol: string; // "GC" | "MC"
|
|
45
|
+
partner_payout: boolean; // true = IAG handles USDC payout
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Pool list item (GET /api/pools/match/:matchId) ─────────────────────────
|
|
49
|
+
|
|
50
|
+
export interface ITDPool {
|
|
51
|
+
id: number;
|
|
52
|
+
match_id: number;
|
|
53
|
+
game_type: number; // TDGameType: 1=PRE_MATCH_BETS, 2=FANTASY_11, 3=BALL_SEQUENCE
|
|
54
|
+
name: string;
|
|
55
|
+
machine_name: string | null;
|
|
56
|
+
display_price: string; // "Free" | "$1" | "$5"
|
|
57
|
+
entry_fee: number;
|
|
58
|
+
starting_coins: number;
|
|
59
|
+
status: number; // TDPoolStatus: 1=DRAFT, 2=OPEN, 3=CLOSED, 4=RESOLVING, 5=COMPLETE, 6=CANCELLED
|
|
60
|
+
entry_count: number;
|
|
61
|
+
challenge_count: number;
|
|
62
|
+
currency: ITDCurrency;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Challenge option (nested in pool detail) ────────────────────────────────
|
|
66
|
+
|
|
67
|
+
export interface ITDChallengeOption {
|
|
68
|
+
key: string;
|
|
69
|
+
label: string;
|
|
70
|
+
odds: number;
|
|
71
|
+
entry: number;
|
|
72
|
+
risk: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Challenge (nested in pool detail) ───────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export interface ITDChallenge {
|
|
78
|
+
id: number;
|
|
79
|
+
question: string;
|
|
80
|
+
category: string;
|
|
81
|
+
icon: string;
|
|
82
|
+
accent_color: string;
|
|
83
|
+
sort_order: number;
|
|
84
|
+
options: ITDChallengeOption[];
|
|
85
|
+
status: number; // TDChallengeStatus: 1=DRAFT, 2=OPEN, 3=CLOSED, 4=RESOLVED, 5=CANCELLED
|
|
86
|
+
correct_option: string | null;
|
|
87
|
+
resolved_at: string | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Payout tier ─────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export interface ITDPayoutTier {
|
|
93
|
+
rank_from: number;
|
|
94
|
+
rank_to: number;
|
|
95
|
+
payout_type: "percentage" | "fixed" | "multiplier";
|
|
96
|
+
value: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── PMB pool config (config JSON for pre_match_bets pools) ──────────────────
|
|
100
|
+
|
|
101
|
+
export interface ITDPMBConfig {
|
|
102
|
+
payout_tiers: ITDPayoutTier[];
|
|
103
|
+
compound_multipliers: number[];
|
|
104
|
+
parlay: {
|
|
105
|
+
max_slots: number;
|
|
106
|
+
min_legs: number;
|
|
107
|
+
max_legs: number;
|
|
108
|
+
default_stake: number;
|
|
109
|
+
stake_increments: number;
|
|
110
|
+
};
|
|
111
|
+
category_labels: Record<string, string>;
|
|
112
|
+
max_entries?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Content/display config ──────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
export interface ITDPoolContentConfig {
|
|
118
|
+
banner_image?: string;
|
|
119
|
+
player_images?: [string, string];
|
|
120
|
+
sections?: { type: string; title: string; content: string }[];
|
|
121
|
+
colors?: {
|
|
122
|
+
parlay_slot_colors?: string[];
|
|
123
|
+
accent_color?: string;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Pool detail (GET /api/pools/:id) ────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
export interface ITDPoolDetail extends ITDPool {
|
|
130
|
+
description: string | null;
|
|
131
|
+
rake_percentage: number;
|
|
132
|
+
config: ITDPMBConfig | Record<string, unknown>;
|
|
133
|
+
content_config: ITDPoolContentConfig | null;
|
|
134
|
+
opens_at: string | null;
|
|
135
|
+
closes_at: string | null;
|
|
136
|
+
challenges: ITDChallenge[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Leaderboard entry ───────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
export interface ITDLeaderboardEntry {
|
|
142
|
+
rank: number;
|
|
143
|
+
user_id: number;
|
|
144
|
+
partner_ext_id: string | null;
|
|
145
|
+
entry_id: number;
|
|
146
|
+
current_coins: number;
|
|
147
|
+
final_coins: number | null;
|
|
148
|
+
total_risked: number;
|
|
149
|
+
max_possible: number;
|
|
150
|
+
bets_placed: number;
|
|
151
|
+
bets_won: number;
|
|
152
|
+
bets_lost: number;
|
|
153
|
+
questions_left: number;
|
|
154
|
+
active_parlays: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface ITDLeaderboardResponse {
|
|
158
|
+
pool_id: number;
|
|
159
|
+
total_entries: number;
|
|
160
|
+
leaderboard: ITDLeaderboardEntry[];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Pool entry (GET /api/pools/:id/my-entry) ─────────────────────────────────
|
|
164
|
+
|
|
165
|
+
export interface ITDPoolEntryRecord {
|
|
166
|
+
id: number;
|
|
167
|
+
pool_id: number;
|
|
168
|
+
user_id: number;
|
|
169
|
+
entry_fee_paid: number;
|
|
170
|
+
current_coins: number;
|
|
171
|
+
final_coins: number | null;
|
|
172
|
+
total_risked: number;
|
|
173
|
+
max_possible: number;
|
|
174
|
+
bets_placed: number;
|
|
175
|
+
bets_won: number;
|
|
176
|
+
bets_lost: number;
|
|
177
|
+
questions_left: number;
|
|
178
|
+
questions_total: number;
|
|
179
|
+
active_parlays: number;
|
|
180
|
+
created_at: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ITDPoolBet {
|
|
184
|
+
id: number;
|
|
185
|
+
entry_id: number;
|
|
186
|
+
pool_challenge_id: number;
|
|
187
|
+
challenge_id: number;
|
|
188
|
+
selected_option: string;
|
|
189
|
+
coin_amount: number;
|
|
190
|
+
odds_at_bet: number;
|
|
191
|
+
potential_return: number;
|
|
192
|
+
actual_return: number | null;
|
|
193
|
+
status: number; // TDBetStatus: 1=PENDING, 2=WON, 3=LOST, 4=CANCELLED
|
|
194
|
+
parlay_id: number | null;
|
|
195
|
+
created_at: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ITDPoolParlay {
|
|
199
|
+
id: number;
|
|
200
|
+
entry_id: number;
|
|
201
|
+
total_coin_amount: number;
|
|
202
|
+
combined_multiplier: number;
|
|
203
|
+
potential_return: number;
|
|
204
|
+
actual_return: number | null;
|
|
205
|
+
status: number; // TDBetStatus: 1=PENDING, 2=WON, 3=LOST, 4=CANCELLED
|
|
206
|
+
bets: {
|
|
207
|
+
id: number;
|
|
208
|
+
challenge_id: number;
|
|
209
|
+
selected_option: string;
|
|
210
|
+
coin_amount: number;
|
|
211
|
+
odds_at_bet: number;
|
|
212
|
+
status: number; // TDBetStatus
|
|
213
|
+
}[];
|
|
214
|
+
created_at: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface ITDMyEntryResponse {
|
|
218
|
+
entry: ITDPoolEntryRecord;
|
|
219
|
+
bets: ITDPoolBet[];
|
|
220
|
+
parlays: ITDPoolParlay[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── Bet input ────────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
export interface ITDBetInput {
|
|
226
|
+
challenge_id: number;
|
|
227
|
+
selected_option: string;
|
|
228
|
+
coin_amount: number;
|
|
229
|
+
}
|