@devrongx/games 0.4.0 → 0.4.2

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.0",
3
+ "version": "0.4.2",
4
4
  "description": "Game UI components for sports prediction markets",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -25,6 +25,7 @@ import { useTDMatches } from "../../matches/useTDMatches";
25
25
  import { buildPMBConfig } from "../../pools/mapper";
26
26
  import { joinTDPool, placeTDBets } from "../../pools/actions";
27
27
  import type { ITDLeaderboardEntry } from "../../pools/types";
28
+ import { TDPoolStatus } from "../../pools/types";
28
29
  import { calcRankPayout } from "./config";
29
30
 
30
31
  const GAME_ID = "pre-match";
@@ -35,13 +36,13 @@ type UserFlowState = "intro" | "questions" | "game";
35
36
  type ActiveView = UserFlowState | "submitted" | "live" | "results";
36
37
 
37
38
  function getActiveView(
38
- poolStatus: string | undefined,
39
+ poolStatus: number | undefined,
39
40
  hasEntry: boolean,
40
41
  userFlowState: UserFlowState,
41
42
  ): ActiveView {
42
- if (!poolStatus) return userFlowState;
43
- if (poolStatus === "complete" || poolStatus === "cancelled") return "results";
44
- if (poolStatus === "closed" || poolStatus === "resolving") return hasEntry ? "live" : "results";
43
+ if (poolStatus === undefined) return userFlowState;
44
+ if (poolStatus === TDPoolStatus.COMPLETE || poolStatus === TDPoolStatus.CANCELLED) return "results";
45
+ if (poolStatus === TDPoolStatus.CLOSED || poolStatus === TDPoolStatus.RESOLVING) return hasEntry ? "live" : "results";
45
46
  // Pool is open
46
47
  if (hasEntry) return "submitted";
47
48
  return userFlowState;
@@ -121,7 +122,7 @@ export const PreMatchBetsPopup = ({ poolId, matchId }: PreMatchBetsPopupProps) =
121
122
  const [submitError, setSubmitError] = useState<string | null>(null);
122
123
 
123
124
  const betSummary = useMemo(
124
- () => config ? calcBetSummary(bets, config) : { selectedCount: 0, compoundMultiplier: 0, totalEntry: 0, compoundedReward: 0, remainingBalance: 0, riskPercent: 0, potentialBalance: 0, prizes: [] },
125
+ () => config ? calcBetSummary(bets, config) : { selectedCount: 0, compoundMultiplier: 0, totalEntry: 0, baseReward: 0, compoundedReward: 0, remainingBalance: 0, riskPercent: 0, potentialBalance: 0 },
125
126
  [bets, config],
126
127
  );
127
128
 
@@ -9,6 +9,7 @@ import { IChallengeConfig } from "./config";
9
9
  import { LeaderboardRow } from "./LeaderboardRow";
10
10
  import { OUTFIT, MARKET_ICONS } from "./constants";
11
11
  import type { ITDPoolDetail, ITDMyEntryResponse, ITDLeaderboardEntry } from "../../pools/types";
12
+ import { TDChallengeStatus, TDBetStatus } from "../../pools/types";
12
13
 
13
14
  interface PreMatchLiveProps {
14
15
  config: IChallengeConfig;
@@ -50,7 +51,7 @@ export function PreMatchLive({
50
51
  resolved_at: null as string | null,
51
52
  }));
52
53
 
53
- const resolvedCount = challenges.filter((c) => c.status === "resolved").length;
54
+ const resolvedCount = challenges.filter((c) => c.status === TDChallengeStatus.RESOLVED).length;
54
55
  const totalCount = challenges.length;
55
56
  const progressPct = totalCount > 0 ? Math.round((resolvedCount / totalCount) * 100) : 0;
56
57
 
@@ -105,9 +106,9 @@ export function PreMatchLive({
105
106
  {challenges.map((c) => {
106
107
  const Icon = MARKET_ICONS[c.icon] ?? MARKET_ICONS.coin;
107
108
  const myBet = myBets.find((b) => b.challenge_id === c.id);
108
- const isResolved = c.status === "resolved";
109
- const won = isResolved && myBet?.status === "won";
110
- const lost = isResolved && myBet?.status === "lost";
109
+ const isResolved = c.status === TDChallengeStatus.RESOLVED;
110
+ const won = isResolved && myBet?.status === TDBetStatus.WON;
111
+ const lost = isResolved && myBet?.status === TDBetStatus.LOST;
111
112
 
112
113
  return (
113
114
  <div
@@ -8,6 +8,7 @@ import { IChallengeConfig } from "./config";
8
8
  import { LeaderboardRow } from "./LeaderboardRow";
9
9
  import { OUTFIT, MARKET_ICONS } from "./constants";
10
10
  import type { ITDPoolDetail, ITDMyEntryResponse, ITDLeaderboardEntry } from "../../pools/types";
11
+ import { TDBetStatus } from "../../pools/types";
11
12
 
12
13
  // USDC icon inline
13
14
  const UsdcIcon = ({ size = 12 }: { size?: number }) => (
@@ -42,8 +43,8 @@ export function PreMatchResults({
42
43
 
43
44
  const stats = useMemo(() => {
44
45
  const individualBets = myBets.filter((b) => b.parlay_id === null);
45
- const won = individualBets.filter((b) => b.status === "won").length;
46
- const lost = individualBets.filter((b) => b.status === "lost").length;
46
+ const won = individualBets.filter((b) => b.status === TDBetStatus.WON).length;
47
+ const lost = individualBets.filter((b) => b.status === TDBetStatus.LOST).length;
47
48
  const totalSpent = individualBets.reduce((s, b) => s + b.coin_amount, 0);
48
49
  const totalEarned = entry?.final_coins ?? 0;
49
50
  return {
@@ -116,8 +117,8 @@ export function PreMatchResults({
116
117
  {myBets.filter((b) => b.parlay_id === null).map((bet) => {
117
118
  const market = config.markets.find((m) => m.backendChallengeId === bet.challenge_id);
118
119
  const Icon = MARKET_ICONS[market?.icon ?? "coin"];
119
- const won = bet.status === "won";
120
- const lost = bet.status === "lost";
120
+ const won = bet.status === TDBetStatus.WON;
121
+ const lost = bet.status === TDBetStatus.LOST;
121
122
 
122
123
  return (
123
124
  <div
@@ -7,6 +7,7 @@ import { Calendar, MapPin, Loader2, Trophy, Zap, Target, Users } from "lucide-re
7
7
  import { useTDMatches } from "./useTDMatches";
8
8
  import { useTDPools } from "../pools/hooks";
9
9
  import type { ITDPool } from "../pools/types";
10
+ import { TDGameType, TDPoolStatus } from "../pools/types";
10
11
  import type { ITDMatch } from "./types";
11
12
 
12
13
  // TD match status enum values
@@ -105,15 +106,16 @@ function groupByDate(matches: ITDMatch[]): IMatchDay[] {
105
106
 
106
107
  /* ── Pool pills for a match ── */
107
108
 
108
- const GAME_TYPE_STYLE: Record<string, { icon: typeof Target; color: string; bg: string }> = {
109
- pre_match_bets: { icon: Target, color: "#22E3E8", bg: "rgba(34,227,232,0.06)" },
110
- fantasy_11: { icon: Users, color: "#9945FF", bg: "rgba(153,69,255,0.06)" },
109
+ const GAME_TYPE_STYLE: Record<number, { icon: typeof Target; color: string; bg: string; label: string }> = {
110
+ [TDGameType.PRE_MATCH_BETS]: { icon: Target, color: "#22E3E8", bg: "rgba(34,227,232,0.06)", label: "Pre-Match Bets" },
111
+ [TDGameType.FANTASY_11]: { icon: Users, color: "#9945FF", bg: "rgba(153,69,255,0.06)", label: "Fantasy 11" },
112
+ [TDGameType.BALL_SEQUENCE]: { icon: Zap, color: "#FF9945", bg: "rgba(255,153,69,0.06)", label: "Ball Sequence" },
111
113
  };
112
114
 
113
115
  function PoolPill({ pool, onPress }: { pool: ITDPool; onPress: (pool: ITDPool) => void }) {
114
116
  const style = GAME_TYPE_STYLE[pool.game_type] ?? { icon: Target, color: "#22E3E8", bg: "rgba(34,227,232,0.06)" };
115
117
  const Icon = style.icon;
116
- const isClosed = pool.status === "closed" || pool.status === "resolving" || pool.status === "complete";
118
+ const isClosed = pool.status === TDPoolStatus.CLOSED || pool.status === TDPoolStatus.RESOLVING || pool.status === TDPoolStatus.COMPLETE;
117
119
 
118
120
  return (
119
121
  <button
@@ -150,13 +152,15 @@ function MatchGameSection({
150
152
 
151
153
  if (isCompleted || loading || pools.length === 0) return null;
152
154
 
153
- // Group by game_type
154
- const grouped: Record<string, ITDPool[]> = {};
155
+ // Group by game_type (integer)
156
+ const grouped: Record<number, ITDPool[]> = {};
155
157
  for (const p of pools) {
156
158
  (grouped[p.game_type] ??= []).push(p);
157
159
  }
158
160
 
159
- const gameTypes = Object.keys(grouped).filter((gt) => gt in GAME_TYPE_STYLE);
161
+ const gameTypes = (Object.keys(grouped) as unknown as number[])
162
+ .map(Number)
163
+ .filter((gt) => gt in GAME_TYPE_STYLE);
160
164
  if (gameTypes.length === 0) return null;
161
165
 
162
166
  return (
@@ -169,7 +173,7 @@ function MatchGameSection({
169
173
  <div className="flex items-center gap-1 px-1">
170
174
  <Icon size={8} style={{ color: style.color }} />
171
175
  <span className="text-[7px] barlowcondensedBold tracking-wide text-white/60">
172
- {gt === "pre_match_bets" ? "Pre-Match Bets" : gt === "fantasy_11" ? "Fantasy 11" : gt}
176
+ {style.label}
173
177
  </span>
174
178
  </div>
175
179
  <div className="flex gap-1 flex-wrap">
@@ -1,5 +1,40 @@
1
1
  // @devrongx/games — pools/types.ts
2
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;
3
38
 
4
39
  // ─── Currency ───────────────────────────────────────────────────────────────
5
40
 
@@ -15,13 +50,13 @@ export interface ITDCurrency {
15
50
  export interface ITDPool {
16
51
  id: number;
17
52
  match_id: number;
18
- game_type: string; // "pre_match_bets" | "fantasy_11" | "ball_sequence"
53
+ game_type: number; // TDGameType: 1=PRE_MATCH_BETS, 2=FANTASY_11, 3=BALL_SEQUENCE
19
54
  name: string;
20
55
  machine_name: string | null;
21
56
  display_price: string; // "Free" | "$1" | "$5"
22
57
  entry_fee: number;
23
58
  starting_coins: number;
24
- status: string; // "draft"|"open"|"closed"|"resolving"|"complete"|"cancelled"
59
+ status: number; // TDPoolStatus: 1=DRAFT, 2=OPEN, 3=CLOSED, 4=RESOLVING, 5=COMPLETE, 6=CANCELLED
25
60
  entry_count: number;
26
61
  challenge_count: number;
27
62
  currency: ITDCurrency;
@@ -47,7 +82,7 @@ export interface ITDChallenge {
47
82
  accent_color: string;
48
83
  sort_order: number;
49
84
  options: ITDChallengeOption[];
50
- status: string; // "draft"|"open"|"closed"|"resolved"
85
+ status: number; // TDChallengeStatus: 1=DRAFT, 2=OPEN, 3=CLOSED, 4=RESOLVED, 5=CANCELLED
51
86
  correct_option: string | null;
52
87
  resolved_at: string | null;
53
88
  }
@@ -155,7 +190,7 @@ export interface ITDPoolBet {
155
190
  odds_at_bet: number;
156
191
  potential_return: number;
157
192
  actual_return: number | null;
158
- status: string; // "pending"|"won"|"lost"|"push"
193
+ status: number; // TDBetStatus: 1=PENDING, 2=WON, 3=LOST, 4=CANCELLED
159
194
  parlay_id: number | null;
160
195
  created_at: string;
161
196
  }
@@ -167,14 +202,14 @@ export interface ITDPoolParlay {
167
202
  combined_multiplier: number;
168
203
  potential_return: number;
169
204
  actual_return: number | null;
170
- status: string;
205
+ status: number; // TDBetStatus: 1=PENDING, 2=WON, 3=LOST, 4=CANCELLED
171
206
  bets: {
172
207
  id: number;
173
208
  challenge_id: number;
174
209
  selected_option: string;
175
210
  coin_amount: number;
176
211
  odds_at_bet: number;
177
- status: string;
212
+ status: number; // TDBetStatus
178
213
  }[];
179
214
  created_at: string;
180
215
  }