@vatvaghool/create-ipl-dashboard 0.1.0

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.
Files changed (108) hide show
  1. package/README.md +75 -0
  2. package/package.json +27 -0
  3. package/src/generate-template.mjs +73 -0
  4. package/src/index.mjs +98 -0
  5. package/src/prompts.mjs +78 -0
  6. package/src/scaffold.mjs +129 -0
  7. package/src/scraper.mjs +79 -0
  8. package/template/.dockerignore +13 -0
  9. package/template/AGENTS.md +5 -0
  10. package/template/Dockerfile.sync +14 -0
  11. package/template/README.md +160 -0
  12. package/template/app/api/ipl/data.ts +24 -0
  13. package/template/app/api/ipl/route.ts +505 -0
  14. package/template/app/api/ipl/transfers/route.ts +261 -0
  15. package/template/app/api/ipl/transfers/transform.ts +156 -0
  16. package/template/app/api/ipl/transform.ts +20 -0
  17. package/template/app/api/ipl/upcoming-matches/route.ts +18 -0
  18. package/template/app/api/ops/status/route.ts +225 -0
  19. package/template/app/components/AIRoasting.tsx +278 -0
  20. package/template/app/components/ColorWave.tsx +193 -0
  21. package/template/app/components/CrownBattle.tsx +207 -0
  22. package/template/app/components/DashboardContent.tsx +377 -0
  23. package/template/app/components/FantasyStockTicker.tsx +192 -0
  24. package/template/app/components/FireworksBurst.tsx +225 -0
  25. package/template/app/components/LiveMatchTicker.tsx +117 -0
  26. package/template/app/components/MatchRecapScroll.tsx +135 -0
  27. package/template/app/components/MatchStoryScrubber.tsx +274 -0
  28. package/template/app/components/PerformanceTracker.tsx +132 -0
  29. package/template/app/components/ProgressGlowRings.tsx +157 -0
  30. package/template/app/components/TeamDNAScanner.tsx +238 -0
  31. package/template/app/components/ThemeToggle.tsx +74 -0
  32. package/template/app/components/dashboard/CaptainBoard.tsx +138 -0
  33. package/template/app/components/dashboard/ChartBoard.tsx +162 -0
  34. package/template/app/components/dashboard/LatestBadge.tsx +23 -0
  35. package/template/app/components/dashboard/LedgerTable.tsx +385 -0
  36. package/template/app/components/dashboard/SectionCard.tsx +59 -0
  37. package/template/app/components/dashboard/StickyMini.tsx +20 -0
  38. package/template/app/components/dashboard/index.ts +6 -0
  39. package/template/app/components/ui/DashboardChartFrame.tsx +74 -0
  40. package/template/app/components/ui/DoodleSpinner.tsx +15 -0
  41. package/template/app/components/ui/TeamPills.tsx +41 -0
  42. package/template/app/data/match-points.ts +3 -0
  43. package/template/app/data/teams.ts +32 -0
  44. package/template/app/globals.css +1267 -0
  45. package/template/app/hooks/dashboard/index.ts +1 -0
  46. package/template/app/hooks/dashboard/useDashboardModel.ts +25 -0
  47. package/template/app/hooks/dashboardCache.ts +53 -0
  48. package/template/app/hooks/dashboardPolling.ts +53 -0
  49. package/template/app/hooks/snapshotCache.ts +47 -0
  50. package/template/app/hooks/useDashboardData.ts +28 -0
  51. package/template/app/layout.tsx +75 -0
  52. package/template/app/lib/aiAgent.ts +444 -0
  53. package/template/app/lib/config.ts +29 -0
  54. package/template/app/lib/dashboard/index.ts +1 -0
  55. package/template/app/lib/dashboard/model.ts +257 -0
  56. package/template/app/lib/dashboardData.ts +50 -0
  57. package/template/app/lib/dashboardView.ts +22 -0
  58. package/template/app/lib/detailedData.ts +112 -0
  59. package/template/app/lib/matchStatus.ts +28 -0
  60. package/template/app/lib/matches.ts +131 -0
  61. package/template/app/lib/teamBadges.ts +223 -0
  62. package/template/app/lib/upcomingMatches.ts +154 -0
  63. package/template/app/lib/useDb.ts +29 -0
  64. package/template/app/lib/utils/diff.ts +24 -0
  65. package/template/app/lib/utils/getChartColor.ts +17 -0
  66. package/template/app/lib/utils/getStdDeviation.ts +6 -0
  67. package/template/app/lib/utils/time.ts +40 -0
  68. package/template/app/lib/utils.ts +70 -0
  69. package/template/app/page.tsx +15 -0
  70. package/template/app/store/dashboardStore.ts +85 -0
  71. package/template/app/types/dashboard.ts +75 -0
  72. package/template/app/types.ts +130 -0
  73. package/template/app/utils/dashboard/index.ts +72 -0
  74. package/template/eslint.config.mjs +18 -0
  75. package/template/infra/cloud-run/README.md +68 -0
  76. package/template/infra/cloud-run/sync-job.yaml +32 -0
  77. package/template/infra/cutover/README.md +84 -0
  78. package/template/infra/vercel/README.md +57 -0
  79. package/template/next.config.ts +7 -0
  80. package/template/package-lock.json +7330 -0
  81. package/template/package.json +47 -0
  82. package/template/packages/ipl-dashboard-utils/README.md +316 -0
  83. package/template/packages/ipl-dashboard-utils/package.json +34 -0
  84. package/template/packages/ipl-dashboard-utils/src/index.ts +22 -0
  85. package/template/packages/ipl-dashboard-utils/src/transform.ts +687 -0
  86. package/template/packages/ipl-dashboard-utils/src/types.ts +88 -0
  87. package/template/packages/ipl-dashboard-utils/tsconfig.build.json +17 -0
  88. package/template/postcss.config.mjs +7 -0
  89. package/template/scripts/capture-ipl-auth.mjs +54 -0
  90. package/template/scripts/deploy-cloud-run-sync.sh +48 -0
  91. package/template/scripts/deploy-cloud-scheduler.sh +42 -0
  92. package/template/scripts/dev-simple.js +31 -0
  93. package/template/scripts/dev-welcome.mjs +38 -0
  94. package/template/scripts/monitor-ops-status.sh +50 -0
  95. package/template/scripts/seed-mongodb.ts +115 -0
  96. package/template/scripts/sync-cloud.mjs +50 -0
  97. package/template/scripts/sync-ipl.mjs +238 -0
  98. package/template/scripts/sync-transfers-daily.mjs +175 -0
  99. package/template/scripts/verify-production.mjs +108 -0
  100. package/template/tests/coverage-gaps.test.ts +290 -0
  101. package/template/tests/dashboard-polling.test.ts +96 -0
  102. package/template/tests/detailed-data.test.ts +60 -0
  103. package/template/tests/ipl-transform.test.ts +590 -0
  104. package/template/tests/transfers-route.test.ts +109 -0
  105. package/template/tests/upcoming-matches.test.ts +34 -0
  106. package/template/tests/utils-and-cache.test.ts +267 -0
  107. package/template/tsconfig.json +35 -0
  108. package/template/vercel.json +7 -0
