@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,75 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { ComponentType } from "react";
3
+
4
+ export interface InsightCard {
5
+ id: string;
6
+ title: string;
7
+ note: string;
8
+ accent: string;
9
+ icon: ComponentType<{ className?: string }>;
10
+ }
11
+
12
+ export interface SummaryCard {
13
+ id: string;
14
+ title: string;
15
+ value: string;
16
+ note: string;
17
+ accent: string;
18
+ icon: ComponentType<{ className?: string }>;
19
+ }
20
+
21
+ export interface ChartItem {
22
+ label: string;
23
+ value: number;
24
+ sublabel: string;
25
+ difference?: number;
26
+ fill?: string;
27
+ }
28
+
29
+ export interface DashboardModel {
30
+ leader: any; // From OverallChartItem
31
+ topTeams: any[];
32
+ normalizedOverall: any[];
33
+ teamBadges: any;
34
+ insights: InsightCard[];
35
+ summaryCards: SummaryCard[];
36
+ barLeaderboard: ChartItem[];
37
+ liveBars: ChartItem[];
38
+ topTeamsChart: ChartItem[];
39
+ captainTeams: any[];
40
+ tableRows: any[];
41
+ liveLeader: any;
42
+ liveLabel: string;
43
+ liveChartTitle: string;
44
+ matchCount: number;
45
+ hottest: any;
46
+ }
47
+
48
+ export interface SectionCardProps {
49
+ title: string;
50
+ note: string;
51
+ children: React.ReactNode;
52
+ className?: string;
53
+ index: number;
54
+ icon: ComponentType<{ className?: string }>;
55
+ accent?: string;
56
+ }
57
+
58
+ export interface StickyMiniProps {
59
+ title: string;
60
+ value: string;
61
+ note: string;
62
+ variant: "yellow" | "blue" | "pink";
63
+ icon: ComponentType<{ className?: string }>;
64
+ }
65
+
66
+ export interface ChartBoardProps {
67
+ items: ChartItem[];
68
+ valueLabel: string;
69
+ emptyLabel?: string;
70
+ }
71
+
72
+ export interface LatestBadgeProps {
73
+ pts: number;
74
+ isTop: boolean;
75
+ }
@@ -0,0 +1,130 @@
1
+ export type OverallChartItem = {
2
+ name: string;
3
+ points: number;
4
+ rank: number;
5
+ previousRank?: number;
6
+ previousPoints?: number;
7
+ lastMatchPoints?: number;
8
+ gapToNext?: number;
9
+ gapPercent?: number;
10
+ movement?: "up" | "down" | "same" | "new";
11
+ transfersLeft?: number;
12
+ transfersUsed?: number;
13
+ totalTransfers?: number;
14
+ boostersUsed?: string;
15
+ efficiency?: number;
16
+ isLastMatchLeader?: boolean;
17
+ captain?: ScrapedSquadPlayer | null;
18
+ viceCaptain?: ScrapedSquadPlayer | null;
19
+ players?: ScrapedSquadPlayer[];
20
+ };
21
+
22
+ export type DailyChartRow = {
23
+ day: string;
24
+ [teamName: string]: string | number;
25
+ };
26
+
27
+ export type DashboardData = {
28
+ overall: OverallChartItem[];
29
+ daily: DailyChartRow[];
30
+ updatedAt?: string;
31
+ dailyTransferUpdatedAt?: string;
32
+ completedMatches?: number;
33
+ source?: "database";
34
+ snapshot?: ScrapedDashboardPayload;
35
+ transfers?: ScrapedTransferSnapshot;
36
+ };
37
+
38
+ export type RawApiUser = {
39
+ rno: number;
40
+ temname: string;
41
+ points: number;
42
+ matches: Array<{
43
+ matchId: number;
44
+ points: number;
45
+ }>;
46
+ };
47
+
48
+ export type TeamRecord = {
49
+ id: number;
50
+ name: string;
51
+ owner: string;
52
+ fantasyNames: string[];
53
+ };
54
+
55
+ export type TeamMatchPointsRecord = {
56
+ teamId: number;
57
+ matchId: number;
58
+ points: number;
59
+ };
60
+
61
+ export type LeaderSnapshotRecord = {
62
+ teamId: number;
63
+ rank: number;
64
+ totalPoints: number;
65
+ lastMatchPoints?: number;
66
+ transfersLeft?: number;
67
+ transfersUsed?: number;
68
+ totalTransfers?: number;
69
+ boostersUsed?: string;
70
+ captain?: ScrapedSquadPlayer | null;
71
+ viceCaptain?: ScrapedSquadPlayer | null;
72
+ players?: ScrapedSquadPlayer[];
73
+ capturedAt?: string;
74
+ };
75
+
76
+ export type ScrapedSquadPlayer = {
77
+ name: string;
78
+ number?: string;
79
+ points?: number;
80
+ };
81
+
82
+ export type ScrapedLeaderboardItem = {
83
+ rank: number;
84
+ name: string;
85
+ points: number;
86
+ matchesPlayed?: number;
87
+ lastMatchPoints?: number;
88
+ netPoints?: number;
89
+ netRank?: number;
90
+ transfersLeft?: number;
91
+ transfersUsed?: number;
92
+ totalTransfers?: number;
93
+ boostersUsed?: string;
94
+ captain?: ScrapedSquadPlayer | null;
95
+ viceCaptain?: ScrapedSquadPlayer | null;
96
+ players?: ScrapedSquadPlayer[];
97
+ };
98
+
99
+ export type ScrapedDashboardPayload = {
100
+ updatedAt?: string;
101
+ dailyTransferUpdatedAt?: string;
102
+ completedMatches?: number;
103
+ leaders: ScrapedLeaderboardItem[];
104
+ };
105
+
106
+ export type ScrapedTransferItem = {
107
+ team: string;
108
+ matchesPlayed: number;
109
+ boostersUsed: number;
110
+ transfersLeft: string;
111
+ updatedAt: string;
112
+ };
113
+
114
+ export type ScrapedTransferSnapshot = {
115
+ updatedAt?: string;
116
+ teams: ScrapedTransferItem[];
117
+ };
118
+
119
+ export type RawOverviewUser = {
120
+ name: string;
121
+ totalPoints: number;
122
+ };
123
+
124
+ export type RawDailyUser = {
125
+ name: string;
126
+ daily: Array<{
127
+ day: string;
128
+ points: number;
129
+ }>;
130
+ };
@@ -0,0 +1,72 @@
1
+ import { getChartColor } from "../../lib/utils/getChartColor";
2
+
3
+ export function formatCompactNumber(value: number): string {
4
+ if (value >= 1000000) {
5
+ return `${(value / 1000000).toFixed(1)}M`;
6
+ }
7
+ if (value >= 1000) {
8
+ return `${(value / 1000).toFixed(1)}K`;
9
+ }
10
+ return value.toString();
11
+ }
12
+
13
+ export function formatLedgerNumber(value?: number): string {
14
+ if (typeof value !== "number" || !Number.isFinite(value)) {
15
+ return "-";
16
+ }
17
+
18
+ return value.toLocaleString("en-IN", {
19
+ maximumFractionDigits: value % 1 === 0 ? 0 : 1,
20
+ });
21
+ }
22
+
23
+ export function formatDateTime(value?: string | null): string {
24
+ if (!value) {
25
+ return "just now";
26
+ }
27
+
28
+ const date = new Date(value);
29
+ if (Number.isNaN(date.getTime())) {
30
+ return "just now";
31
+ }
32
+
33
+ return date.toLocaleString("en-IN", {
34
+ day: "2-digit",
35
+ month: "short",
36
+ hour: "2-digit",
37
+ minute: "2-digit",
38
+ });
39
+ }
40
+
41
+ export function getBoosterCount(value?: string): number {
42
+ if (!value) {
43
+ return 0;
44
+ }
45
+
46
+ const numeric = Number(value.trim());
47
+ return Number.isFinite(numeric) && numeric > 0 ? numeric : 0;
48
+ }
49
+
50
+ export function getTeamColor(index: number, total: number): string {
51
+ return getChartColor(index, total);
52
+ }
53
+
54
+ export function createChartData(
55
+ items: Array<{
56
+ label: string;
57
+ value: number;
58
+ sublabel: string;
59
+ difference?: number;
60
+ }>,
61
+ ): Array<{
62
+ label: string;
63
+ value: number;
64
+ sublabel: string;
65
+ difference?: number;
66
+ fill: string;
67
+ }> {
68
+ return items.map((item, index) => ({
69
+ ...item,
70
+ fill: getChartColor(index, items.length),
71
+ }));
72
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,68 @@
1
+ # Cloud Run Scraper Job
2
+
3
+ This folder contains the deployment scaffold for moving the Playwright sync off local machines and scheduled GitHub Actions.
4
+
5
+ ## Files
6
+
7
+ - `sync-job.yaml`: example Cloud Run Job manifest
8
+
9
+ The container image is built from the repo root `Dockerfile.sync`.
10
+
11
+ ## Required secrets
12
+
13
+ Create these in Google Secret Manager before deploying:
14
+
15
+ - `IPL_POST_SECRET`
16
+ - `IPL_STORAGE_STATE_B64`
17
+
18
+ `IPL_STORAGE_STATE_B64` should contain the base64-encoded contents of `ipl-auth.json`.
19
+
20
+ ## Required env vars
21
+
22
+ - `IPL_API_BASE_URL`
23
+ - `IPL_LEAGUE_URL`
24
+ - `IPL_HEADLESS=1`
25
+
26
+ ## Deploy
27
+
28
+ From the repo root:
29
+
30
+ ```bash
31
+ PROJECT_ID=your-gcp-project \
32
+ REGION=asia-south1 \
33
+ APP_BASE_URL=https://your-app-domain \
34
+ bash scripts/deploy-cloud-run-sync.sh
35
+ ```
36
+
37
+ ## Schedule
38
+
39
+ After the job exists, attach Cloud Scheduler to it on a 5-10 minute cadence:
40
+
41
+ ```bash
42
+ PROJECT_ID=your-gcp-project \
43
+ REGION=asia-south1 \
44
+ SERVICE_ACCOUNT_EMAIL=your-scheduler-sa@your-gcp-project.iam.gserviceaccount.com \
45
+ bash scripts/deploy-cloud-scheduler.sh
46
+ ```
47
+
48
+ ## Notes
49
+
50
+ - The web app should already be deployed with `MONGODB_URI` and `IPL_POST_SECRET`.
51
+ - The scraper job is intentionally separate from the Next.js runtime so browser automation does not compete with web traffic.
52
+ - Run `APP_BASE_URL=https://your-app-domain npm run verify:production` before wiring the scheduler.
53
+
54
+ ## Alerting hook
55
+
56
+ Use `/api/ops/status` as the primary freshness endpoint.
57
+
58
+ Suggested alert behavior:
59
+
60
+ - alert immediately if it returns `503`
61
+ - alert if Cloud Run job executions start failing repeatedly
62
+ - alert if Cloud Scheduler stops invoking the job on schedule
63
+
64
+ Quick manual check:
65
+
66
+ ```bash
67
+ APP_BASE_URL=https://your-app-domain npm run monitor:ops
68
+ ```
@@ -0,0 +1,32 @@
1
+ apiVersion: run.googleapis.com/v1
2
+ kind: Job
3
+ metadata:
4
+ name: ipl-sync
5
+ spec:
6
+ template:
7
+ spec:
8
+ template:
9
+ spec:
10
+ timeoutSeconds: 1200
11
+ maxRetries: 1
12
+ containers:
13
+ - image: REGION-docker.pkg.dev/PROJECT_ID/ipl-dashboard/ipl-sync:latest
14
+ env:
15
+ - name: NODE_ENV
16
+ value: production
17
+ - name: IPL_HEADLESS
18
+ value: "1"
19
+ - name: IPL_LEAGUE_URL
20
+ value: https://fantasy.iplt20.com/classic/league/view/66930102
21
+ - name: IPL_API_BASE_URL
22
+ value: https://YOUR_APP_DOMAIN
23
+ - name: IPL_POST_SECRET
24
+ valueFrom:
25
+ secretKeyRef:
26
+ name: IPL_POST_SECRET
27
+ key: latest
28
+ - name: IPL_STORAGE_STATE_B64
29
+ valueFrom:
30
+ secretKeyRef:
31
+ name: IPL_STORAGE_STATE_B64
32
+ key: latest
@@ -0,0 +1,84 @@
1
+ # Production Cutover Checklist
2
+
3
+ Use this checklist when moving from local/dev operation to the cloud-first production setup.
4
+
5
+ ## 1. Prepare secrets
6
+
7
+ - Create `MONGODB_URI` for the Vercel project
8
+ - Create `IPL_POST_SECRET` for both Vercel and the scraper job
9
+ - Create `IPL_STORAGE_STATE_B64` in Google Secret Manager
10
+
11
+ ## 2. Deploy the web app
12
+
13
+ - Import the repo into Vercel
14
+ - Set production env vars
15
+ - Deploy the app
16
+ - Attach the final custom domain
17
+
18
+ Reference:
19
+
20
+ - `infra/vercel/README.md`
21
+
22
+ ## 3. Verify the deployed app
23
+
24
+ Run:
25
+
26
+ ```bash
27
+ APP_BASE_URL=https://your-app-domain npm run verify:production
28
+ ```
29
+
30
+ Expected:
31
+
32
+ - `/api/ops/status` returns JSON
33
+ - `/api/ipl` returns dashboard data or a structured error
34
+ - `/api/ipl?format=snapshot` returns snapshot data or a structured error
35
+ - `/api/ipl/transfers` returns transfer data or a structured error
36
+
37
+ ## 4. Deploy the scraper job
38
+
39
+ Run:
40
+
41
+ ```bash
42
+ PROJECT_ID=your-gcp-project \
43
+ REGION=asia-south1 \
44
+ APP_BASE_URL=https://your-app-domain \
45
+ bash scripts/deploy-cloud-run-sync.sh
46
+ ```
47
+
48
+ Reference:
49
+
50
+ - `infra/cloud-run/README.md`
51
+
52
+ ## 5. Attach the scheduler
53
+
54
+ Run:
55
+
56
+ ```bash
57
+ PROJECT_ID=your-gcp-project \
58
+ REGION=asia-south1 \
59
+ SERVICE_ACCOUNT_EMAIL=your-scheduler-sa@your-gcp-project.iam.gserviceaccount.com \
60
+ bash scripts/deploy-cloud-scheduler.sh
61
+ ```
62
+
63
+ ## 6. Run one manual sync
64
+
65
+ Trigger the Cloud Run job once manually and confirm:
66
+
67
+ - `/api/ops/status` shows Mongo connected
68
+ - `/api/ipl?format=snapshot` returns leaders
69
+ - `/api/ipl/transfers` returns teams
70
+
71
+ ## 7. Disable legacy paths
72
+
73
+ - Keep GitHub Actions sync as manual fallback only
74
+ - Do not rely on local file persistence in production
75
+ - Keep `IPL_WRITE_SEED_DATA_FILE` unset in production
76
+
77
+ ## 8. Monitor after cutover
78
+
79
+ Watch:
80
+
81
+ - Vercel function logs
82
+ - Cloud Run job executions
83
+ - Cloud Scheduler execution history
84
+ - MongoDB Atlas metrics
@@ -0,0 +1,57 @@
1
+ # Vercel Deployment
2
+
3
+ This app is intended to run on Vercel as the web tier and MongoDB Atlas as the live datastore.
4
+
5
+ ## Recommended production shape
6
+
7
+ - Vercel hosts the Next.js app
8
+ - MongoDB Atlas stores dashboard, raw-user, and transfer data
9
+ - Cloud Run Job runs the Playwright scraper
10
+ - Cloud Scheduler triggers the scraper job
11
+
12
+ ## Required Vercel environment variables
13
+
14
+ Set these in the Vercel project for the `Production` environment:
15
+
16
+ - `MONGODB_URI`
17
+ - `IPL_POST_SECRET`
18
+ - `IPL_API_LOG=0`
19
+ - `IPL_API_LOG_PAYLOAD=0`
20
+ - `IPL_DASHBOARD_STALE_MINUTES=20`
21
+ - `IPL_TRANSFERS_STALE_MINUTES=720`
22
+
23
+ Optional:
24
+
25
+ - `IPL_WRITE_SEED_DATA_FILE=0`
26
+
27
+ ## Deployment checklist
28
+
29
+ 1. Import the Git repository into Vercel.
30
+ 2. Confirm the framework preset is `Next.js`.
31
+ 3. Set the root directory to the repository root.
32
+ 4. Add the production environment variables listed above.
33
+ 5. Deploy once to create the initial project URL.
34
+ 6. Add your custom domain.
35
+ 7. Verify `GET /api/ops/status` on the deployed domain.
36
+ 8. Point the Cloud Run Job to the final app base URL.
37
+
38
+ ## Post-deploy verification
39
+
40
+ Run:
41
+
42
+ ```bash
43
+ APP_BASE_URL=https://your-app-domain npm run verify:production
44
+ ```
45
+
46
+ Expected production behavior:
47
+
48
+ - live snapshot writes require MongoDB
49
+ - local snapshot files are ignored
50
+ - the app remains stateless at the web tier
51
+ - `/api/ops/status` returns `503` when dashboard freshness is critically stale
52
+
53
+ ## Notes
54
+
55
+ - Preview deployments can use a separate MongoDB database if you want realistic previews.
56
+ - Keep `IPL_POST_SECRET` identical between Vercel and the scraper job.
57
+ - The repo root `vercel.json` keeps the deployment contract explicit for the project.
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;