@@ -0,0 +1,257 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { DashboardData } from "../../types";
3
+ import {
4
+ getDisplayLiveMatchId,
5
+ getPlayedMatchRows,
6
+ getNumericRowEntries,
7
+ } from "../../lib/dashboardData";
8
+ import { getMatchViewState } from "../../lib/dashboardView";
9
+ import { buildTeamBadgeMap } from "../../lib/teamBadges";
10
+ import { buildDetailedTableRows } from "../../lib/detailedData";
11
+ import type { DashboardModel } from "../../types/dashboard";
12
+ import { formatCompactNumber } from "../../utils/dashboard";
13
+ import {
14
+ FaTrophy,
15
+ FaArrowTrendUp,
16
+ FaBolt,
17
+ FaArrowTrendDown,
18
+ FaCircle,
19
+ FaMinus,
20
+ } from "react-icons/fa6";
21
+
22
+ const BRIGHT_COLORS = {
23
+ yellow: "#eab308",
24
+ green: "#84cc16",
25
+ pink: "#ec4899",
26
+ orange: "#f97316",
27
+ blue: "#3b82f6",
28
+ purple: "#a855f7",
29
+ cyan: "#22d3ee",
30
+ amber: "#f59e0b",
31
+ } as const;
32
+
33
+ export function buildDashboardModel(
34
+ data: DashboardData | null,
35
+ ): DashboardModel | null {
36
+ if (!data?.overall?.length) {
37
+ return null;
38
+ }
39
+
40
+ const overall = [...data.overall].sort((a, b) => a.rank - b.rank);
41
+ const normalizedOverall = overall.map((team, index) => {
42
+ const nextTeam = overall[index + 1];
43
+ const gapToNext =
44
+ typeof nextTeam?.points === "number"
45
+ ? Math.max(0, team.points - nextTeam.points)
46
+ : undefined;
47
+
48
+ return {
49
+ ...team,
50
+ gapToNext: team.gapToNext ?? gapToNext,
51
+ };
52
+ });
53
+
54
+ const playedMatches = getPlayedMatchRows(data.daily);
55
+ const matchView = getMatchViewState(data.daily);
56
+ const liveEntries = matchView.latestRow
57
+ ? getNumericRowEntries(matchView.latestRow).sort(
58
+ (a, b) => b.points - a.points,
59
+ )
60
+ : [];
61
+
62
+ const leader = normalizedOverall[0];
63
+ const chaser = normalizedOverall[1];
64
+ const woodenSpoon = normalizedOverall.at(-1);
65
+ const mostTransfers = [...normalizedOverall]
66
+ .filter((team) => typeof team.transfersLeft === "number")
67
+ .sort((a, b) => (b.transfersLeft ?? 0) - (a.transfersLeft ?? 0))[0];
68
+ const hottest = [...normalizedOverall].sort(
69
+ (a, b) => (b.lastMatchPoints ?? 0) - (a.lastMatchPoints ?? 0),
70
+ )[0];
71
+
72
+ const topTeams = normalizedOverall.slice(0, 8);
73
+ const barLeaderboard = normalizedOverall.slice(0, 8).map((team) => ({
74
+ label: team.name,
75
+ value: team.points,
76
+ sublabel: `#${team.rank}`,
77
+ }));
78
+
79
+ const liveBars = liveEntries.slice(0, 8).map((entry) => ({
80
+ label: entry.team,
81
+ value: entry.points,
82
+ sublabel: matchView.isLive
83
+ ? "live"
84
+ : (matchView.latestRow?.day ?? "latest"),
85
+ }));
86
+
87
+ const captainTeams = normalizedOverall.slice(0, 8);
88
+ const latestDailyRow = matchView.latestRow;
89
+ const tableRows = buildDetailedTableRows({
90
+ overall: normalizedOverall,
91
+ latestDailyRow,
92
+ snapshotLeaders: data.snapshot?.leaders,
93
+ transfers: data.transfers,
94
+ }).slice(0, 8);
95
+
96
+ const teamBadges = buildTeamBadgeMap(data);
97
+ const topTeamsChart = normalizedOverall.slice(0, 8).map((team) => ({
98
+ label: team.name,
99
+ value: team.points,
100
+ sublabel: `#${team.rank}`,
101
+ difference: typeof team.gapToNext === "number" ? team.gapToNext : undefined,
102
+ }));
103
+
104
+ const insights = createInsights(
105
+ leader,
106
+ chaser,
107
+ mostTransfers,
108
+ woodenSpoon,
109
+ liveEntries,
110
+ matchView,
111
+ );
112
+ const summaryCards = createSummaryCards(
113
+ leader,
114
+ hottest,
115
+ mostTransfers,
116
+ woodenSpoon,
117
+ chaser,
118
+ );
119
+
120
+ return {
121
+ leader,
122
+ topTeams,
123
+ normalizedOverall,
124
+ teamBadges,
125
+ insights,
126
+ summaryCards,
127
+ barLeaderboard,
128
+ liveBars,
129
+ topTeamsChart,
130
+ captainTeams,
131
+ tableRows,
132
+ liveLeader: liveEntries[0],
133
+ liveLabel: matchView.isLive
134
+ ? `Match ${getDisplayLiveMatchId(data.daily)} live`
135
+ : playedMatches.length
136
+ ? `Match ${getDisplayLiveMatchId(data.daily)} recap`
137
+ : "Live page",
138
+ liveChartTitle: matchView.isLive
139
+ ? `Match ${getDisplayLiveMatchId(data.daily)} Pulse`
140
+ : playedMatches.length
141
+ ? `Match ${getDisplayLiveMatchId(data.daily)} Recap`
142
+ : "Latest Match Pulse",
143
+ matchCount: playedMatches.length,
144
+ hottest,
145
+ };
146
+ }
147
+
148
+ function createInsights(
149
+ leader: any,
150
+ chaser: any,
151
+ mostTransfers: any,
152
+ woodenSpoon: any,
153
+ liveEntries: any[],
154
+ matchView: any,
155
+ ) {
156
+ return [
157
+ {
158
+ id: "leader-gap",
159
+ title: "Big circle around first place",
160
+ note: chaser
161
+ ? `${leader.name} leads ${chaser.name} by ${(leader.points - chaser.points).toLocaleString()} points.`
162
+ : `${leader.name} is alone at the top for now.`,
163
+ accent: BRIGHT_COLORS.yellow,
164
+ icon: FaTrophy,
165
+ },
166
+ {
167
+ id: "live-leader",
168
+ title: matchView.isLive ? "Live scribble" : "Last finished match",
169
+ note: liveEntries[0]
170
+ ? `${liveEntries[0].team} owns the latest page with ${liveEntries[0].points.toLocaleString()} points.`
171
+ : "No live row yet, so the margin is waiting for the next burst.",
172
+ accent: BRIGHT_COLORS.green,
173
+ icon: FaArrowTrendUp,
174
+ },
175
+ {
176
+ id: "transfers",
177
+ title: "Sticky note on transfers",
178
+ note: mostTransfers
179
+ ? `${mostTransfers.name} still has ${mostTransfers.transfersLeft} transfers tucked away.`
180
+ : "Transfer counts will appear once the snapshot includes them.",
181
+ accent: BRIGHT_COLORS.pink,
182
+ icon: FaBolt,
183
+ },
184
+ {
185
+ id: "dramatic",
186
+ title: "Teacher red pen",
187
+ note: woodenSpoon
188
+ ? `${woodenSpoon.name} is on cleanup duty at rank #${woodenSpoon.rank}, but one wild match can rewrite the page.`
189
+ : "The bottom of the table is still being drafted.",
190
+ accent: BRIGHT_COLORS.orange,
191
+ icon: FaArrowTrendDown,
192
+ },
193
+ ];
194
+ }
195
+
196
+ function createSummaryCards(
197
+ leader: any,
198
+ hottest: any,
199
+ mostTransfers: any,
200
+ woodenSpoon: any,
201
+ chaser: any,
202
+ ) {
203
+ return [
204
+ {
205
+ id: "leader-summary",
206
+ title: "Leader",
207
+ value: `${leader.name}`,
208
+ note: `${formatCompactNumber(leader.points)} pts`,
209
+ accent: BRIGHT_COLORS.yellow,
210
+ icon: FaTrophy,
211
+ },
212
+ {
213
+ id: "hot-streak",
214
+ title: "Hottest",
215
+ value: `${hottest.name}`,
216
+ note: `${(hottest.lastMatchPoints ?? 0).toLocaleString()} pts`,
217
+ accent: BRIGHT_COLORS.green,
218
+ icon: FaArrowTrendUp,
219
+ },
220
+ {
221
+ id: "transfer-stock",
222
+ title: "Transfers",
223
+ value: mostTransfers ? `${mostTransfers.name}` : "—",
224
+ note: mostTransfers ? `${mostTransfers.transfersLeft} left` : "No data",
225
+ accent: BRIGHT_COLORS.pink,
226
+ icon: FaBolt,
227
+ },
228
+ {
229
+ id: "bottom-watch",
230
+ title: "Bottom",
231
+ value: woodenSpoon ? `${woodenSpoon.name}` : "—",
232
+ note: woodenSpoon ? `#${woodenSpoon.rank}` : "No data",
233
+ accent: BRIGHT_COLORS.orange,
234
+ icon: FaCircle,
235
+ },
236
+ {
237
+ id: "chaser",
238
+ title: "Close chase",
239
+ value: chaser ? `${chaser.name}` : "—",
240
+ note: chaser
241
+ ? `${(leader.points - chaser.points).toLocaleString()} pts behind`
242
+ : "No chaser",
243
+ accent: BRIGHT_COLORS.blue,
244
+ icon: FaBolt,
245
+ },
246
+ {
247
+ id: "gap-to-last",
248
+ title: "Field spread",
249
+ value: woodenSpoon
250
+ ? `${(leader.points - woodenSpoon.points).toLocaleString()} pts`
251
+ : "—",
252
+ note: "Leader vs bottom gap",
253
+ accent: BRIGHT_COLORS.purple,
254
+ icon: FaMinus,
255
+ },
256
+ ];
257
+ }
@@ -0,0 +1,50 @@
1
+ import type { DailyChartRow } from "../types";
2
+
3
+ export const parseMatchId = (day: string) => {
4
+ const match = day.match(/^Match\s+(\d+)$/i);
5
+ const parsed = match ? Number(match[1]) : Number.NaN;
6
+
7
+ return Number.isFinite(parsed) ? parsed : undefined;
8
+ };
9
+
10
+ export const isLiveUpdateRow = (row: DailyChartRow) => row.day === "Live Update";
11
+
12
+ export const isMatchRow = (row: DailyChartRow) =>
13
+ typeof parseMatchId(row.day) === "number";
14
+
15
+ export const getNumericRowEntries = (row: DailyChartRow) =>
16
+ Object.entries(row)
17
+ .filter(([key]) => key !== "day")
18
+ .map(([team, value]) => ({
19
+ team,
20
+ points: Number(value ?? 0),
21
+ }))
22
+ .filter((entry) => Number.isFinite(entry.points));
23
+
24
+ export const hasMeaningfulMatchData = (row: DailyChartRow) =>
25
+ getNumericRowEntries(row).some((entry) => entry.points !== 0);
26
+
27
+ export const getLiveUpdateRow = (daily: DailyChartRow[]) =>
28
+ daily.find(isLiveUpdateRow);
29
+
30
+ export const getHistoricalMatchRows = (daily: DailyChartRow[]) =>
31
+ daily.filter(isMatchRow).sort((a, b) => {
32
+ const aId = parseMatchId(a.day) ?? 0;
33
+ const bId = parseMatchId(b.day) ?? 0;
34
+
35
+ return aId - bId;
36
+ });
37
+
38
+ export const getPlayedMatchRows = (daily: DailyChartRow[]) =>
39
+ getHistoricalMatchRows(daily).filter(hasMeaningfulMatchData);
40
+
41
+ export const getLatestPlayedMatchId = (daily: DailyChartRow[]) =>
42
+ getPlayedMatchRows(daily).reduce((latest, row) => {
43
+ const matchId = parseMatchId(row.day) ?? 0;
44
+ return Math.max(latest, matchId);
45
+ }, 0);
46
+
47
+ export const getDisplayLiveMatchId = (daily: DailyChartRow[]) => {
48
+ const latestPlayedMatchId = getLatestPlayedMatchId(daily);
49
+ return getLiveUpdateRow(daily) ? latestPlayedMatchId + 1 : latestPlayedMatchId;
50
+ };
@@ -0,0 +1,22 @@
1
+ import type { DailyChartRow } from "../types";
2
+ import { getLiveUpdateRow, getPlayedMatchRows } from "./dashboardData.ts";
3
+
4
+ export const getLatestMatchRow = (daily: DailyChartRow[]) =>
5
+ getLiveUpdateRow(daily) ?? getPlayedMatchRows(daily).at(-1);
6
+
7
+ export const hasLiveMatchRow = (daily: DailyChartRow[]) =>
8
+ Boolean(getLiveUpdateRow(daily));
9
+
10
+ export const getMatchViewState = (daily: DailyChartRow[]) => {
11
+ const liveRow = getLiveUpdateRow(daily);
12
+ const playedRows = getPlayedMatchRows(daily);
13
+ const latestRow = liveRow ?? playedRows.at(-1);
14
+
15
+ return {
16
+ liveRow,
17
+ playedRows,
18
+ latestRow,
19
+ isLive: Boolean(liveRow),
20
+ hasMatches: playedRows.length > 0 || Boolean(liveRow),
21
+ };
22
+ };
@@ -0,0 +1,112 @@
1
+ import { config } from "./config.ts";
2
+ import type {
3
+ DashboardData,
4
+ OverallChartItem,
5
+ ScrapedLeaderboardItem,
6
+ } from "../types";
7
+
8
+ type DetailedTableRow = OverallChartItem & {
9
+ latestPoints?: number;
10
+ transferMatchesPlayed?: number;
11
+ };
12
+
13
+ const getSnapshotBoostersUsed = (leader: unknown) => {
14
+ if (!leader || typeof leader !== "object") {
15
+ return undefined;
16
+ }
17
+
18
+ const item = leader as Record<string, unknown>;
19
+ const rawValue = item.boostersUsed ?? item.boosters;
20
+
21
+ if (typeof rawValue === "string") {
22
+ return rawValue.trim();
23
+ }
24
+
25
+ if (typeof rawValue === "number" && Number.isFinite(rawValue)) {
26
+ return String(rawValue);
27
+ }
28
+
29
+ return undefined;
30
+ };
31
+
32
+ const toNumber = (value: string | number | undefined) => {
33
+ if (typeof value === "number") {
34
+ return Number.isFinite(value) ? value : undefined;
35
+ }
36
+
37
+ if (typeof value === "string") {
38
+ const parsed = Number(value.trim());
39
+ return Number.isFinite(parsed) ? parsed : undefined;
40
+ }
41
+
42
+ return undefined;
43
+ };
44
+
45
+ const buildTransferMap = (transfers?: DashboardData["transfers"]) => {
46
+ return new Map(
47
+ (transfers?.teams ?? []).map((team) => [team.team.toLowerCase(), team]),
48
+ );
49
+ };
50
+
51
+ const buildSnapshotLeaderMap = (leaders?: ScrapedLeaderboardItem[]) => {
52
+ return new Map((leaders ?? []).map((leader) => [leader.name, leader]));
53
+ };
54
+
55
+ export const buildDetailedTableRows = ({
56
+ overall,
57
+ latestDailyRow,
58
+ snapshotLeaders,
59
+ transfers,
60
+ }: {
61
+ overall: OverallChartItem[];
62
+ latestDailyRow?: Record<string, string | number>;
63
+ snapshotLeaders?: ScrapedLeaderboardItem[];
64
+ transfers?: DashboardData["transfers"];
65
+ }): DetailedTableRow[] => {
66
+ const transferMap = buildTransferMap(transfers);
67
+ const snapshotLeaderMap = buildSnapshotLeaderMap(snapshotLeaders);
68
+
69
+ return [...overall]
70
+ .sort((a, b) => a.rank - b.rank)
71
+ .map((row) => {
72
+ const snapshotLeader = snapshotLeaderMap.get(row.name);
73
+ const transferRecord = transferMap.get(row.name.toLowerCase());
74
+ const latestDailyPoints = latestDailyRow?.[row.name];
75
+ const latestPoints =
76
+ typeof latestDailyPoints === "number"
77
+ ? latestDailyPoints
78
+ : row.lastMatchPoints;
79
+
80
+ const transfersLeft =
81
+ toNumber(transferRecord?.transfersLeft) ??
82
+ row.transfersLeft ??
83
+ (typeof snapshotLeader?.transfersLeft === "number"
84
+ ? snapshotLeader.transfersLeft
85
+ : undefined);
86
+ const totalTransfers = config.league.totalTransfers;
87
+ const transfersUsed =
88
+ typeof transfersLeft === "number"
89
+ ? totalTransfers - transfersLeft
90
+ : undefined;
91
+ const efficiency =
92
+ typeof transfersUsed === "number" && transfersUsed > 0 && row.points > 0
93
+ ? row.points / transfersUsed
94
+ : undefined;
95
+
96
+ return {
97
+ ...row,
98
+ latestPoints,
99
+ transfersLeft,
100
+ transfersUsed,
101
+ totalTransfers,
102
+ efficiency,
103
+ boostersUsed:
104
+ (typeof transferRecord?.boostersUsed === "number"
105
+ ? String(transferRecord.boostersUsed)
106
+ : undefined) ??
107
+ row.boostersUsed ??
108
+ getSnapshotBoostersUsed(snapshotLeader),
109
+ transferMatchesPlayed: transferRecord?.matchesPlayed,
110
+ };
111
+ });
112
+ };
@@ -0,0 +1,28 @@
1
+ import { matches } from "./matches";
2
+
3
+ export type MatchStatus = "ENDED" | "LIVE" | "SOON" | "UPCOMING";
4
+
5
+ export function getMatchStatus(date: string) {
6
+ const now = new Date().getTime();
7
+ const matchTime = new Date(date).getTime();
8
+
9
+ const diff = matchTime - now;
10
+
11
+ if (diff < -4 * 60 * 60 * 1000) return "ENDED" as const;
12
+ if (diff <= 0) return "LIVE" as const;
13
+ if (diff <= 30 * 60 * 1000) return "SOON" as const;
14
+
15
+ return "UPCOMING" as const;
16
+ }
17
+
18
+ export function getCurrentMatchStatus() {
19
+ const current = matches
20
+ .map((match) => ({
21
+ ...match,
22
+ status: getMatchStatus(match.date),
23
+ }))
24
+ .filter((match) => match.status !== "ENDED")
25
+ .sort((a, b) => +new Date(a.date) - +new Date(b.date))[0];
26
+
27
+ return current?.status ?? "ENDED";
28
+ }
@@ -0,0 +1,131 @@
1
+ type Match = {
2
+ id: number;
3
+ team1: string;
4
+ team2: string;
5
+ date: string;
6
+ };
7
+
8
+ export const matches: Match[] = [
9
+ {
10
+ id: 51,
11
+ team1: "DC",
12
+ team2: "KKR",
13
+ date: "2026-05-08T14:00:00Z",
14
+ },
15
+ {
16
+ id: 52,
17
+ team1: "RR",
18
+ team2: "GT",
19
+ date: "2026-05-09T14:00:00Z",
20
+ },
21
+ {
22
+ id: 53,
23
+ team1: "CSK",
24
+ team2: "LSG",
25
+ date: "2026-05-10T10:00:00Z",
26
+ },
27
+ {
28
+ id: 54,
29
+ team1: "RCB",
30
+ team2: "MI",
31
+ date: "2026-05-10T14:00:00Z",
32
+ },
33
+ {
34
+ id: 55,
35
+ team1: "PBKS",
36
+ team2: "DC",
37
+ date: "2026-05-11T14:00:00Z",
38
+ },
39
+ {
40
+ id: 56,
41
+ team1: "GT",
42
+ team2: "SRH",
43
+ date: "2026-05-12T14:00:00Z",
44
+ },
45
+ {
46
+ id: 57,
47
+ team1: "RCB",
48
+ team2: "KKR",
49
+ date: "2026-05-13T14:00:00Z",
50
+ },
51
+ {
52
+ id: 58,
53
+ team1: "PBKS",
54
+ team2: "MI",
55
+ date: "2026-05-14T14:00:00Z",
56
+ },
57
+ {
58
+ id: 59,
59
+ team1: "LSG",
60
+ team2: "CSK",
61
+ date: "2026-05-15T14:00:00Z",
62
+ },
63
+ {
64
+ id: 60,
65
+ team1: "KKR",
66
+ team2: "GT",
67
+ date: "2026-05-16T14:00:00Z",
68
+ },
69
+ {
70
+ id: 61,
71
+ team1: "PBKS",
72
+ team2: "RCB",
73
+ date: "2026-05-17T10:00:00Z",
74
+ },
75
+ {
76
+ id: 62,
77
+ team1: "DC",
78
+ team2: "RR",
79
+ date: "2026-05-17T14:00:00Z",
80
+ },
81
+ {
82
+ id: 63,
83
+ team1: "CSK",
84
+ team2: "SRH",
85
+ date: "2026-05-18T14:00:00Z",
86
+ },
87
+ {
88
+ id: 64,
89
+ team1: "RR",
90
+ team2: "LSG",
91
+ date: "2026-05-19T14:00:00Z",
92
+ },
93
+ {
94
+ id: 65,
95
+ team1: "KKR",
96
+ team2: "MI",
97
+ date: "2026-05-20T14:00:00Z",
98
+ },
99
+ {
100
+ id: 66,
101
+ team1: "CSK",
102
+ team2: "GT",
103
+ date: "2026-05-21T14:00:00Z",
104
+ },
105
+ {
106
+ id: 67,
107
+ team1: "SRH",
108
+ team2: "RCB",
109
+ date: "2026-05-22T14:00:00Z",
110
+ },
111
+ {
112
+ id: 68,
113
+ team1: "LSG",
114
+ team2: "PBKS",
115
+ date: "2026-05-23T14:00:00Z",
116
+ },
117
+ {
118
+ id: 69,
119
+ team1: "MI",
120
+ team2: "RR",
121
+ date: "2026-05-24T10:00:00Z",
122
+ },
123
+ {
124
+ id: 70,
125
+ team1: "KKR",
126
+ team2: "DC",
127
+ date: "2026-05-24T14:00:00Z",
128
+ },
129
+ ];
130
+
131
+ export type UpcomingMatch = Match;