From 62c995a9f3d369f9e6391f39c9ffdca1d57229f5 Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Sun, 7 Dec 2025 17:25:17 +0200 Subject: [PATCH 1/8] close to end added alot of things including css and html waiting for cr --- .vscode/tasks.json | 2 +- apps/playoff-schedule/backend/Dockerfile | 5 + apps/playoff-schedule/backend/build.ts | 37 + .../backend/docker-compose.yml | 11 + apps/playoff-schedule/backend/package.json | 20 + apps/playoff-schedule/backend/src/main.ts | 65 + .../backend/src/routes/index.ts | 9 + apps/playoff-schedule/backend/turbo.json | 12 + apps/playoff-schedule/frontend/Dockerfile | 7 + .../frontend/docker-compose.yml | 11 + apps/playoff-schedule/frontend/index.html | 13 + apps/playoff-schedule/frontend/package.json | 32 + apps/playoff-schedule/frontend/src/App.tsx | 373 ++++ .../frontend/src/assets/greenblitz.svg | 38 + .../frontend/src/endpoints/EventsSimple.tsx | 27 + .../frontend/src/endpoints/MatchSimple.tsx | 27 + .../frontend/src/endpoints/SimpleAlliance.tsx | 9 + .../frontend/src/endpoints/TeamsSimple.tsx | 16 + .../src/endpoints/unUsed/EventsFull.tsx | 39 + apps/playoff-schedule/frontend/src/index.css | 100 + apps/playoff-schedule/frontend/src/main.tsx | 11 + .../frontend/src/services/CreateMatch.tsx | 19 + .../frontend/src/utils/TypeUtils.tsx | 3 + .../frontend/src/utils/matchUtils.tsx | 25 + apps/playoff-schedule/frontend/start.ts | 24 + .../frontend/tsconfig.app.json | 28 + apps/playoff-schedule/frontend/tsconfig.json | 7 + .../frontend/tsconfig.node.json | 26 + apps/playoff-schedule/frontend/turbo.json | 12 + apps/playoff-schedule/frontend/vite.config.ts | 18 + apps/template/docker-compose.yml | 12 - package-lock.json | 1830 ++++++++--------- package.json | 3 + 33 files changed, 1880 insertions(+), 991 deletions(-) create mode 100644 apps/playoff-schedule/backend/Dockerfile create mode 100644 apps/playoff-schedule/backend/build.ts create mode 100644 apps/playoff-schedule/backend/docker-compose.yml create mode 100644 apps/playoff-schedule/backend/package.json create mode 100644 apps/playoff-schedule/backend/src/main.ts create mode 100644 apps/playoff-schedule/backend/src/routes/index.ts create mode 100644 apps/playoff-schedule/backend/turbo.json create mode 100644 apps/playoff-schedule/frontend/Dockerfile create mode 100644 apps/playoff-schedule/frontend/docker-compose.yml create mode 100644 apps/playoff-schedule/frontend/index.html create mode 100644 apps/playoff-schedule/frontend/package.json create mode 100644 apps/playoff-schedule/frontend/src/App.tsx create mode 100644 apps/playoff-schedule/frontend/src/assets/greenblitz.svg create mode 100644 apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx create mode 100644 apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx create mode 100644 apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx create mode 100644 apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx create mode 100644 apps/playoff-schedule/frontend/src/endpoints/unUsed/EventsFull.tsx create mode 100644 apps/playoff-schedule/frontend/src/index.css create mode 100644 apps/playoff-schedule/frontend/src/main.tsx create mode 100644 apps/playoff-schedule/frontend/src/services/CreateMatch.tsx create mode 100644 apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx create mode 100644 apps/playoff-schedule/frontend/src/utils/matchUtils.tsx create mode 100644 apps/playoff-schedule/frontend/start.ts create mode 100644 apps/playoff-schedule/frontend/tsconfig.app.json create mode 100644 apps/playoff-schedule/frontend/tsconfig.json create mode 100644 apps/playoff-schedule/frontend/tsconfig.node.json create mode 100644 apps/playoff-schedule/frontend/turbo.json create mode 100644 apps/playoff-schedule/frontend/vite.config.ts delete mode 100644 apps/template/docker-compose.yml diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2354f2e..fb32ad6 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -42,7 +42,7 @@ "id": "project", "description": "Full Stack Projects worth running", "type": "pickString", - "options": ["template", "test"] + "options": ["template", "test","playoff-schedule"] }, { "id": "workspace", diff --git a/apps/playoff-schedule/backend/Dockerfile b/apps/playoff-schedule/backend/Dockerfile new file mode 100644 index 0000000..7f398e6 --- /dev/null +++ b/apps/playoff-schedule/backend/Dockerfile @@ -0,0 +1,5 @@ +FROM node:20-alpine +WORKDIR /usr/src/app +COPY ./dist /usr/src/app +EXPOSE 4590 +CMD ["node","bundle.js"] \ No newline at end of file diff --git a/apps/playoff-schedule/backend/build.ts b/apps/playoff-schedule/backend/build.ts new file mode 100644 index 0000000..3802a64 --- /dev/null +++ b/apps/playoff-schedule/backend/build.ts @@ -0,0 +1,37 @@ +// בס"ד +import { build, context } from "esbuild"; +import { spawn } from "child_process"; + +const isDev = process.env.NODE_ENV === "DEV"; + +const bundlePath = "dist/bundle.js"; + +const buildSettings = { + entryPoints: ["src/main.ts"], + outfile: "dist/bundle.js", + bundle: true, + plugins: [], + minify: true, + platform: "node", + target: ["ES2022"], + format: "cjs", + external: ["@repo/config-env"], +} satisfies Parameters[0]; + +const buildDev = async () => + context(buildSettings) + .then(async (ctx) => ctx.watch()) + .then(() => { + console.log("Starting nodemon to manage execution of bundle.js"); + spawn( + "nodemon", + [bundlePath, "--watch", bundlePath, "--ext", "js", "--exec", "node"], + { stdio: "inherit", shell: true } + ); + }); + +const buildedProject = isDev ? buildDev() : build(buildSettings); + +buildedProject.catch((error: unknown) => { + console.warn(error); +}); diff --git a/apps/playoff-schedule/backend/docker-compose.yml b/apps/playoff-schedule/backend/docker-compose.yml new file mode 100644 index 0000000..dc82686 --- /dev/null +++ b/apps/playoff-schedule/backend/docker-compose.yml @@ -0,0 +1,11 @@ +services: + backend: + build: + context: . + dockerfile: Dockerfile + + env_file: + - ../../../.public.env + - ../../../.secret.env + ports: + - "${BACKEND_PORT}:4590" # Maps host:${FRONTEND_PORT} to container:4590 diff --git a/apps/playoff-schedule/backend/package.json b/apps/playoff-schedule/backend/package.json new file mode 100644 index 0000000..6d29169 --- /dev/null +++ b/apps/playoff-schedule/backend/package.json @@ -0,0 +1,20 @@ +{ + "name": "playoff-schedule-backend", + "version": "1.0.0", + "description": "Backend for the application", + "main": "index.js", + "scripts": { + "test": "echo Backend Test Succeeded && exit 0", + "build": "tsx build.ts", + "serve": "node dist/bundle.js", + "dev": "tsx build.ts" + }, + "author": "", + "license": "ISC", + "dependencies": { + "process": "^0.11.10" + }, + "devDependencies": { + "@types/node": "^24.8.1" + } +} diff --git a/apps/playoff-schedule/backend/src/main.ts b/apps/playoff-schedule/backend/src/main.ts new file mode 100644 index 0000000..875ab7e --- /dev/null +++ b/apps/playoff-schedule/backend/src/main.ts @@ -0,0 +1,65 @@ +// בס"ד +import express from "express"; +import { apiRouter } from "./routes"; +import cors from "cors"; + +const app = express(); +app.use(cors({ + origin: "*" +})); +app.use("/api/v1", apiRouter); + +const defaultPort = 4590; +const port = process.env.BACKEND_PORT ?? defaultPort; + +app.use(express.json({ limit: "10mb" })); +app.use(express.urlencoded({ limit: "10mb", extended: true })); +const statusBadServer = 500; +const statusGood = 200; +const statusBadUser = 400; + +// enter a valid api key +const apiKey = "YOUR_API_KEY"; + + + +export const fetchData = async (url: string): Promise => { + const response = await fetch(url, { + method: "GET", + headers: { + "X-TBA-Auth-Key": apiKey, + "Content-Type": "application/json", + }, + mode: "cors", + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + console.log("TBA data:", data); + return data; + +}; + + +app.get("/fetch", async (req, res) => { + try { + const encodedUrl = req.query.url; + if (!encodedUrl || typeof encodedUrl !== "string") { + res.status(statusBadUser).json({ error: "missing url param" }); + return; + } + const fullUrl = decodeURIComponent(encodedUrl); + console.log("Incoming /fetch with url:", fullUrl); + + const data = await fetchData(fullUrl); + res.status(statusGood).json(data); + } catch (error) { + console.error("Error in /fetch:", error); + res.status(statusBadServer).json({ error: "Failed to fetch data" }); + } +}); + +app.listen(port, () => { + console.log(`Production server running at http://localhost:${port}`); +}); \ No newline at end of file diff --git a/apps/playoff-schedule/backend/src/routes/index.ts b/apps/playoff-schedule/backend/src/routes/index.ts new file mode 100644 index 0000000..b62ad50 --- /dev/null +++ b/apps/playoff-schedule/backend/src/routes/index.ts @@ -0,0 +1,9 @@ +// בס"ד +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; + +export const apiRouter = Router(); + +apiRouter.get("/health", (req, res) => { + res.status(StatusCodes.OK).send({ message: "Healthy!" }); +}); \ No newline at end of file diff --git a/apps/playoff-schedule/backend/turbo.json b/apps/playoff-schedule/backend/turbo.json new file mode 100644 index 0000000..52e8c76 --- /dev/null +++ b/apps/playoff-schedule/backend/turbo.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "//" + ], + "tasks": { + "build": { + "outputs": [ + "dist/**" + ] + } + } +} diff --git a/apps/playoff-schedule/frontend/Dockerfile b/apps/playoff-schedule/frontend/Dockerfile new file mode 100644 index 0000000..d625b35 --- /dev/null +++ b/apps/playoff-schedule/frontend/Dockerfile @@ -0,0 +1,7 @@ +FROM node:20-alpine +WORKDIR /app +COPY ./start.ts /app/start.ts +COPY ./dist /app/dist +EXPOSE 443 +RUN ["npm","install","express"] +CMD ["npm","exec","--","tsx","start.ts"] \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/docker-compose.yml b/apps/playoff-schedule/frontend/docker-compose.yml new file mode 100644 index 0000000..a27c44f --- /dev/null +++ b/apps/playoff-schedule/frontend/docker-compose.yml @@ -0,0 +1,11 @@ +services: + frontend: + build: + context: . + dockerfile: Dockerfile + + env_file: + - ../../../.public.env + - ../../../.secret.env + ports: + - "${FRONTEND_PORT}:443" # Maps host:${FRONTEND_PORT} to container:443 diff --git a/apps/playoff-schedule/frontend/index.html b/apps/playoff-schedule/frontend/index.html new file mode 100644 index 0000000..de79403 --- /dev/null +++ b/apps/playoff-schedule/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + GreenBlitz 4590 + + +
+ + + diff --git a/apps/playoff-schedule/frontend/package.json b/apps/playoff-schedule/frontend/package.json new file mode 100644 index 0000000..f9c8ee5 --- /dev/null +++ b/apps/playoff-schedule/frontend/package.json @@ -0,0 +1,32 @@ +{ + "name": "playoff-schedule-frontend", + "version": "1.0.0", + "description": "Frontend for the application", + "main": "index.js", + "scripts": { + "test": "echo Frontend Test Succeeded && exit 0", + "dev": "vite", + "build": "tsc -b && vite build", + "serve": "tsx start.ts", + "lint": "eslint .", + "preview": "vite preview" + }, + "author": "", + "license": "ISC", + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1", + "tailwindcss": "^4.1.16", + "@tailwindcss/vite": "^4.1.16" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/node": "^24.6.0", + "@types/react": "^19.1.16", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.4", + "babel-plugin-react-compiler": "^19.1.0-rc.3", + "globals": "^16.4.0", + "vite": "^7.1.7" + } +} diff --git a/apps/playoff-schedule/frontend/src/App.tsx b/apps/playoff-schedule/frontend/src/App.tsx new file mode 100644 index 0000000..a1053d4 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/App.tsx @@ -0,0 +1,373 @@ +// בס"ד + + +import type React from "react"; +import { useEffect, useState, useMemo, useCallback } from "react"; +import { urlMatches, type MatchesSimpleType } from "./endpoints/MatchSimple"; +import { urlTeamsInEvent, type TeamsInEventType } from "./endpoints/TeamsSimple"; + +const targetTeamNumber = 4590; +const targetTeamKey = `frc${targetTeamNumber}`; +const backendPort = 4590; +const refreshIntervalMs = 30000; +const timeMultiplier = 1000; +const sliceStart = 3; +const firstIndex = 0; +const nextMatchLimit = 2; +const noGap = 0; +const matchTime = 0; +const dayInSeconds = 86400; +const backendBaseUrl = `http://localhost:${backendPort}/fetch?url=`; + +const colorRedBg = "#FFEBEE"; +const colorRedBorder = "#EF5350"; +const colorRedText = "#C62828"; +const colorBlueBg = "#E3F2FD"; +const colorBlueBorder = "#42A5F5"; +const colorBlueText = "#1565C0"; +const colorNeutralBg = "#f5f5f5"; +const colorCardBg = "#ffffff"; +const colorHeaderBg = "#263238"; +const colorTextMain = "#333333"; +const colorTextSub = "#666666"; +const colorAccent = "#FFA000"; +const colorFuture = "#2196F3"; +const colorButtonBg = "#00E676"; +const colorButtonText = "#1B5E20"; +const colorRankBg = "#E8EAF6"; +const colorRankText = "#3F51B5"; + +interface TeamRecord { + wins: number; + losses: number; + ties: number; +} + +interface RankItem { + rank: number; + team_key: string; + record: TeamRecord; +} + +interface RankingsResponse { + rankings: RankItem[]; +} + +const urlRankings = (eventKey: string) => + `https://www.thebluealliance.com/api/v3/event/${eventKey}/rankings`; + +const fetchFromProxy = async (targetUrl: string): Promise => { + const fullUrl = `${backendBaseUrl}${encodeURIComponent(targetUrl)}`; + const response = await fetch(fullUrl); + if (!response.ok) { + throw new Error(`HTTP error status: ${response.status}`); + } + return response.json() as Promise; +}; + +const App: React.FC = () => { + const [inputEventKey, setInputEventKey] = useState(""); + const [activeEventKey, setActiveEventKey] = useState(""); + const [teams, setTeams] = useState([]); + const [allMatches, setAllMatches] = useState([]); + const [teamRank, setTeamRank] = useState(null); + const [searchStatus, setSearchStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); + + const performSearch = useCallback(async (eventKey: string) => { + if (!eventKey) return; + + setSearchStatus("loading"); + setActiveEventKey(""); + setAllMatches([]); + setTeams([]); + setTeamRank(null); + + try { + const teamsUrl = urlTeamsInEvent(eventKey); + const matchesUrl = urlMatches(eventKey); + const rankingsUrl = urlRankings(eventKey); + + const [teamsData, matchesData, rankingsData] = await Promise.all([ + fetchFromProxy(teamsUrl), + fetchFromProxy(matchesUrl), + fetchFromProxy(rankingsUrl).catch(() => ({ rankings: [] })) + ]); + + if (Array.isArray(teamsData) && Array.isArray(matchesData)) { + setTeams(teamsData); + matchesData.sort((a, b) => (a.predicted_time ?? a.time ?? noGap) - (b.predicted_time ?? b.time ?? noGap)); + setAllMatches(matchesData); + + const myRank = rankingsData.rankings.find(r => r.team_key === targetTeamKey) ?? null; + setTeamRank(myRank); + + setActiveEventKey(eventKey); + setSearchStatus("success"); + } else { + setSearchStatus("error"); + } + } catch { + setSearchStatus("error"); + } + }, []); + + const handleSearchSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const formattedKey = inputEventKey.trim().toLowerCase(); + void performSearch(formattedKey); + }; + + useEffect(() => { + let intervalId: number | undefined = undefined; + + if (searchStatus === "success" && activeEventKey) { + intervalId = window.setInterval(() => { + const matchesUrl = urlMatches(activeEventKey); + void fetchFromProxy(matchesUrl).then(data => { + if (Array.isArray(data)) { + data.sort((a, b) => (a.predicted_time ?? a.time ?? noGap) - (b.predicted_time ?? b.time ?? noGap)); + setAllMatches(data); + } + }).catch(console.error); + }, refreshIntervalMs); + } + + return () => { + if (intervalId !== undefined) { + window.clearInterval(intervalId); + } + }; + }, [activeEventKey, searchStatus]); + + const teamNameMap = useMemo(() => { + const map: Map = new Map(); + teams.forEach(t => map.set(t.key, t.nickname)); + return map; + }, [teams]); + + const { currentGlobalMatch, targetTeamMatches, isEventOver, isTeamDone, isFutureEvent } = useMemo(() => { + +// testing 2025isios +// const currentTimeSecs = Math.floor(new Date("2025-10-08T10:44:00").getTime() / timeMultiplier); +// testing 2025iscmp +// const currentTimeSecs = Math.floor(new Date("2025-03-26T17:00:00").getTime() / timeMultiplier); +// normal one + const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); + + + const getMatchTime = (m: MatchesSimpleType) => m.predicted_time ?? m.time ?? matchTime; + + const futureMatches = allMatches.filter(m => { + const t = getMatchTime(m); + return t > noGap && t > currentTimeSecs; + }); + + const pastMatches = allMatches.filter(m => { + const t = getMatchTime(m); + return t > noGap && t <= currentTimeSecs; + }); + + const currentMatch = futureMatches.length > noGap + ? futureMatches[firstIndex] + : undefined; + + const teamFutureMatches = futureMatches.filter(m => + m.alliances.blue.team_keys.includes(targetTeamKey) || + m.alliances.red.team_keys.includes(targetTeamKey) + ); + + const teamPastMatches = pastMatches.filter(m => + m.alliances.blue.team_keys.includes(targetTeamKey) || + m.alliances.red.team_keys.includes(targetTeamKey) + ); + + const hasEventEnded = futureMatches.length === noGap && allMatches.length > noGap; + const hasTeamDone = !hasEventEnded && teamFutureMatches.length === noGap && teamPastMatches.length > noGap; + + let isItFutureEvent = false; + if (currentMatch) { + const t = getMatchTime(currentMatch); + if (t > currentTimeSecs + dayInSeconds) { + isItFutureEvent = true; + } + } + + return { + currentGlobalMatch: currentMatch, + targetTeamMatches: hasTeamDone ? [] : teamFutureMatches.slice(firstIndex, nextMatchLimit), + isEventOver: hasEventEnded, + isTeamDone: hasTeamDone, + isFutureEvent: isItFutureEvent + }; + }, [allMatches]); + + return ( +
+ +
+
+

FRC Dashboard

+
+ { setInputEventKey(e.target.value); }} + style={{ padding: "12px", fontSize: "16px", flexGrow: 1, border: "none", borderRadius: "6px", outline: "none" }} + /> + +
+ + {searchStatus === "error" && ( +
+ Event not found. +
+ )} +
+
+ +
+ {searchStatus === "success" && ( + <> +
+ Event: {activeEventKey} + Teams: {teams.length} +
+ +
+

+ {isFutureEvent ? "Upcoming Event" : "Current Field Status"} +

+ {currentGlobalMatch !== undefined ? ( +
+ + {currentGlobalMatch.comp_level.toUpperCase()}-{currentGlobalMatch.match_number} + + + {isFutureEvent ? "SCHEDULED" : "IN PROGRESS"} + +
+ ) : ( +

{allMatches.length > noGap ? "Event Concluded" : "No matches scheduled"}

+ )} +
+ +

+ {isEventOver ? `Final Results: Team ${targetTeamNumber}` : `Upcoming: Team ${targetTeamNumber}`} +

+ + {isEventOver ? ( +
+ {teamRank ? ( + <> +
Final Rank
+
#{teamRank.rank}
+
+ Record: {teamRank.record.wins}-{teamRank.record.losses}-{teamRank.record.ties} +
+ + ) : ( +

Rank data not available for this event.

+ )} +
+ ) : ( + <> + {targetTeamMatches.length === noGap && ( +
+ {isTeamDone + ?

Team {targetTeamNumber} has completed all scheduled matches for this event (or for today).

+ :

No upcoming matches found for Team {targetTeamNumber}.

+ } +
+ )} + + {targetTeamMatches.map((match) => { + const effectiveTime = match.predicted_time ?? match.time; + const predictedDate = effectiveTime + ? new Date(effectiveTime * timeMultiplier).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + : "TBD"; + + let matchesAway = noGap; + if (currentGlobalMatch !== undefined && match.comp_level === currentGlobalMatch.comp_level) { + matchesAway = match.match_number - currentGlobalMatch.match_number; + } + + const isRedAlliance = match.alliances.red.team_keys.includes(targetTeamKey); + + return ( +
+
+
+ + {match.comp_level.toUpperCase()} - {match.match_number} + +
+
+
+ {predictedDate} +
+ +
+ {matchesAway <= noGap ? "PLAYING NOW" : `IN ${matchesAway} MATCHES`} +
+
+
+ +
+
+
Red Alliance
+ {match.alliances.red.team_keys.map(key => { + const num = key.slice(sliceStart); + const name = teamNameMap.get(key) ?? "Unknown"; + const isMe = key === targetTeamKey; + return ( +
+ {num} + {name} + {isMe && YOU} +
+ ); + })} +
+ +
+
Blue Alliance
+ {match.alliances.blue.team_keys.map(key => { + const num = key.slice(sliceStart); + const name = teamNameMap.get(key) ?? "Unknown"; + const isMe = key === targetTeamKey; + return ( +
+ {num} + {name} + {isMe && YOU} +
+ ); + })} +
+
+
+ ); + })} + + )} + + )} +
+
+ ); +}; + +export default App; \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/src/assets/greenblitz.svg b/apps/playoff-schedule/frontend/src/assets/greenblitz.svg new file mode 100644 index 0000000..e8eb8c5 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/assets/greenblitz.svg @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx new file mode 100644 index 0000000..c049ee3 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx @@ -0,0 +1,27 @@ +// בס"ד +import * as t from "io-ts"; +import { numberOrNull, stringOrNull } from "../utils/TypeUtils"; + +export const simpleEventsInYear = t.type({ + //did + city: stringOrNull, + country: stringOrNull, + district: t.unknown /*null*/, + end_date: t.string, + event_code: t.string, + event_type: t.number, + key: t.string, + name: t.string, + start_date: t.string, + state_prov: stringOrNull, + year: t.number, +}); +export type EventsInYearType = typeof simpleEventsInYear._A; + +/** + * + * @param year in urlEventsInYear switch to curr year: now.getFullYear() + * @returns + */ +export const urlEventsInYear = (year: number /*or curr time*/) => + `https://www.thebluealliance.com/api/v3/events/${year}/simple`; diff --git a/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx new file mode 100644 index 0000000..b7dc540 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx @@ -0,0 +1,27 @@ +// בס"ד + +import * as t from "io-ts"; +import { simpleAlliance } from "./SimpleAlliance"; +import { numberOrNull } from "../utils/TypeUtils"; + +export const matchSimple = t.type({ + actual_time: numberOrNull, + alliances: t.type({ + blue: simpleAlliance, + red: simpleAlliance, + }), + comp_level: t.string, + event_key: t.string, + key: t.string, + match_number: t.number, + predicted_time: numberOrNull, + set_number: t.number, + time: numberOrNull, + winning_alliance: t.string, +}); + +export type MatchesSimpleType = typeof matchSimple._A; + +export const urlMatches = (eventKey: string) => + `https://www.thebluealliance.com/api/v3/event/${eventKey}/matches/simple`; + diff --git a/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx b/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx new file mode 100644 index 0000000..99af797 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx @@ -0,0 +1,9 @@ +// בס"ד +import * as t from "io-ts"; +export const simpleAlliance = t.type({ + dq_team_keys: t.array(t.string), + score: t.number, + surrogate_team_keys: t.array(t.string), + team_keys: t.array(t.string), +}); +type SimpleAllianceType = typeof simpleAlliance._A; diff --git a/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx new file mode 100644 index 0000000..48984d4 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx @@ -0,0 +1,16 @@ +// בס"ד +import * as t from "io-ts"; +import { stringOrNull } from "../utils/TypeUtils"; + +export const simpleTeamsInEvent = t.type({//did + city: stringOrNull, + country: stringOrNull, + key: t.string, + name: t.string, + nickname: t.string, + state_prov: stringOrNull, + team_number: t.number, +}); +export type TeamsInEventType = typeof simpleTeamsInEvent._A; + +export const urlTeamsInEvent = (eventKey:string)=>`https://www.thebluealliance.com/api/v3/event/${eventKey}/teams/simple`; \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/src/endpoints/unUsed/EventsFull.tsx b/apps/playoff-schedule/frontend/src/endpoints/unUsed/EventsFull.tsx new file mode 100644 index 0000000..adae5af --- /dev/null +++ b/apps/playoff-schedule/frontend/src/endpoints/unUsed/EventsFull.tsx @@ -0,0 +1,39 @@ +// בס"ד +// const eventsInYear = t.type({// if you need this +// address: t.string, +// city: t.string, +// country: t.string, +// district: t.unknown, +// division_keys: t.array(t.string), +// end_date: t.string, +// event_code: t.string, +// event_type: t.number, +// event_type_string: t.string, +// first_event_code: t.string, +// first_event_id: t.unknown, +// gmaps_place_id: t.string, +// gmaps_url: t.string, +// key: t.string, +// lat: t.number, +// lng: t.number, +// location_name: t.string, +// name: t.string, +// parent_event_key: t.unknown, +// playoff_type: t.number, +// playoff_type_string: t.string, +// postal_code: t.string, +// remap_teams: t.unknown, +// short_name: t.string, +// start_date: t.string, +// state_prov: t.string, +// timezone: t.string, +// webcasts: t.array( +// t.type({ +// channel: t.string, +// type: t.string, +// }) +// ), +// website: t.string, +// week: t.number, +// year: t.number, +// }); \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/src/index.css b/apps/playoff-schedule/frontend/src/index.css new file mode 100644 index 0000000..1817850 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/index.css @@ -0,0 +1,100 @@ +@import "tailwindcss"; +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 100%; + margin: 0; + padding: 0; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/apps/playoff-schedule/frontend/src/main.tsx b/apps/playoff-schedule/frontend/src/main.tsx new file mode 100644 index 0000000..e37945f --- /dev/null +++ b/apps/playoff-schedule/frontend/src/main.tsx @@ -0,0 +1,11 @@ +// בס"ד +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/apps/playoff-schedule/frontend/src/services/CreateMatch.tsx b/apps/playoff-schedule/frontend/src/services/CreateMatch.tsx new file mode 100644 index 0000000..74e808b --- /dev/null +++ b/apps/playoff-schedule/frontend/src/services/CreateMatch.tsx @@ -0,0 +1,19 @@ +// בס"ד + +type TranslateValue< + Value extends object, + Key extends keyof Value = keyof Value +> = Value[Key] extends string | number ? Key : never; + +export const endpointResultToDict = ( + values: Value[], + idKey: TranslateValue +) => { + return values.reduce( + (acc, value) => ({ + ...acc, + [value[idKey] as string]: value, + }), + {} + ); +}; diff --git a/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx b/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx new file mode 100644 index 0000000..94de78b --- /dev/null +++ b/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx @@ -0,0 +1,3 @@ +import * as t from "io-ts"; +export const numberOrNull = t.union([t.number, t.null]); //for if type is null +export const stringOrNull = t.union([t.string, t.null]); //for if type is null diff --git a/apps/playoff-schedule/frontend/src/utils/matchUtils.tsx b/apps/playoff-schedule/frontend/src/utils/matchUtils.tsx new file mode 100644 index 0000000..5d29fec --- /dev/null +++ b/apps/playoff-schedule/frontend/src/utils/matchUtils.tsx @@ -0,0 +1,25 @@ +// בס"ד +import type { MatchesSimpleType } from "../endpoints/MatchSimple"; + +const millisecToSec = 1000; +const startIndex = 0; +const endIndex = 2; + +export const findNextMatches = (allMatches: MatchesSimpleType[]): MatchesSimpleType[] => { +const currentTime = Math.floor(Date.now() / millisecToSec); + +const futureMatches = allMatches.filter(match => { + if (match.actual_time !== null && match.actual_time < currentTime) { + return false; +} + if (match.predicted_time === null) { + return false; +} + return true; +}); + + futureMatches.sort((a, b) => { + return a.predicted_time! - b.predicted_time!; +}); +return futureMatches.slice(startIndex, endIndex); +}; \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/start.ts b/apps/playoff-schedule/frontend/start.ts new file mode 100644 index 0000000..a99498b --- /dev/null +++ b/apps/playoff-schedule/frontend/start.ts @@ -0,0 +1,24 @@ +// בס"ד +import express from "express"; +import path from "path"; + +const app = express(); + +const defaultPort = 80; +const securePort = 443; +const port = parseInt(process.env.FRONTEND_PORT ?? defaultPort.toString()); +const protocol = port === securePort ? "https" : "http"; + +const dirname = path.dirname(__filename); +const distDirectory = path.join(dirname, "dist"); +const indexHTML = path.join(distDirectory, "index.html"); + +app.use(express.static(distDirectory)); + +app.get(/^(.*)$/, (req, res) => { + res.sendFile(indexHTML); +}); + +app.listen(port, () => { + console.log(`Production server running at ${protocol}://localhost:${port}`); +}); diff --git a/apps/playoff-schedule/frontend/tsconfig.app.json b/apps/playoff-schedule/frontend/tsconfig.app.json new file mode 100644 index 0000000..a9b5a59 --- /dev/null +++ b/apps/playoff-schedule/frontend/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/apps/playoff-schedule/frontend/tsconfig.json b/apps/playoff-schedule/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/apps/playoff-schedule/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/playoff-schedule/frontend/tsconfig.node.json b/apps/playoff-schedule/frontend/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/apps/playoff-schedule/frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/playoff-schedule/frontend/turbo.json b/apps/playoff-schedule/frontend/turbo.json new file mode 100644 index 0000000..52e8c76 --- /dev/null +++ b/apps/playoff-schedule/frontend/turbo.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "//" + ], + "tasks": { + "build": { + "outputs": [ + "dist/**" + ] + } + } +} diff --git a/apps/playoff-schedule/frontend/vite.config.ts b/apps/playoff-schedule/frontend/vite.config.ts new file mode 100644 index 0000000..37eb678 --- /dev/null +++ b/apps/playoff-schedule/frontend/vite.config.ts @@ -0,0 +1,18 @@ +// בס"ד +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + + +// https://vite.dev/config/ +export default defineConfig({ + server: { port: 80 }, + plugins: [ + react({ + babel: { + plugins: [["babel-plugin-react-compiler"]], + }, + }), + tailwindcss(), + ], +}); diff --git a/apps/template/docker-compose.yml b/apps/template/docker-compose.yml deleted file mode 100644 index 96e13a2..0000000 --- a/apps/template/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -services: - frontend: - build: - context: ./frontend # Looks for Dockerfile in the ./frontend directory - ports: - - "${FRONTEND_PORT}:80" # Maps host:${FRONTEND_PORT} to container:80 - - backend: - build: - context: ./backend # Looks for Dockerfile in the ./backend directory - ports: - - "${BACKEND_PORT}:4590" # Maps host:${BACKEND_PORT} to container:4590 diff --git a/package-lock.json b/package-lock.json index 43dd24c..46bc34a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,15 @@ ], "dependencies": { "@tailwindcss/vite": "^4.1.17", + "cors": "^2.8.5", "dotenv": "^17.2.3", "dotenv-cli": "^11.0.0", "eslint-plugin-bsd": "^1.0.0", "eslint-plugin-react": "^7.37.5", "express": "^5.1.0", "http-status-codes": "^2.3.0", + "io-ts": "^2.2.22", + "react": "^19.2.1", "tailwindcss": "^4.1.17", "tsx": "^4.21.0" }, @@ -39,9 +42,9 @@ "node": ">=18" } }, - "apps/backend": { + "apps/playoff-schedule/backend": { + "name": "playoff-schedule-backend", "version": "1.0.0", - "extraneous": true, "license": "ISC", "dependencies": { "process": "^0.11.10" @@ -50,28 +53,9 @@ "@types/node": "^24.8.1" } }, - "apps/docs": { - "version": "0.1.0", - "extraneous": true, - "dependencies": { - "@repo/ui": "*", - "next": "^16.0.1", - "react": "^19.2.0", - "react-dom": "^19.2.0" - }, - "devDependencies": { - "@repo/eslint-config": "*", - "@repo/typescript-config": "*", - "@types/node": "^22.15.3", - "@types/react": "19.2.2", - "@types/react-dom": "19.2.2", - "eslint": "^9.39.1", - "typescript": "5.9.2" - } - }, - "apps/frontend": { + "apps/playoff-schedule/frontend": { + "name": "playoff-schedule-frontend", "version": "1.0.0", - "extraneous": true, "license": "ISC", "dependencies": { "@tailwindcss/vite": "^4.1.16", @@ -86,15 +70,21 @@ "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", "babel-plugin-react-compiler": "^19.1.0-rc.3", - "eslint": "^9.36.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.22", "globals": "^16.4.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.45.0", "vite": "^7.1.7" } }, + "apps/playoff-schedule/frontend/node_modules/globals": { + "version": "16.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "apps/template/backend": { "name": "template-backend", "version": "1.0.0", @@ -129,8 +119,6 @@ }, "apps/template/frontend/node_modules/globals": { "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { @@ -140,25 +128,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "apps/web": { - "version": "0.1.0", - "extraneous": true, - "dependencies": { - "@repo/ui": "*", - "next": "^16.0.1", - "react": "^19.2.0", - "react-dom": "^19.2.0" - }, - "devDependencies": { - "@repo/eslint-config": "*", - "@repo/typescript-config": "*", - "@types/node": "^22.15.3", - "@types/react": "19.2.2", - "@types/react-dom": "19.2.2", - "eslint": "^9.39.1", - "typescript": "5.9.2" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -215,6 +184,16 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", @@ -249,6 +228,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -483,16 +472,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/@emnapi/runtime": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz", - "integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@epic-web/invariant": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", @@ -501,9 +480,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", - "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", "cpu": [ "ppc64" ], @@ -517,9 +496,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", - "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", "cpu": [ "arm" ], @@ -533,9 +512,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", - "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", "cpu": [ "arm64" ], @@ -549,9 +528,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", - "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", "cpu": [ "x64" ], @@ -565,9 +544,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", - "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", "cpu": [ "arm64" ], @@ -581,9 +560,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", - "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", "cpu": [ "x64" ], @@ -597,9 +576,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", - "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", "cpu": [ "arm64" ], @@ -613,9 +592,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", - "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", "cpu": [ "x64" ], @@ -629,9 +608,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", - "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", "cpu": [ "arm" ], @@ -645,9 +624,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", - "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", "cpu": [ "arm64" ], @@ -661,9 +640,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", - "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", "cpu": [ "ia32" ], @@ -677,9 +656,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", - "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", "cpu": [ "loong64" ], @@ -693,9 +672,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", - "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", "cpu": [ "mips64el" ], @@ -709,9 +688,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", - "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", "cpu": [ "ppc64" ], @@ -725,9 +704,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", - "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", "cpu": [ "riscv64" ], @@ -741,9 +720,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", - "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", "cpu": [ "s390x" ], @@ -757,9 +736,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", - "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", "cpu": [ "x64" ], @@ -773,9 +752,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", - "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", "cpu": [ "arm64" ], @@ -789,9 +768,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", - "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", "cpu": [ "x64" ], @@ -805,9 +784,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", - "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", "cpu": [ "arm64" ], @@ -821,9 +800,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", - "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", "cpu": [ "x64" ], @@ -837,9 +816,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", - "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", "cpu": [ "arm64" ], @@ -853,9 +832,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", - "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", "cpu": [ "x64" ], @@ -869,9 +848,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", - "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", "cpu": [ "arm64" ], @@ -885,9 +864,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", - "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", "cpu": [ "ia32" ], @@ -901,9 +880,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", - "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", "cpu": [ "x64" ], @@ -934,18 +913,6 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -956,47 +923,55 @@ } }, "node_modules/@eslint-react/ast": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-2.3.5.tgz", - "integrity": "sha512-gTnLEdQ82Kcy2Yn8fLe6ks/yQx1kI3OYuWgYNb4D1XSAOYvL1Cj+UIx2/+ew9vMBLMO3NJr90EMPUr0yVOhC7w==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-2.3.12.tgz", + "integrity": "sha512-2wlRvqS4dxleGlL4Gp3Bh5PNb47wnAEa99CsGppzWCFXSPvm3d/bM5nJPvOwQOF53+PGa6xq1ZqwGh70zL7+zw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/eff": "2.3.5", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/typescript-estree": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", - "string-ts": "^2.2.1" + "@eslint-react/eff": "2.3.12", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/typescript-estree": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", + "string-ts": "^2.3.1" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/core": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-2.3.5.tgz", - "integrity": "sha512-6+/3bMmkxIk4vlMwfxw4lU6y7/Z1cjGURPsooAULitbBS4+s0M0N1UjWaPpDwT4FR0SVVqjOp1yUcI66uQvQKg==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-2.3.12.tgz", + "integrity": "sha512-Q3w6f0WfVyIJriJa+tYHS4rmVQ3nwnubCH7o/VYlBCR3qczpvpvkCi2XK4clU/7vpVwHbbaXGICAbJu7tNZqoQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@eslint-react/var": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", + "@eslint-react/ast": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "birecord": "^0.1.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/eff": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-2.3.5.tgz", - "integrity": "sha512-F2bj6v7Q1hgLn+N28pkJyYvBiTaUFh0qOEz3IXUupkqqnu9zGxmh3P7c0l//8AlR2CvRTCmSVBBhem4BhoSczw==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-2.3.12.tgz", + "integrity": "sha512-QjFENG1VGVrD67YFc0yiLm9zef2kTeXZGux4hlMjGLnxTHnn0tPx4T/xGzh5C1WRmolcNeIzjVWMqSngFrTphQ==", "dev": true, "license": "MIT", "engines": { @@ -1004,65 +979,73 @@ } }, "node_modules/@eslint-react/eslint-plugin": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@eslint-react/eslint-plugin/-/eslint-plugin-2.3.5.tgz", - "integrity": "sha512-5VTcKcbyDNGrpXj3y5wfYKogA8g1aVPcyupSL9/URyxLhnv14tfSNAJ64qTh0NBunETU69n7T81e4ZYJS2ctGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/type-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", - "eslint-plugin-react-dom": "2.3.5", - "eslint-plugin-react-hooks-extra": "2.3.5", - "eslint-plugin-react-naming-convention": "2.3.5", - "eslint-plugin-react-web-api": "2.3.5", - "eslint-plugin-react-x": "2.3.5", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/eslint-plugin/-/eslint-plugin-2.3.12.tgz", + "integrity": "sha512-/nrnrKINpsUEWIDfy7/YT4oGMvtyMUAJy6gm1nk3YLBrW9v6SVofcOnw2k6xwmB9Zl7RExlL58amlkdRpenkzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/type-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", + "eslint-plugin-react-dom": "2.3.12", + "eslint-plugin-react-hooks-extra": "2.3.12", + "eslint-plugin-react-naming-convention": "2.3.12", + "eslint-plugin-react-web-api": "2.3.12", + "eslint-plugin-react-x": "2.3.12", "ts-api-utils": "^2.1.0" }, "engines": { "node": ">=20.19.0" }, "peerDependencies": { - "eslint": "^9.39.1", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/shared": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-2.3.5.tgz", - "integrity": "sha512-k65W/X2MeiDX21HPwtcPaFHciYVRYrzE+EZ2ok2BVQWcl24GQUEckAfdMzKQ6cS19OgjQm9k0juHjpUcyHj29g==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-2.3.12.tgz", + "integrity": "sha512-mIgxjEwKOknJabbQs/bxvkEhKitJnET0QDc0a89pFx36DBLJIEvdcGMCDEXFgtgjDV/WwMxIava/+coE6T3Dyw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/eff": "2.3.5", - "@typescript-eslint/utils": "^8.46.4", + "@eslint-react/eff": "2.3.12", + "@typescript-eslint/utils": "^8.48.1", "ts-pattern": "^5.9.0", - "zod": "^4.1.12" + "zod": "^4.1.13" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/var": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-2.3.5.tgz", - "integrity": "sha512-BDq9o4kUu4h0Lvv29AY+N9LFh69tgICRNDmr5GnRmRFaYZ6/fq+UbO18K47ccb2tj2TI8V6VJFpkPx1fK7lYeQ==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-2.3.12.tgz", + "integrity": "sha512-jjgeRcop74NTzWzCF8rBN1H5avdSDLEOALJjwmYWOdxoSUNGO7OIeM/pZvHZ7G36kHDuD619P2JauCVM2/c+7A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", + "@eslint-react/ast": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint/config-array": { @@ -1080,6 +1063,30 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/config-helpers": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", @@ -1107,9 +1114,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "license": "MIT", "peer": true, "dependencies": { @@ -1119,7 +1126,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -1130,6 +1137,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "9.39.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", @@ -1263,44 +1294,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@repo/better_child_process": { "resolved": "packages/better_child_process", "link": true @@ -1313,9 +1306,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", - "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -1327,9 +1320,9 @@ "peer": true }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", - "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -1341,9 +1334,9 @@ "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", - "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -1355,9 +1348,9 @@ "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", - "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -1369,9 +1362,9 @@ "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", - "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -1383,9 +1376,9 @@ "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", - "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -1397,9 +1390,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", - "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -1411,9 +1404,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", - "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -1425,9 +1418,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", - "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -1439,9 +1432,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", - "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -1453,9 +1446,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", - "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -1467,9 +1460,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", - "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -1481,9 +1474,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", - "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", "cpu": [ "riscv64" ], @@ -1495,9 +1488,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", - "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -1509,9 +1502,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", - "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -1523,9 +1516,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", - "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -1537,9 +1530,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", - "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -1551,9 +1544,9 @@ "peer": true }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", - "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", "cpu": [ "arm64" ], @@ -1565,9 +1558,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", - "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -1579,9 +1572,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", - "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -1593,9 +1586,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", - "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", "cpu": [ "x64" ], @@ -1607,9 +1600,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", - "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", "cpu": [ "x64" ], @@ -1960,15 +1953,15 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", - "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^1" + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { @@ -1998,13 +1991,6 @@ "license": "MIT", "peer": true }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", @@ -2015,13 +2001,6 @@ "undici-types": "~7.16.0" } }, - "node_modules/@types/node/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -2037,19 +2016,19 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", - "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", - "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2067,40 +2046,28 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", - "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz", + "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/type-utils": "8.47.0", - "@typescript-eslint/utils": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/type-utils": "8.48.1", + "@typescript-eslint/utils": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2114,7 +2081,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.47.0", + "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2130,16 +2097,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz", - "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz", + "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "engines": { @@ -2155,14 +2122,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", - "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", + "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.47.0", - "@typescript-eslint/types": "^8.47.0", + "@typescript-eslint/tsconfig-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "engines": { @@ -2177,14 +2144,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", - "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", + "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0" + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2195,9 +2162,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", - "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", + "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", "dev": true, "license": "MIT", "engines": { @@ -2212,15 +2179,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz", - "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", + "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/utils": "8.47.0", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2237,9 +2204,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz", - "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", + "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", "dev": true, "license": "MIT", "engines": { @@ -2251,21 +2218,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz", - "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", + "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.47.0", - "@typescript-eslint/tsconfig-utils": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", + "@typescript-eslint/project-service": "8.48.1", + "@typescript-eslint/tsconfig-utils": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -2279,56 +2245,17 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", - "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", + "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0" + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2343,13 +2270,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz", - "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", + "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2360,6 +2287,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", @@ -2642,9 +2582,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.27", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.27.tgz", - "integrity": "sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.2.tgz", + "integrity": "sha512-PxSsosKQjI38iXkmb3d0Y32efqyA0uW4s41u4IVBsLlWLhCiYNpH/AfNOVWRqCQBlD8TFJTz6OUWNd4DFJCnmw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2672,33 +2612,37 @@ "license": "(MIT OR Apache-2.0)" }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -2715,9 +2659,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2735,11 +2679,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -2815,9 +2759,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "dev": true, "funding": [ { @@ -2925,15 +2869,16 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -2970,6 +2915,19 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", @@ -3003,9 +2961,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, "license": "MIT" }, @@ -3233,9 +3191,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.250", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", - "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", + "version": "1.5.264", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.264.tgz", + "integrity": "sha512-1tEf0nLgltC3iy9wtlYDlQDc5Rg9lEKVjEmIHJ21rI9OcqkvD45K1oyNIRA4rR1z3LgJ7KeGzEBojVcV6m4qjA==", "dev": true, "license": "ISC" }, @@ -3431,9 +3389,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", - "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -3443,32 +3401,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.0", - "@esbuild/android-arm": "0.27.0", - "@esbuild/android-arm64": "0.27.0", - "@esbuild/android-x64": "0.27.0", - "@esbuild/darwin-arm64": "0.27.0", - "@esbuild/darwin-x64": "0.27.0", - "@esbuild/freebsd-arm64": "0.27.0", - "@esbuild/freebsd-x64": "0.27.0", - "@esbuild/linux-arm": "0.27.0", - "@esbuild/linux-arm64": "0.27.0", - "@esbuild/linux-ia32": "0.27.0", - "@esbuild/linux-loong64": "0.27.0", - "@esbuild/linux-mips64el": "0.27.0", - "@esbuild/linux-ppc64": "0.27.0", - "@esbuild/linux-riscv64": "0.27.0", - "@esbuild/linux-s390x": "0.27.0", - "@esbuild/linux-x64": "0.27.0", - "@esbuild/netbsd-arm64": "0.27.0", - "@esbuild/netbsd-x64": "0.27.0", - "@esbuild/openbsd-arm64": "0.27.0", - "@esbuild/openbsd-x64": "0.27.0", - "@esbuild/openharmony-arm64": "0.27.0", - "@esbuild/sunos-x64": "0.27.0", - "@esbuild/win32-arm64": "0.27.0", - "@esbuild/win32-ia32": "0.27.0", - "@esbuild/win32-x64": "0.27.0" + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" } }, "node_modules/escalade": { @@ -3605,131 +3563,131 @@ } }, "node_modules/eslint-plugin-react-dom": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-dom/-/eslint-plugin-react-dom-2.3.5.tgz", - "integrity": "sha512-SsIF5HbsXLJcbEoFbzgabqA7DOnfGd0BhD7QzZd5tqgz4gL2j2mUGCBbQjQIE0BMbKtOihbhuceQfQ/QxoJJIg==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-dom/-/eslint-plugin-react-dom-2.3.12.tgz", + "integrity": "sha512-GBiIANlm5267Ys+xM5AH74F5kzK43N1c6pm2oy+/P+tiZUpebIgEAilFGSAoAHqyyK1ul+kYsYr5Z8k2hP6aOw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/core": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@eslint-react/var": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", + "@eslint-react/ast": "2.3.12", + "@eslint-react/core": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "compare-versions": "^6.1.1", - "string-ts": "^2.2.1", + "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" }, "peerDependencies": { - "eslint": "^9.39.1", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/eslint-plugin-react-hooks-extra": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks-extra/-/eslint-plugin-react-hooks-extra-2.3.5.tgz", - "integrity": "sha512-IxPs6O/XCpm8FAv38TyJKcHkeS/qNb97PdbH1OqHbf4BAT/QTInWweNEpePiyydQ0YuLvHqTo1dreY8Jj6Re3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/core": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@eslint-react/var": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/type-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", - "string-ts": "^2.2.1", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks-extra/-/eslint-plugin-react-hooks-extra-2.3.12.tgz", + "integrity": "sha512-6Dg8MMcDIGMTEguB5qanN1iERVEQIXmr7wC3hSR7pexmSASGLqer2MrM8VRa66aq28r5XmhTcNjKqh/R3Hetnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "2.3.12", + "@eslint-react/core": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/type-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", + "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { - "eslint": "^9.39.1", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/eslint-plugin-react-naming-convention": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-naming-convention/-/eslint-plugin-react-naming-convention-2.3.5.tgz", - "integrity": "sha512-sjKvdJq90HWNYRBtwia7C/N8NXdg+k8O7ikQqf6QsOuTUHGLgFWGtxx1AktfizlSusCdb96w5LJ4MSi+KsuVZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/core": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@eslint-react/var": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/type-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", - "string-ts": "^2.2.1", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-naming-convention/-/eslint-plugin-react-naming-convention-2.3.12.tgz", + "integrity": "sha512-2kTupCIdJaeuf8p2QuWckjGzyA+QiIuH6+kO8dZbuMw5dNdP3oJDtDZWLgMXk+WT8mdegu5a2Q9i2QjwxMKKbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "2.3.12", + "@eslint-react/core": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/type-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", + "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" }, "peerDependencies": { - "eslint": "^9.39.1", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/eslint-plugin-react-web-api": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-web-api/-/eslint-plugin-react-web-api-2.3.5.tgz", - "integrity": "sha512-wY/hNWQxshTZ2niuu8QcARQuDg5w+cEA2OYtnrnPDjhy0qxikAaYA4NUx7HTAXoMC1Kxl78+NbQBBXnlwoMAZA==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-web-api/-/eslint-plugin-react-web-api-2.3.12.tgz", + "integrity": "sha512-9CWujvWbuwsBSrz2Ts4+NR996VKbDMbYv6ziHmmZLucHt3hZELF8eu5YHz99SLQeFzQynOMnZdR63ICr/dF+JA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/core": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@eslint-react/var": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", - "string-ts": "^2.2.1", + "@eslint-react/ast": "2.3.12", + "@eslint-react/core": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", + "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" }, "peerDependencies": { - "eslint": "^9.39.1", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/eslint-plugin-react-x": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-2.3.5.tgz", - "integrity": "sha512-Yj+6e2ds6Gg3KRPgNdifincu3cuxDYPcboCXc5EGHC//6JZXRgtqQ3N5uP9RVHnCHmKF2EiZ76XyPDnp4hMgEg==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-2.3.12.tgz", + "integrity": "sha512-G9ThX5LZQun3243JN/UchMbGPra9ZL1D7Wi4dwaIgqh26nRK8W6LBqRTJC+jlrmOanosg+flcxpUyFS/N+Ch7A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.5", - "@eslint-react/core": "2.3.5", - "@eslint-react/eff": "2.3.5", - "@eslint-react/shared": "2.3.5", - "@eslint-react/var": "2.3.5", - "@typescript-eslint/scope-manager": "^8.46.4", - "@typescript-eslint/type-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "@typescript-eslint/utils": "^8.46.4", + "@eslint-react/ast": "2.3.12", + "@eslint-react/core": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/type-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "compare-versions": "^6.1.1", "is-immutable-type": "^5.0.1", - "string-ts": "^2.2.1", + "string-ts": "^2.3.1", "ts-api-utils": "^2.1.0", "ts-pattern": "^5.9.0" }, @@ -3737,8 +3695,39 @@ "node": ">=20.19.0" }, "peerDependencies": { - "eslint": "^9.39.1", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/eslint-scope": { @@ -3759,10 +3748,34 @@ } }, "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3770,6 +3783,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -3788,6 +3814,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3842,18 +3881,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -3890,36 +3930,6 @@ "license": "MIT", "peer": true }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3934,16 +3944,6 @@ "license": "MIT", "peer": true }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3971,9 +3971,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -3984,7 +3984,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { @@ -4048,6 +4052,13 @@ "node": ">= 0.6" } }, + "node_modules/fp-ts": { + "version": "2.16.11", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", + "integrity": "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==", + "license": "MIT", + "peer": true + }, "node_modules/fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", @@ -4349,28 +4360,23 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-status-codes": { @@ -4380,15 +4386,19 @@ "license": "MIT" }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ignore": { @@ -4455,6 +4465,15 @@ "node": ">= 0.4" } }, + "node_modules/io-ts": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.22.tgz", + "integrity": "sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==", + "license": "MIT", + "peerDependencies": { + "fp-ts": "^2.5.0" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5342,30 +5361,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -5376,27 +5371,35 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -5483,6 +5486,17 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5493,17 +5507,17 @@ "node": ">=4" } }, - "node_modules/nodemon/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, "node_modules/nodemon/node_modules/supports-color": { @@ -5791,6 +5805,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playoff-schedule-backend": { + "resolved": "apps/playoff-schedule/backend", + "link": true + }, + "node_modules/playoff-schedule-frontend": { + "resolved": "apps/playoff-schedule/frontend", + "link": true + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5800,6 +5822,34 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5811,9 +5861,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", "bin": { @@ -5891,27 +5941,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5922,55 +5951,39 @@ } }, "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.0" + "react": "^19.2.1" } }, "node_modules/react-is": { @@ -6089,21 +6102,10 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", - "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -6116,28 +6118,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.2", - "@rollup/rollup-android-arm64": "4.53.2", - "@rollup/rollup-darwin-arm64": "4.53.2", - "@rollup/rollup-darwin-x64": "4.53.2", - "@rollup/rollup-freebsd-arm64": "4.53.2", - "@rollup/rollup-freebsd-x64": "4.53.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", - "@rollup/rollup-linux-arm-musleabihf": "4.53.2", - "@rollup/rollup-linux-arm64-gnu": "4.53.2", - "@rollup/rollup-linux-arm64-musl": "4.53.2", - "@rollup/rollup-linux-loong64-gnu": "4.53.2", - "@rollup/rollup-linux-ppc64-gnu": "4.53.2", - "@rollup/rollup-linux-riscv64-gnu": "4.53.2", - "@rollup/rollup-linux-riscv64-musl": "4.53.2", - "@rollup/rollup-linux-s390x-gnu": "4.53.2", - "@rollup/rollup-linux-x64-gnu": "4.53.2", - "@rollup/rollup-linux-x64-musl": "4.53.2", - "@rollup/rollup-openharmony-arm64": "4.53.2", - "@rollup/rollup-win32-arm64-msvc": "4.53.2", - "@rollup/rollup-win32-ia32-msvc": "4.53.2", - "@rollup/rollup-win32-x64-gnu": "4.53.2", - "@rollup/rollup-win32-x64-msvc": "4.53.2", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, @@ -6157,30 +6159,6 @@ "node": ">= 18" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -6200,26 +6178,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6266,12 +6224,16 @@ "license": "MIT" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -6469,19 +6431,6 @@ "node": ">=10" } }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6514,9 +6463,9 @@ } }, "node_modules/string-ts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.2.1.tgz", - "integrity": "sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.3.1.tgz", + "integrity": "sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw==", "dev": true, "license": "MIT" }, @@ -6810,13 +6759,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true - }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -6837,27 +6779,27 @@ } }, "node_modules/turbo": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.6.1.tgz", - "integrity": "sha512-qBwXXuDT3rA53kbNafGbT5r++BrhRgx3sAo0cHoDAeG9g1ItTmUMgltz3Hy7Hazy1ODqNpR+C7QwqL6DYB52yA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.6.3.tgz", + "integrity": "sha512-bf6YKUv11l5Xfcmg76PyWoy/e2vbkkxFNBGJSnfdSXQC33ZiUfutYh6IXidc5MhsnrFkWfdNNLyaRk+kHMLlwA==", "dev": true, "license": "MIT", "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "2.6.1", - "turbo-darwin-arm64": "2.6.1", - "turbo-linux-64": "2.6.1", - "turbo-linux-arm64": "2.6.1", - "turbo-windows-64": "2.6.1", - "turbo-windows-arm64": "2.6.1" + "turbo-darwin-64": "2.6.3", + "turbo-darwin-arm64": "2.6.3", + "turbo-linux-64": "2.6.3", + "turbo-linux-arm64": "2.6.3", + "turbo-windows-64": "2.6.3", + "turbo-windows-arm64": "2.6.3" } }, "node_modules/turbo-darwin-64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.6.1.tgz", - "integrity": "sha512-Dm0HwhyZF4J0uLqkhUyCVJvKM9Rw7M03v3J9A7drHDQW0qAbIGBrUijQ8g4Q9Cciw/BXRRd8Uzkc3oue+qn+ZQ==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.6.3.tgz", + "integrity": "sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg==", "cpu": [ "x64" ], @@ -6869,9 +6811,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.6.1.tgz", - "integrity": "sha512-U0PIPTPyxdLsrC3jN7jaJUwgzX5sVUBsKLO7+6AL+OASaa1NbT1pPdiZoTkblBAALLP76FM0LlnsVQOnmjYhyw==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.6.3.tgz", + "integrity": "sha512-MwVt7rBKiOK7zdYerenfCRTypefw4kZCue35IJga9CH1+S50+KTiCkT6LBqo0hHeoH2iKuI0ldTF2a0aB72z3w==", "cpu": [ "arm64" ], @@ -6883,9 +6825,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.6.1.tgz", - "integrity": "sha512-eM1uLWgzv89bxlK29qwQEr9xYWBhmO/EGiH22UGfq+uXr+QW1OvNKKMogSN65Ry8lElMH4LZh0aX2DEc7eC0Mw==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.6.3.tgz", + "integrity": "sha512-cqpcw+dXxbnPtNnzeeSyWprjmuFVpHJqKcs7Jym5oXlu/ZcovEASUIUZVN3OGEM6Y/OTyyw0z09tOHNt5yBAVg==", "cpu": [ "x64" ], @@ -6897,9 +6839,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.6.1.tgz", - "integrity": "sha512-MFFh7AxAQAycXKuZDrbeutfWM5Ep0CEZ9u7zs4Hn2FvOViTCzIfEhmuJou3/a5+q5VX1zTxQrKGy+4Lf5cdpsA==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.6.3.tgz", + "integrity": "sha512-MterpZQmjXyr4uM7zOgFSFL3oRdNKeflY7nsjxJb2TklsYqiu3Z9pQ4zRVFFH8n0mLGna7MbQMZuKoWqqHb45w==", "cpu": [ "arm64" ], @@ -6911,9 +6853,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.6.1.tgz", - "integrity": "sha512-buq7/VAN7KOjMYi4tSZT5m+jpqyhbRU2EUTTvp6V0Ii8dAkY2tAAjQN1q5q2ByflYWKecbQNTqxmVploE0LVwQ==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.6.3.tgz", + "integrity": "sha512-biDU70v9dLwnBdLf+daoDlNJVvqOOP8YEjqNipBHzgclbQlXbsi6Gqqelp5er81Qo3BiRgmTNx79oaZQTPb07Q==", "cpu": [ "x64" ], @@ -6925,9 +6867,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.6.1.tgz", - "integrity": "sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.6.3.tgz", + "integrity": "sha512-dDHVKpSeukah3VsI/xMEKeTnV9V9cjlpFSUs4bmsUiLu3Yv2ENlgVEZv65wxbeE0bh0jjpmElDT+P1KaCxArQQ==", "cpu": [ "arm64" ], @@ -7054,16 +6996,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.47.0.tgz", - "integrity": "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz", + "integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.47.0", - "@typescript-eslint/parser": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/utils": "8.47.0" + "@typescript-eslint/eslint-plugin": "8.48.1", + "@typescript-eslint/parser": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7102,6 +7044,13 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7112,9 +7061,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "dev": true, "funding": [ { @@ -7162,9 +7111,9 @@ } }, "node_modules/vite": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", - "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -7747,34 +7696,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vite/node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7911,9 +7832,9 @@ } }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", "funding": { @@ -7927,53 +7848,6 @@ "packages/common": { "version": "1.0.0", "license": "ISC" - }, - "packages/config-env": { - "name": "@repo/config-env", - "version": "0.0.0", - "extraneous": true - }, - "packages/eslint-config": { - "name": "@repo/eslint-config", - "version": "0.0.0", - "extraneous": true, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@next/eslint-plugin-next": "^15.5.0", - "eslint": "^9.39.1", - "eslint-config-prettier": "^10.1.1", - "eslint-plugin-only-warn": "^1.1.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-turbo": "^2.6.0", - "globals": "^16.5.0", - "typescript": "^5.9.2", - "typescript-eslint": "^8.46.3" - } - }, - "packages/typescript-config": { - "name": "@repo/typescript-config", - "version": "0.0.0", - "extraneous": true, - "license": "MIT" - }, - "packages/ui": { - "name": "@repo/ui", - "version": "0.0.0", - "extraneous": true, - "dependencies": { - "react": "^19.2.0", - "react-dom": "^19.2.0" - }, - "devDependencies": { - "@repo/eslint-config": "*", - "@repo/typescript-config": "*", - "@types/node": "^22.15.3", - "@types/react": "19.2.2", - "@types/react-dom": "19.2.2", - "eslint": "^9.39.1", - "typescript": "5.9.2" - } } } } diff --git a/package.json b/package.json index 35155bd..b671b51 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,15 @@ ], "dependencies": { "@tailwindcss/vite": "^4.1.17", + "cors": "^2.8.5", "dotenv": "^17.2.3", "dotenv-cli": "^11.0.0", "eslint-plugin-bsd": "^1.0.0", "eslint-plugin-react": "^7.37.5", "express": "^5.1.0", "http-status-codes": "^2.3.0", + "io-ts": "^2.2.22", + "react": "^19.2.1", "tailwindcss": "^4.1.17", "tsx": "^4.21.0" } From aeb81a95fa59bc5c3ac3f1611d7ea988a2946879 Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Tue, 9 Dec 2025 21:49:30 +0200 Subject: [PATCH 2/8] yoni check i am done --- .vscode/tasks.json | 5 +- apps/playoff-schedule/backend/src/main.ts | 2 +- apps/playoff-schedule/frontend/src/App.tsx | 222 +++++++++++------- .../frontend/src/Hooks/LocalStorageHook.tsx | 33 +++ .../src/endpoints/unUsed/EventsFull.tsx | 39 --- .../frontend/src/services/CreateMatch.tsx | 19 -- 6 files changed, 171 insertions(+), 149 deletions(-) create mode 100644 apps/playoff-schedule/frontend/src/Hooks/LocalStorageHook.tsx delete mode 100644 apps/playoff-schedule/frontend/src/endpoints/unUsed/EventsFull.tsx delete mode 100644 apps/playoff-schedule/frontend/src/services/CreateMatch.tsx diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fb32ad6..4d64829 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,8 @@ { "label": "dev", "type": "shell", - "command": "./node_modules/.bin/dotenv -e .dev.env -e .public.env -e .secret.env -- 'turbo run dev --filter=${input:project}-frontend --filter=${input:project}-backend'" + "command": "./node_modules/.bin/dotenv -e .dev.env -e .public.env -e .secret.env -- 'turbo run dev --filter=${input:project}-frontend --filter=${input:project}-backend'", + "problemMatcher": [] }, { "label": "serve", @@ -42,7 +43,7 @@ "id": "project", "description": "Full Stack Projects worth running", "type": "pickString", - "options": ["template", "test","playoff-schedule"] + "options": ["template", "test", "playoff-schedule"] }, { "id": "workspace", diff --git a/apps/playoff-schedule/backend/src/main.ts b/apps/playoff-schedule/backend/src/main.ts index 875ab7e..a61db0d 100644 --- a/apps/playoff-schedule/backend/src/main.ts +++ b/apps/playoff-schedule/backend/src/main.ts @@ -7,6 +7,7 @@ const app = express(); app.use(cors({ origin: "*" })); + app.use("/api/v1", apiRouter); const defaultPort = 4590; @@ -22,7 +23,6 @@ const statusBadUser = 400; const apiKey = "YOUR_API_KEY"; - export const fetchData = async (url: string): Promise => { const response = await fetch(url, { method: "GET", diff --git a/apps/playoff-schedule/frontend/src/App.tsx b/apps/playoff-schedule/frontend/src/App.tsx index a1053d4..6509b6a 100644 --- a/apps/playoff-schedule/frontend/src/App.tsx +++ b/apps/playoff-schedule/frontend/src/App.tsx @@ -1,13 +1,11 @@ // בס"ד - - import type React from "react"; import { useEffect, useState, useMemo, useCallback } from "react"; import { urlMatches, type MatchesSimpleType } from "./endpoints/MatchSimple"; import { urlTeamsInEvent, type TeamsInEventType } from "./endpoints/TeamsSimple"; +import useLocalStorage from "./Hooks/LocalStorageHook"; const targetTeamNumber = 4590; -const targetTeamKey = `frc${targetTeamNumber}`; const backendPort = 4590; const refreshIntervalMs = 30000; const timeMultiplier = 1000; @@ -15,8 +13,20 @@ const sliceStart = 3; const firstIndex = 0; const nextMatchLimit = 2; const noGap = 0; -const matchTime = 0; +const matchTimeDefault = 0; +const matchTimeMissing = 0; const dayInSeconds = 86400; + +const sortABeforeB = -1; +const sortBBeforeA = 1; +const weightQm = 1; +const weightEf = 2; +const weightQf = 3; +const weightSf = 4; +const weightF = 5; +const weightDefault = 99; + +const targetTeamKey = `frc${targetTeamNumber}`; const backendBaseUrl = `http://localhost:${backendPort}/fetch?url=`; const colorRedBg = "#FFEBEE"; @@ -59,28 +69,54 @@ const urlRankings = (eventKey: string) => const fetchFromProxy = async (targetUrl: string): Promise => { const fullUrl = `${backendBaseUrl}${encodeURIComponent(targetUrl)}`; const response = await fetch(fullUrl); - if (!response.ok) { - throw new Error(`HTTP error status: ${response.status}`); - } - return response.json() as Promise; + + return response.ok + ? (response.json() as Promise) + : Promise.reject(new Error(`HTTP error status: ${response.status}`)); }; +const getLevelWeight = (level: string): number => + level === 'qm' ? weightQm + : level === 'ef' ? weightEf + : level === 'qf' ? weightQf + : level === 'sf' ? weightSf + : level === 'f' ? weightF + : weightDefault; + const App: React.FC = () => { - const [inputEventKey, setInputEventKey] = useState(""); - const [activeEventKey, setActiveEventKey] = useState(""); + const [activeEventKey, setActiveEventKey] = useLocalStorage("dashboard_active_event", ""); + const [inputEventKey, setInputEventKey] = useState(activeEventKey); const [teams, setTeams] = useState([]); const [allMatches, setAllMatches] = useState([]); const [teamRank, setTeamRank] = useState(null); const [searchStatus, setSearchStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); - const performSearch = useCallback(async (eventKey: string) => { - if (!eventKey) return; - + const resetInSearch = () => { setSearchStatus("loading"); - setActiveEventKey(""); setAllMatches([]); setTeams([]); setTeamRank(null); + }; + + const sortMatches = useCallback((a: MatchesSimpleType, b: MatchesSimpleType) => { + const timeA = a.predicted_time ?? a.time ?? matchTimeDefault; + const timeB = b.predicted_time ?? b.time ?? matchTimeDefault; + const weightA = getLevelWeight(a.comp_level); + const weightB = getLevelWeight(b.comp_level); + + return (timeA > matchTimeMissing && timeB > matchTimeMissing) ? timeA - timeB + : (timeA === matchTimeMissing && timeB > matchTimeMissing) ? sortABeforeB + : (timeA > matchTimeMissing && timeB === matchTimeMissing) ? sortBBeforeA + : (weightA !== weightB) + ? weightA - weightB + : a.match_number - b.match_number; + }, []); + + const performSearch = useCallback(async (eventKey: string) => { + if (!eventKey) return; + + resetInSearch(); + setActiveEventKey(eventKey); try { const teamsUrl = urlTeamsInEvent(eventKey); @@ -93,113 +129,124 @@ const App: React.FC = () => { fetchFromProxy(rankingsUrl).catch(() => ({ rankings: [] })) ]); - if (Array.isArray(teamsData) && Array.isArray(matchesData)) { - setTeams(teamsData); - matchesData.sort((a, b) => (a.predicted_time ?? a.time ?? noGap) - (b.predicted_time ?? b.time ?? noGap)); - setAllMatches(matchesData); - - const myRank = rankingsData.rankings.find(r => r.team_key === targetTeamKey) ?? null; - setTeamRank(myRank); - - setActiveEventKey(eventKey); - setSearchStatus("success"); - } else { - setSearchStatus("error"); - } + Array.isArray(teamsData) && Array.isArray(matchesData) + ? (() => { + setTeams(teamsData); + matchesData.sort(sortMatches); + setAllMatches(matchesData); + setTeamRank(rankingsData.rankings.find(r => r.team_key === targetTeamKey) ?? null); + setSearchStatus("success"); + })() + : setSearchStatus("error"); + } catch { setSearchStatus("error"); } - }, []); + }, [setActiveEventKey, sortMatches]); - const handleSearchSubmit = (e: React.FormEvent) => { - e.preventDefault(); + const handleSearchSubmit = (event: React.FormEvent) => { + event.preventDefault(); const formattedKey = inputEventKey.trim().toLowerCase(); void performSearch(formattedKey); }; useEffect(() => { - let intervalId: number | undefined = undefined; + if (activeEventKey && searchStatus === "idle") { + void performSearch(activeEventKey); + } + }, [activeEventKey, performSearch, searchStatus]); - if (searchStatus === "success" && activeEventKey) { - intervalId = window.setInterval(() => { + useEffect(() => { + const intervalId: number | undefined = searchStatus === "success" && activeEventKey ? + window.setInterval(() => { const matchesUrl = urlMatches(activeEventKey); + const rankingsUrl = urlRankings(activeEventKey); + void fetchFromProxy(matchesUrl).then(data => { if (Array.isArray(data)) { - data.sort((a, b) => (a.predicted_time ?? a.time ?? noGap) - (b.predicted_time ?? b.time ?? noGap)); + data.sort(sortMatches); setAllMatches(data); } }).catch(console.error); - }, refreshIntervalMs); - } + + void fetchFromProxy(rankingsUrl).then(data => { + const myRank = data.rankings.find(r => r.team_key === targetTeamKey) ?? null; + setTeamRank(myRank); + }).catch(console.error); + + }, refreshIntervalMs) : undefined; return () => { if (intervalId !== undefined) { window.clearInterval(intervalId); } }; - }, [activeEventKey, searchStatus]); + }, [activeEventKey, searchStatus, sortMatches]); const teamNameMap = useMemo(() => { const map: Map = new Map(); - teams.forEach(t => map.set(t.key, t.nickname)); + teams.forEach(team => map.set(team.key, team.nickname)); return map; }, [teams]); const { currentGlobalMatch, targetTeamMatches, isEventOver, isTeamDone, isFutureEvent } = useMemo(() => { - -// testing 2025isios -// const currentTimeSecs = Math.floor(new Date("2025-10-08T10:44:00").getTime() / timeMultiplier); -// testing 2025iscmp -// const currentTimeSecs = Math.floor(new Date("2025-03-26T17:00:00").getTime() / timeMultiplier); -// normal one - const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); +// normal one +const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); + +// testing 2025isios +//const currentTimeSecs = Math.floor(new Date("2025-10-08T12:44:00").getTime() / timeMultiplier); + +// testing 2025iscmp +// const currentTimeSecs = Math.floor(new Date("2025-03-26T10:00:00").getTime() / timeMultiplier); + +// testing 2025cmptx +//const currentTimeSecs = Math.floor(new Date("2025-04-19T10:00:00").getTime() / timeMultiplier); - const getMatchTime = (m: MatchesSimpleType) => m.predicted_time ?? m.time ?? matchTime; - const futureMatches = allMatches.filter(m => { - const t = getMatchTime(m); - return t > noGap && t > currentTimeSecs; + const getMatchTime = (match: MatchesSimpleType) => match.predicted_time ?? match.time ?? matchTimeDefault; + + const futureMatches = allMatches.filter(match => { + const time = getMatchTime(match); + return time === matchTimeMissing || time > currentTimeSecs; }); - const pastMatches = allMatches.filter(m => { - const t = getMatchTime(m); - return t > noGap && t <= currentTimeSecs; + const pastMatches = allMatches.filter(match => { + const time = getMatchTime(match); + return time > matchTimeMissing && time <= currentTimeSecs; }); + futureMatches.sort(sortMatches); + const currentMatch = futureMatches.length > noGap ? futureMatches[firstIndex] : undefined; - const teamFutureMatches = futureMatches.filter(m => - m.alliances.blue.team_keys.includes(targetTeamKey) || - m.alliances.red.team_keys.includes(targetTeamKey) + const teamFutureMatches = futureMatches.filter(match => + match.alliances.blue.team_keys.includes(targetTeamKey) || + match.alliances.red.team_keys.includes(targetTeamKey) ); - const teamPastMatches = pastMatches.filter(m => - m.alliances.blue.team_keys.includes(targetTeamKey) || - m.alliances.red.team_keys.includes(targetTeamKey) + const teamPastMatches = pastMatches.filter(match => + match.alliances.blue.team_keys.includes(targetTeamKey) || + match.alliances.red.team_keys.includes(targetTeamKey) ); const hasEventEnded = futureMatches.length === noGap && allMatches.length > noGap; const hasTeamDone = !hasEventEnded && teamFutureMatches.length === noGap && teamPastMatches.length > noGap; - let isItFutureEvent = false; - if (currentMatch) { - const t = getMatchTime(currentMatch); - if (t > currentTimeSecs + dayInSeconds) { - isItFutureEvent = true; - } - } + const isNextMatchInFarFuture = currentMatch + ? (getMatchTime(currentMatch) > currentTimeSecs + dayInSeconds && getMatchTime(currentMatch) !== matchTimeMissing) + : false; return { currentGlobalMatch: currentMatch, targetTeamMatches: hasTeamDone ? [] : teamFutureMatches.slice(firstIndex, nextMatchLimit), isEventOver: hasEventEnded, isTeamDone: hasTeamDone, - isFutureEvent: isItFutureEvent + isFutureEvent: isNextMatchInFarFuture }; - }, [allMatches]); + }, [allMatches, sortMatches]); return (
@@ -212,7 +259,7 @@ const App: React.FC = () => { type="text" placeholder="Event ID (e.g., 2025isios)" value={inputEventKey} - onChange={(e) => { setInputEventKey(e.target.value); }} + onChange={(event) => { setInputEventKey(event.target.value); }} style={{ padding: "12px", fontSize: "16px", flexGrow: 1, border: "none", borderRadius: "6px", outline: "none" }} /> {searchStatus === "error" && ( -
+
Event not found.
)}
-
+
{searchStatus === "success" && ( <> -
- Event: {activeEventKey} - Teams: {teams.length} +
+ + Event: {activeEventKey} + + + Teams: {teams.length} +
-
-

+
+

{isFutureEvent ? "Upcoming Event" : "Current Field Status"}

{currentGlobalMatch !== undefined ? ( -
- - {currentGlobalMatch.comp_level.toUpperCase()}-{currentGlobalMatch.match_number} +
+ + {getMatchDisplayName(currentGlobalMatch)} - + {isFutureEvent ? "SCHEDULED" : "IN PROGRESS"}
) : ( -

{allMatches.length > noGap ? "Event Concluded" : "No matches scheduled"}

+

+ {allMatches.length > noGap + ? "Event Concluded" + : "No matches scheduled"} +

)}
-

- {isEventOver ? `Final Results: Team ${targetTeamNumber}` : `Upcoming: Team ${targetTeamNumber}`} +

+ {isEventOver + ? `Final Results: Team ${targetTeamNumber}` + : `Upcoming: Team ${targetTeamNumber}`}

- + {isEventOver ? ( -
+
{teamRank ? ( - <> -
Final Rank
-
#{teamRank.rank}
-
- Record: {teamRank.record.wins}-{teamRank.record.losses}-{teamRank.record.ties} -
- + <> +
+ Final Rank +
+
+ #{teamRank.rank} +
+
+ Record: {teamRank.record.wins}-{teamRank.record.losses}- + {teamRank.record.ties} +
+ ) : ( -

Rank data not available for this event.

+

+ Rank data not available for this event. +

)}
) : ( <> {targetTeamMatches.length === noGap && ( -
- {isTeamDone - ?

Team {targetTeamNumber} has completed all scheduled matches for this event (or for today).

- :

No upcoming matches found for Team {targetTeamNumber}.

- } +
+ {isTeamDone ? ( +

+ Team {targetTeamNumber} has completed all scheduled + matches for this event (or for today). +

+ ) : ( +

+ No upcoming matches found for Team {targetTeamNumber}. +

+ )}
)} {targetTeamMatches.map((match) => { const effectiveTime = match.predicted_time ?? match.time; - const predictedDate = effectiveTime - ? new Date(effectiveTime * timeMultiplier).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + const predictedDate = effectiveTime + ? new Date( + effectiveTime * timeMultiplier + ).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }) : "TBD"; - const matchesAway = (currentGlobalMatch !== undefined && match.comp_level === currentGlobalMatch.comp_level) - ? match.match_number - currentGlobalMatch.match_number - : noGap; + const matchIndex = futureMatches.findIndex( + (m) => m.key === match.key + ); + const matchesAway = + matchIndex > notFoundIndex ? matchIndex : noGap; - const isRedAlliance = match.alliances.red.team_keys.includes(targetTeamKey); + const isRedAlliance = + match.alliances.red.team_keys.includes(targetTeamKey); return ( -
-
+
+
- - {match.comp_level.toUpperCase()} - {match.match_number} + + {getMatchDisplayName(match)}
-
-
+
+
{predictedDate}
- -
- {matchesAway <= noGap ? "PLAYING NOW" : `IN ${matchesAway} MATCHES`} +
+ {matchesAway <= noGap + ? "PLAYING NOW" + : `IN ${matchesAway} MATCHES`}
-
-
-
Red Alliance
- {match.alliances.red.team_keys.map(teamKey => { +
+
+
+ Red Alliance +
+ {match.alliances.red.team_keys.map((teamKey) => { const teamNumber = teamKey.slice(sliceStart); - const teamName = teamNameMap.get(teamKey) ?? "Unknown"; + const teamName = + teamNameMap.get(teamKey) ?? "Unknown"; const isTargetTeam = teamKey === targetTeamKey; return ( -
- {teamNumber} - {teamName} - {isTargetTeam && YOU} +
+ + {teamNumber} + + + {teamName} + + {isTargetTeam && ( + + YOU + + )}
); })}
-
-
Blue Alliance
- {match.alliances.blue.team_keys.map(teamKey => { +
+
+ Blue Alliance +
+ {match.alliances.blue.team_keys.map((teamKey) => { const teamNumber = teamKey.slice(sliceStart); - const teamName = teamNameMap.get(teamKey) ?? "Unknown"; + const teamName = + teamNameMap.get(teamKey) ?? "Unknown"; const isTargetTeam = teamKey === targetTeamKey; return ( -
- {teamNumber} - {teamName} - {isTargetTeam && YOU} +
+ + {teamNumber} + + + {teamName} + + {isTargetTeam && ( + + YOU + + )}
); })} @@ -416,4 +574,4 @@ const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); ); }; -export default App; \ No newline at end of file +export default App; diff --git a/apps/playoff-schedule/frontend/src/AppCss.css b/apps/playoff-schedule/frontend/src/AppCss.css new file mode 100644 index 0000000..24623f9 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/AppCss.css @@ -0,0 +1,278 @@ +:root { + --color-neutral-bg: #f5f5f5; + --color-header-bg: #263238; + --color-card-bg: #ffffff; + --color-text-main: #333333; + --color-text-sub: #666666; + --color-red-bg: #ffebee; + --color-red-border: #ef5350; + --color-red-text: #c62828; + --color-blue-bg: #e3f2fd; + --color-blue-border: #42a5f5; + --color-blue-text: #1565c0; + --color-accent: #ffa000; + --color-future: #2196f3; + --color-button-bg: #00e676; + --color-button-text: #1b5e20; + --color-rank-bg: #e8eaf6; + --color-rank-text: #3f51b5; +} + +body { + margin: 0; + font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: var(--color-neutral-bg); +} + +.app-container { + min-height: 100vh; + padding-bottom: 40px; +} + +.header-wrapper { + background-color: var(--color-header-bg); + color: white; + padding: 20px 0; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} + +.header-content { + max-width: 800px; + margin: 0 auto; + padding: 0 20px; +} + +.header-title { + margin: 0 0 15px 0; + font-size: 1.8rem; +} + +.search-form { + display: flex; + gap: 10px; +} + +.search-input { + padding: 12px; + font-size: 16px; + flex-grow: 1; + border: none; + border-radius: 6px; + outline: none; +} + +.search-button { + padding: 12px 25px; + font-size: 16px; + background-color: var(--color-button-bg); + color: var(--color-button-text); + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: bold; +} + +.search-button:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.error-message { + margin-top: 15px; + padding: 10px; + background-color: var(--color-red-bg); + color: var(--color-red-text); + border-radius: 4px; + font-size: 0.9rem; +} + +.main-content { + max-width: 800px; + margin: 20px auto; + padding: 0 20px; +} + +.event-info-bar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + color: var(--color-text-sub); + font-size: 0.9rem; +} +.status-card { + background-color: var(--color-card-bg); + padding: 20px; + border-radius: 12px; + margin-bottom: 30px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + border-left-width: 5px; + border-left-style: solid; +} +.status-title { + margin: 0 0 5px 0; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +.status-content { + display: flex; + align-items: center; + justify-content: space-between; +} + +.status-match-name { + font-size: 2rem; + font-weight: 800; + color: var(--color-text-main); +} + +.status-badge { + padding: 5px 12px; + border-radius: 20px; + font-size: 0.85rem; + font-weight: bold; +} + +.status-empty { + color: #999; + font-style: italic; +} + +.section-title { + font-size: 1.4rem; + color: var(--color-text-main); + border-bottom: 2px solid #ddd; + padding-bottom: 10px; + margin-bottom: 20px; +} + +.rank-card { + background-color: var(--color-card-bg); + border-radius: 12px; + padding: 30px; + text-align: center; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.rank-label { + font-size: 1rem; + color: var(--color-text-sub); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 10px; +} + +.rank-number { + font-size: 4rem; + font-weight: bold; + color: var(--color-rank-text); + line-height: 1; +} + +.rank-record { + margin-top: 20px; + display: inline-block; + background-color: var(--color-rank-bg); + padding: 8px 20px; + border-radius: 20px; + color: var(--color-rank-text); + font-weight: bold; +} + +.empty-state { + text-align: center; + padding: 40px; + color: #888; + background-color: var(--color-card-bg); + border-radius: 12px; +} + +.match-card { + background-color: var(--color-card-bg); + border-radius: 12px; + margin-bottom: 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + overflow: hidden; +} + +.match-header { + padding: 15px 20px; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #fafafa; +} + +.match-title { + font-size: 1.2rem; + font-weight: bold; + color: var(--color-text-main); +} + +.match-meta { + text-align: right; + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.match-time { + font-size: 0.9rem; + color: var(--color-text-sub); + display: flex; + align-items: center; + gap: 10px; +} + +.match-countdown { + font-weight: bold; + font-size: 0.85rem; + margin-top: 4px; +} + +.alliance-row { + display: flex; +} + +.alliance-block { + flex: 1; + padding: 15px; +} + +.alliance-header { + font-weight: bold; + margin-bottom: 10px; + font-size: 0.9rem; + text-transform: uppercase; +} + +.team-row { + display: flex; + align-items: center; + margin-bottom: 6px; +} + +.team-number { + width: 50px; + font-weight: bold; + color: var(--color-text-main); +} + +.team-name { + font-size: 0.9rem; + color: var(--color-text-sub); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.you-badge { + margin-left: auto; + font-size: 0.7rem; + color: white; + padding: 2px 6px; + border-radius: 4px; +} diff --git a/apps/playoff-schedule/frontend/src/config/frcConfig.ts b/apps/playoff-schedule/frontend/src/config/frcConfig.ts new file mode 100644 index 0000000..23930c5 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/config/frcConfig.ts @@ -0,0 +1,30 @@ +// בס"ד + +export const targetTeamNumber = 4590; +export const backendPort = 4590; + +export const refreshIntervalMs = 30000; +export const timeMultiplier = 1000; + +export const sliceStart = 3; +export const firstIndex = 0; +export const nextMatchLimit = 2; +export const noGap = 0; + +export const matchTimeDefault = 0; +export const matchTimeMissing = 0; +export const dayInSeconds = 86400; + +export const sortABeforeB = -1; +export const sortBBeforeA = 1; + +export const weightQm = 1; +export const weightEf = 2; +export const weightQf = 3; +export const weightSf = 4; +export const weightF = 5; +export const weightDefault = 99; +export const defaultLevelWeight =1; +export const levelWeights = {1:weightQm,2:weightEf,3:weightQf,4:weightSf,5:weightF} +export const targetTeamKey = `frc${targetTeamNumber}`; +export const backendBaseUrl = `http://localhost:${backendPort}/fetch?url=`; diff --git a/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx index c049ee3..f66d301 100644 --- a/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx +++ b/apps/playoff-schedule/frontend/src/endpoints/EventsSimple.tsx @@ -1,6 +1,6 @@ // בס"ד import * as t from "io-ts"; -import { numberOrNull, stringOrNull } from "../utils/TypeUtils"; +import { stringOrNull } from "../utils/TypeUtils"; export const simpleEventsInYear = t.type({ //did diff --git a/apps/playoff-schedule/frontend/src/endpoints/RankingSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/RankingSimple.tsx new file mode 100644 index 0000000..2205258 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/endpoints/RankingSimple.tsx @@ -0,0 +1,4 @@ +// בס"ד + +export const urlRankings = (eventKey: string) => +`https://www.thebluealliance.com/api/v3/event/${eventKey}/rankings`; \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx b/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx index 99af797..8953a64 100644 --- a/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx +++ b/apps/playoff-schedule/frontend/src/endpoints/SimpleAlliance.tsx @@ -6,4 +6,3 @@ export const simpleAlliance = t.type({ surrogate_team_keys: t.array(t.string), team_keys: t.array(t.string), }); -type SimpleAllianceType = typeof simpleAlliance._A; diff --git a/apps/playoff-schedule/frontend/src/utils/matchUtils.ts b/apps/playoff-schedule/frontend/src/utils/matchUtils.ts new file mode 100644 index 0000000..894c8ba --- /dev/null +++ b/apps/playoff-schedule/frontend/src/utils/matchUtils.ts @@ -0,0 +1,50 @@ +// בס"ד +import type { MatchesSimpleType } from "../endpoints/MatchSimple"; +import { + matchTimeDefault, + matchTimeMissing, + sortABeforeB, + sortBBeforeA, + weightQm, + weightEf, + weightQf, + weightSf, + weightF, + weightDefault, +} from "../config/frcConfig"; + +export const getMatchTime = (match: MatchesSimpleType): number => + match.predicted_time ?? match.time ?? matchTimeDefault; + +const getLevelWeight = (level: string): number => + level === 'qm' ? weightQm + : level === 'ef' ? weightEf + : level === 'qf' ? weightQf + : level === 'sf' ? weightSf + : level === 'f' ? weightF + : weightDefault; + +export const sortMatches = (a: MatchesSimpleType, b: MatchesSimpleType) => { + const timeA = getMatchTime(a); + const timeB = getMatchTime(b); + const weightA = getLevelWeight(a.comp_level); + const weightB = getLevelWeight(b.comp_level); + + if (timeA > matchTimeMissing && timeB > matchTimeMissing) { + return timeA - timeB; + } + + if (timeA === matchTimeMissing && timeB > matchTimeMissing) { + return sortABeforeB; + } + + if (timeA > matchTimeMissing && timeB === matchTimeMissing) { + return sortBBeforeA; + } + + if (weightA !== weightB) { + return weightA - weightB; + } + + return a.match_number - b.match_number; +}; diff --git a/turbo.json b/turbo.json index 72419fe..fb6ab51 100644 --- a/turbo.json +++ b/turbo.json @@ -1,7 +1,7 @@ { "$schema": "https://turborepo.com/schema.json", "ui": "tui", - "globalPassThroughEnv": ["FRONTEND_PORT","BACKEND_PORT","NODE_ENV"], + "globalPassThroughEnv": ["FRONTEND_PORT","BACKEND_PORT","NODE_ENV","TBA_API_KEY"], "tasks": { "build": { From 2c47d2de6e31c020ee8f5c6276504796f6728b9a Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Sat, 3 Jan 2026 22:17:41 +0200 Subject: [PATCH 4/8] i am done pls no comments :) --- apps/playoff-schedule/backend/src/main.ts | 18 +- .../backend/src/routes/tba.ts | 8 +- apps/playoff-schedule/frontend/src/App.tsx | 580 ++---------------- .../frontend/src/Hooks/useEventData.ts | 128 ++++ .../frontend/src/Hooks/useMatchProcessing.ts | 104 ++++ .../frontend/src/components/Alliance.tsx | 73 +++ .../src/components/CurrentMatchStatus.tsx | 53 ++ .../frontend/src/components/EmptyState.tsx | 22 + .../frontend/src/components/EventInfoBar.tsx | 23 + .../frontend/src/components/FinalResults.tsx | 32 + .../frontend/src/components/Header.tsx | 48 ++ .../frontend/src/components/MatchCard.tsx | 72 +++ .../frontend/src/components/SectionTitle.tsx | 17 + .../frontend/src/config/frcConfig.ts | 11 +- .../frontend/src/styles/classes.ts | 43 ++ apps/playoff-schedule/frontend/src/types.ts | 46 ++ .../frontend/src/utils/TypeUtils.tsx | 1 + .../frontend/src/utils/apiUtils.ts | 11 + .../frontend/src/utils/matchDisplayUtils.ts | 31 + .../frontend/src/utils/matchUtils.tsx | 25 - .../frontend/src/utils/rankingsUtils.ts | 3 + 21 files changed, 771 insertions(+), 578 deletions(-) create mode 100644 apps/playoff-schedule/frontend/src/Hooks/useEventData.ts create mode 100644 apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts create mode 100644 apps/playoff-schedule/frontend/src/components/Alliance.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/EmptyState.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/EventInfoBar.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/FinalResults.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/Header.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/MatchCard.tsx create mode 100644 apps/playoff-schedule/frontend/src/components/SectionTitle.tsx create mode 100644 apps/playoff-schedule/frontend/src/styles/classes.ts create mode 100644 apps/playoff-schedule/frontend/src/types.ts create mode 100644 apps/playoff-schedule/frontend/src/utils/apiUtils.ts create mode 100644 apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts delete mode 100644 apps/playoff-schedule/frontend/src/utils/matchUtils.tsx create mode 100644 apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts diff --git a/apps/playoff-schedule/backend/src/main.ts b/apps/playoff-schedule/backend/src/main.ts index 3783694..ed1e5be 100644 --- a/apps/playoff-schedule/backend/src/main.ts +++ b/apps/playoff-schedule/backend/src/main.ts @@ -3,6 +3,7 @@ import express from "express"; import { apiRouter } from "./routes"; import cors from "cors"; import dotenv from "dotenv"; +import { StatusCodes } from "http-status-codes"; dotenv.config(); const app = express(); @@ -15,22 +16,15 @@ app.use("/api/v1", apiRouter); const defaultPort = 4590; const port = process.env.BACKEND_PORT ?? defaultPort; -console.log(port); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ limit: "10mb", extended: true })); -const statusBadServer = 500; -const statusGood = 200; -const statusBadUser = 400; const apiKey: string | undefined = process.env.TBA_API_KEY; if (apiKey === undefined) { throw new Error("TBA_API_KEY is not defined."); } -// const apiKey = -// "yLJ97mneQ9bNLVM8neE5p9APXMXXx87Q5FFWpIlGv7ht21N5ljcFcTH9BX9leRyk"; - export const fetchData = async (url: string): Promise => { const response = await fetch(url, { method: "GET", @@ -44,7 +38,6 @@ export const fetchData = async (url: string): Promise => { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); - console.log("TBA data:", data); return data; }; @@ -52,17 +45,18 @@ app.get("/fetch", async (req, res) => { try { const encodedUrl = req.query.url; if (!encodedUrl || typeof encodedUrl !== "string") { - res.status(statusBadUser).json({ error: "missing url param" }); + res.status(StatusCodes.BAD_REQUEST).json({ error: "missing url param" }); return; } const fullUrl = decodeURIComponent(encodedUrl); - console.log("Incoming /fetch with url:", fullUrl); const data = await fetchData(fullUrl); - res.status(statusGood).json(data); + res.status(StatusCodes.OK).json(data); } catch (error) { console.error("Error in /fetch:", error); - res.status(statusBadServer).json({ error: "Failed to fetch data" }); + res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json({ error: "Failed to fetch data" }); } }); diff --git a/apps/playoff-schedule/backend/src/routes/tba.ts b/apps/playoff-schedule/backend/src/routes/tba.ts index b5f691f..270cbbd 100644 --- a/apps/playoff-schedule/backend/src/routes/tba.ts +++ b/apps/playoff-schedule/backend/src/routes/tba.ts @@ -12,7 +12,6 @@ const getApiKey = (): string => { return key; }; - export const fetchData = async (url: string): Promise => { const response = await fetch(url, { method: "GET", @@ -27,24 +26,19 @@ export const fetchData = async (url: string): Promise => { } const data = await response.json(); - console.log("TBA data:", data); return data; }; - tbaRouter.get("/fetch", async (req, res) => { try { const encodedUrl = req.query.url; if (!encodedUrl || typeof encodedUrl !== "string") { - res - .status(StatusCodes.BAD_REQUEST) - .json({ error: "missing url param" }); + res.status(StatusCodes.BAD_REQUEST).json({ error: "missing url param" }); return; } const fullUrl = decodeURIComponent(encodedUrl); - console.log("Incoming /tba/fetch with url:", fullUrl); const data = await fetchData(fullUrl); res.status(StatusCodes.OK).json(data); diff --git a/apps/playoff-schedule/frontend/src/App.tsx b/apps/playoff-schedule/frontend/src/App.tsx index ad9d2bd..071e8c1 100644 --- a/apps/playoff-schedule/frontend/src/App.tsx +++ b/apps/playoff-schedule/frontend/src/App.tsx @@ -1,96 +1,16 @@ // בס"ד -import type React from "react"; -import { useEffect, useState, useMemo, useCallback } from "react"; -import { urlMatches, type MatchesSimpleType } from "./endpoints/MatchSimple"; -import { - urlTeamsInEvent, - type TeamsInEventType, -} from "./endpoints/TeamsSimple"; +import React, { useState, useMemo } from "react"; import useLocalStorage from "./Hooks/LocalStorageHook"; - -const targetTeamNumber = 4590; -const backendPort = 4590; -const refreshIntervalMs = 30000; -const timeMultiplier = 1000; -const sliceStart = 3; -const firstIndex = 0; -const nextMatchLimit = 2; -const noGap = 0; -const matchTimeDefault = 0; -const matchTimeMissing = 0; -const dayInSeconds = 86400; -const notFoundIndex = -1; - -const sortABeforeB = -1; -const sortBBeforeA = 1; -const weightQm = 1; -const weightEf = 2; -const weightQf = 3; -const weightSf = 4; -const weightF = 5; -const weightDefault = 99; - -const targetTeamKey = `frc${targetTeamNumber}`; -const backendBaseUrl = `http://localhost:${backendPort}/fetch?url=`; - -interface TeamRecord { - wins: number; - losses: number; - ties: number; -} - -interface RankItem { - rank: number; - team_key: string; - record: TeamRecord; -} - -interface RankingsResponse { - rankings: RankItem[]; -} - -const urlRankings = (eventKey: string) => - `https://www.thebluealliance.com/api/v3/event/${eventKey}/rankings`; - -const fetchFromProxy = async (targetUrl: string): Promise => { - const fullUrl = `${backendBaseUrl}${encodeURIComponent(targetUrl)}`; - const response = await fetch(fullUrl); - - return response.ok - ? (response.json() as Promise) - : Promise.reject(new Error(`HTTP error status: ${response.status}`)); -}; - -const getLevelWeight = (level: string): number => - level === "qm" - ? weightQm - : level === "ef" - ? weightEf - : level === "qf" - ? weightQf - : level === "sf" - ? weightSf - : level === "f" - ? weightF - : weightDefault; - -const getMatchDisplayName = (match: MatchesSimpleType): string => { - const level = match.comp_level.toUpperCase(); - switch (level) { - case "QM": - return `Quals ${match.match_number}`; - case "QF": - return `Quarterfinal ${match.set_number}, Match ${match.match_number}`; - case "SF": - return `Semifinal ${match.set_number}, Match ${match.match_number}`; - case "F": - return `Finals Match ${match.match_number}`; - case "EF": - return `Octofinal ${match.set_number}, Match ${match.match_number}`; - default: - return `${level} ${match.match_number}`; - } -}; +import { useEventData } from "./Hooks/useEventData"; +import { useMatchProcessing } from "./Hooks/useMatchProcessing"; +import { Header } from "./components/Header"; +import { EventInfoBar } from "./components/EventInfoBar"; +import { CurrentMatchStatus } from "./components/CurrentMatchStatus"; +import { SectionTitle } from "./components/SectionTitle"; +import { FinalResults } from "./components/FinalResults"; +import { MatchCard } from "./components/MatchCard"; +import { EmptyState } from "./components/EmptyState"; +import { noGap } from "./config/frcConfig"; const App: React.FC = () => { const [activeEventKey, setActiveEventKey] = useLocalStorage( @@ -98,137 +18,9 @@ const App: React.FC = () => { "" ); const [inputEventKey, setInputEventKey] = useState(activeEventKey); - const [teams, setTeams] = useState([]); - const [allMatches, setAllMatches] = useState([]); - const [teamRank, setTeamRank] = useState(null); - const [searchStatus, setSearchStatus] = useState< - "idle" | "loading" | "success" | "error" - >("idle"); - - const resetInSearch = () => { - setSearchStatus("loading"); - setAllMatches([]); - setTeams([]); - setTeamRank(null); - }; - - const sortMatches = useCallback( - (a: MatchesSimpleType, b: MatchesSimpleType) => { - const timeA = a.predicted_time ?? a.time ?? matchTimeDefault; - const timeB = b.predicted_time ?? b.time ?? matchTimeDefault; - const weightA = getLevelWeight(a.comp_level); - const weightB = getLevelWeight(b.comp_level); - - if (timeA > matchTimeMissing && timeB > matchTimeMissing) { - return timeA - timeB; - } else if (timeA > matchTimeMissing && timeB === matchTimeMissing) { - return sortABeforeB; - } else if (timeA === matchTimeMissing && timeB > matchTimeMissing) { - return sortBBeforeA; - } else if (weightA !== weightB) { - return weightA - weightB; - } else if (a.comp_level !== "qm") { - if (a.set_number !== b.set_number) { - return a.set_number - b.set_number; - } - return a.match_number - b.match_number; - } - - return a.match_number - b.match_number; - }, - [] - ); - - const performSearch = useCallback( - async (eventKey: string) => { - if (!eventKey) return; - - resetInSearch(); - setActiveEventKey(eventKey); - - try { - const teamsUrl = urlTeamsInEvent(eventKey); - const matchesUrl = urlMatches(eventKey); - const rankingsUrl = urlRankings(eventKey); - - const [teamsData, matchesData, rankingsData] = await Promise.all([ - fetchFromProxy(teamsUrl), - fetchFromProxy(matchesUrl), - fetchFromProxy(rankingsUrl).catch(() => ({ - rankings: [], - })), - ]); - - if (Array.isArray(teamsData) && Array.isArray(matchesData)) { - setTeams(teamsData); - matchesData.sort(sortMatches); - setAllMatches(matchesData); - setTeamRank( - rankingsData.rankings.find((r) => r.team_key === targetTeamKey) ?? - null - ); - setSearchStatus("success"); - } else { - setSearchStatus("error"); - } - } catch { - setSearchStatus("error"); - } - }, - [setActiveEventKey, sortMatches] - ); - - const handleSearchSubmit = (event: React.FormEvent) => { - event.preventDefault(); - const formattedKey = inputEventKey.trim().toLowerCase(); - void performSearch(formattedKey); - }; - - useEffect(() => { - if (activeEventKey && searchStatus === "idle") { - void performSearch(activeEventKey); - } - }, [activeEventKey, performSearch, searchStatus]); - - useEffect(() => { - const intervalId: number | undefined = - searchStatus === "success" && activeEventKey - ? window.setInterval(() => { - const matchesUrl = urlMatches(activeEventKey); - const rankingsUrl = urlRankings(activeEventKey); - - void fetchFromProxy(matchesUrl) - .then((data) => { - if (Array.isArray(data)) { - data.sort(sortMatches); - setAllMatches(data); - } - }) - .catch(console.error); - - void fetchFromProxy(rankingsUrl) - .then((data) => { - const myRank = - data.rankings.find((r) => r.team_key === targetTeamKey) ?? - null; - setTeamRank(myRank); - }) - .catch(console.error); - }, refreshIntervalMs) - : undefined; - - return () => { - if (intervalId !== undefined) { - window.clearInterval(intervalId); - } - }; - }, [activeEventKey, searchStatus, sortMatches]); - const teamNameMap = useMemo(() => { - const map: Map = new Map(); - teams.forEach((team) => map.set(team.key, team.nickname)); - return map; - }, [teams]); + const { teams, allMatches, teamRank, searchStatus, performSearch } = + useEventData(activeEventKey, setActiveEventKey); const { currentGlobalMatch, @@ -237,334 +29,58 @@ const App: React.FC = () => { isEventOver, isTeamDone, isFutureEvent, - } = useMemo(() => { - // normal one - // const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); - - // testing 2025isios - // const currentTimeSecs = Math.floor( - // new Date("2025-10-08T13:44:00").getTime() / timeMultiplier - // ); - - // testing 2025iscmp - // const currentTimeSecs = Math.floor( - // new Date("2025-03-27T10:00:00").getTime() / timeMultiplier - // ); - - // testing 2024iscmp - const currentTimeSecs = Math.floor( - new Date("2024-03-21T18:00:00").getTime() / timeMultiplier - ); - - // testing 2025cmptx - //const currentTimeSecs = Math.floor(new Date("2025-04-19T10:00:00").getTime() / timeMultiplier); - - const getMatchTime = (match: MatchesSimpleType) => - match.predicted_time ?? match.time ?? matchTimeDefault; + } = useMatchProcessing(allMatches); - const futureMatchesArr = allMatches.filter((match) => { - const time = getMatchTime(match); - return time === matchTimeMissing || time > currentTimeSecs; - }); - - const pastMatchesArr = allMatches.filter((match) => { - const time = getMatchTime(match); - return time > matchTimeMissing && time <= currentTimeSecs; - }); - - futureMatchesArr.sort(sortMatches); - - const currentMatch = - futureMatchesArr.length > noGap - ? futureMatchesArr[firstIndex] - : undefined; - - const teamFutureMatches = futureMatchesArr.filter( - (match) => - match.alliances.blue.team_keys.includes(targetTeamKey) || - match.alliances.red.team_keys.includes(targetTeamKey) - ); - - const teamPastMatches = pastMatchesArr.filter( - (match) => - match.alliances.blue.team_keys.includes(targetTeamKey) || - match.alliances.red.team_keys.includes(targetTeamKey) - ); - - const hasEventEnded = - futureMatchesArr.length === noGap && allMatches.length > noGap; - - const hasTeamDone = - !hasEventEnded && - teamFutureMatches.length === noGap && - teamPastMatches.length > noGap; - - const isNextMatchInFarFuture = currentMatch - ? getMatchTime(currentMatch) > currentTimeSecs + dayInSeconds && - getMatchTime(currentMatch) !== matchTimeMissing - : false; + const teamNameMap = useMemo(() => { + const map = new Map(); + teams.forEach((team) => map.set(team.key, team.nickname)); + return map; + }, [teams]); - return { - currentGlobalMatch: currentMatch, - targetTeamMatches: hasTeamDone - ? [] - : teamFutureMatches.slice(firstIndex, nextMatchLimit), - futureMatches: futureMatchesArr, - isEventOver: hasEventEnded, - isTeamDone: hasTeamDone, - isFutureEvent: isNextMatchInFarFuture, - }; - }, [allMatches, sortMatches]); + const handleSearchSubmit = (event: React.FormEvent) => { + event.preventDefault(); + const formattedKey = inputEventKey.trim().toLowerCase(); + void performSearch(formattedKey); + }; return ( -
-
-
-

FRC Dashboard

-
- { - setInputEventKey(event.target.value); - }} - className="flex-1 rounded-md p-3 text-base text-white-900 outline-none ring-2 focus:ring-slate-500 ring-gray-600 search-input" - /> - -
- - {searchStatus === "error" && ( -
- Event not found. -
- )} -
-
+
+
{searchStatus === "success" && ( <> -
- - Event: {activeEventKey} - - - Teams: {teams.length} - -
+ -
-

- {isFutureEvent ? "Upcoming Event" : "Current Field Status"} -

- {currentGlobalMatch !== undefined ? ( -
- - {getMatchDisplayName(currentGlobalMatch)} - - - {isFutureEvent ? "SCHEDULED" : "IN PROGRESS"} - -
- ) : ( -

- {allMatches.length > noGap - ? "Event Concluded" - : "No matches scheduled"} -

- )} -
+ -

- {isEventOver - ? `Final Results: Team ${targetTeamNumber}` - : `Upcoming: Team ${targetTeamNumber}`} -

+ {isEventOver ? ( -
- {teamRank ? ( - <> -
- Final Rank -
-
- #{teamRank.rank} -
-
- Record: {teamRank.record.wins}-{teamRank.record.losses}- - {teamRank.record.ties} -
- - ) : ( -

- Rank data not available for this event. -

- )} -
+ ) : ( <> {targetTeamMatches.length === noGap && ( -
- {isTeamDone ? ( -

- Team {targetTeamNumber} has completed all scheduled - matches for this event (or for today). -

- ) : ( -

- No upcoming matches found for Team {targetTeamNumber}. -

- )} -
+ )} - {targetTeamMatches.map((match) => { - const effectiveTime = match.predicted_time ?? match.time; - const predictedDate = effectiveTime - ? new Date( - effectiveTime * timeMultiplier - ).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }) - : "TBD"; - - const matchIndex = futureMatches.findIndex( - (m) => m.key === match.key - ); - const matchesAway = - matchIndex > notFoundIndex ? matchIndex : noGap; - - const isRedAlliance = - match.alliances.red.team_keys.includes(targetTeamKey); - - return ( -
-
-
- - {getMatchDisplayName(match)} - -
-
-
- {predictedDate} -
-
- {matchesAway <= noGap - ? "PLAYING NOW" - : `IN ${matchesAway} MATCHES`} -
-
-
- -
-
-
- Red Alliance -
- {match.alliances.red.team_keys.map((teamKey) => { - const teamNumber = teamKey.slice(sliceStart); - const teamName = - teamNameMap.get(teamKey) ?? "Unknown"; - const isTargetTeam = teamKey === targetTeamKey; - return ( -
- - {teamNumber} - - - {teamName} - - {isTargetTeam && ( - - YOU - - )} -
- ); - })} -
- -
-
- Blue Alliance -
- {match.alliances.blue.team_keys.map((teamKey) => { - const teamNumber = teamKey.slice(sliceStart); - const teamName = - teamNameMap.get(teamKey) ?? "Unknown"; - const isTargetTeam = teamKey === targetTeamKey; - return ( -
- - {teamNumber} - - - {teamName} - - {isTargetTeam && ( - - YOU - - )} -
- ); - })} -
-
-
- ); - })} + {targetTeamMatches.map((match) => ( + + ))} )} diff --git a/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts b/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts new file mode 100644 index 0000000..6dddf0d --- /dev/null +++ b/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts @@ -0,0 +1,128 @@ +// בס"ד +import { useState, useCallback, useEffect } from "react"; +import { urlMatches, type MatchesSimpleType } from "../endpoints/MatchSimple"; +import { + urlTeamsInEvent, + type TeamsInEventType, +} from "../endpoints/TeamsSimple"; +import { urlRankings } from "../utils/rankingsUtils"; +import { fetchFromProxy } from "../utils/apiUtils"; +import { sortMatches } from "../utils/matchUtils"; +import type { RankItem, RankingsResponse } from "../types"; +import { targetTeamKey, refreshIntervalMs } from "../config/frcConfig"; + +type SearchStatus = "idle" | "searching" | "success" | "error"; + +interface UseEventDataReturn { + teams: TeamsInEventType[]; + allMatches: MatchesSimpleType[]; + teamRank: RankItem | null; + searchStatus: SearchStatus; + performSearch: (eventKey: string) => Promise; + resetSearch: () => void; +} + +export const useEventData = ( + activeEventKey: string, + setActiveEventKey: (key: string) => void +): UseEventDataReturn => { + const [teams, setTeams] = useState([]); + const [allMatches, setAllMatches] = useState([]); + const [teamRank, setTeamRank] = useState(null); + const [searchStatus, setSearchStatus] = useState("idle"); + + const resetSearch = useCallback(() => { + setSearchStatus("searching"); + setAllMatches([]); + setTeams([]); + setTeamRank(null); + }, []); + + const performSearch = useCallback( + async (eventKey: string) => { + if (!eventKey) return; + + resetSearch(); + setActiveEventKey(eventKey); + + try { + const teamsUrl = urlTeamsInEvent(eventKey); + const matchesUrl = urlMatches(eventKey); + const rankingsUrl = urlRankings(eventKey); + + const [teamsData, matchesData, rankingsData] = await Promise.all([ + fetchFromProxy(teamsUrl), + fetchFromProxy(matchesUrl), + fetchFromProxy(rankingsUrl).catch(() => ({ + rankings: [], + })), + ]); + + if (Array.isArray(teamsData) && Array.isArray(matchesData)) { + setTeams(teamsData); + matchesData.sort(sortMatches); + setAllMatches(matchesData); + setTeamRank( + rankingsData.rankings.find((r) => r.team_key === targetTeamKey) ?? + null + ); + setSearchStatus("success"); + } else { + setSearchStatus("error"); + } + } catch { + setSearchStatus("error"); + } + }, + [setActiveEventKey, resetSearch] + ); + + useEffect(() => { + if (activeEventKey && searchStatus === "idle") { + void performSearch(activeEventKey); + } + }, [activeEventKey, performSearch, searchStatus]); + + useEffect(() => { + const intervalId: number | undefined = + searchStatus === "success" && activeEventKey + ? window.setInterval(() => { + const matchesUrl = urlMatches(activeEventKey); + const rankingsUrl = urlRankings(activeEventKey); + + void fetchFromProxy(matchesUrl) + .then((data) => { + if (Array.isArray(data)) { + data.sort(sortMatches); + setAllMatches(data); + } + }) + .catch(console.error); + + void fetchFromProxy(rankingsUrl) + .then((data) => { + const myRank = + data.rankings.find((r) => r.team_key === targetTeamKey) ?? + null; + setTeamRank(myRank); + }) + .catch(console.error); + }, refreshIntervalMs) + : undefined; + + return () => { + if (intervalId !== undefined) { + window.clearInterval(intervalId); + } + }; + }, [activeEventKey, searchStatus]); + + return { + teams, + allMatches, + teamRank, + searchStatus, + performSearch, + resetSearch, + }; +}; diff --git a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts new file mode 100644 index 0000000..69b462a --- /dev/null +++ b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts @@ -0,0 +1,104 @@ +// בס"ד +import { useMemo } from "react"; +import type { MatchesSimpleType } from "../endpoints/MatchSimple"; +import { sortMatches, getMatchTime } from "../utils/matchUtils"; +import { + targetTeamKey, + timeMultiplier, + matchTimeMissing, + dayInSeconds, + firstIndex, + nextMatchLimit, + noGap, +} from "../config/frcConfig"; + +interface UseMatchProcessingReturn { + currentGlobalMatch: MatchesSimpleType | undefined; + targetTeamMatches: MatchesSimpleType[]; + futureMatches: MatchesSimpleType[]; + isEventOver: boolean; + isTeamDone: boolean; + isFutureEvent: boolean; +} + +export const useMatchProcessing = ( + allMatches: MatchesSimpleType[] +): UseMatchProcessingReturn => { + return useMemo(() => { + // i will leave this here so you can check the code with different dates. + +// normal one + const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); + +// testing 2025isios +// const currentTimeSecs = Math.floor( +// new Date("2025-10-08T13:44:00").getTime() / timeMultiplier +// ); + +// testing 2025iscmp +// const currentTimeSecs = Math.floor( +// new Date("2025-03-27T10:00:00").getTime() / timeMultiplier +// ); + +// testing 2024iscmp +// const currentTimeSecs = Math.floor( +// new Date("2024-03-21T18:00:00").getTime() / timeMultiplier +// ); + +// testing 2025cmptx +//const currentTimeSecs = Math.floor(new Date("2025-04-19T10:00:00").getTime() / timeMultiplier); + + const futureMatchesArr = allMatches.filter((match) => { + const time = getMatchTime(match); + return time === matchTimeMissing || time > currentTimeSecs; + }); + + const pastMatchesArr = allMatches.filter((match) => { + const time = getMatchTime(match); + return time > matchTimeMissing && time <= currentTimeSecs; + }); + + futureMatchesArr.sort(sortMatches); + + const currentMatch = + futureMatchesArr.length > noGap + ? futureMatchesArr[firstIndex] + : undefined; + + const teamFutureMatches = futureMatchesArr.filter( + (match) => + match.alliances.blue.team_keys.includes(targetTeamKey) || + match.alliances.red.team_keys.includes(targetTeamKey) + ); + + const teamPastMatches = pastMatchesArr.filter( + (match) => + match.alliances.blue.team_keys.includes(targetTeamKey) || + match.alliances.red.team_keys.includes(targetTeamKey) + ); + + const hasEventEnded = + futureMatchesArr.length === noGap && allMatches.length > noGap; + + const hasTeamDone = + !hasEventEnded && + teamFutureMatches.length === noGap && + teamPastMatches.length > noGap; + + const isNextMatchInFarFuture = currentMatch + ? getMatchTime(currentMatch) > currentTimeSecs + dayInSeconds && + getMatchTime(currentMatch) !== matchTimeMissing + : false; + + return { + currentGlobalMatch: currentMatch, + targetTeamMatches: hasTeamDone + ? [] + : teamFutureMatches.slice(firstIndex, nextMatchLimit), + futureMatches: futureMatchesArr, + isEventOver: hasEventEnded, + isTeamDone: hasTeamDone, + isFutureEvent: isNextMatchInFarFuture, + }; + }, [allMatches]); +}; diff --git a/apps/playoff-schedule/frontend/src/components/Alliance.tsx b/apps/playoff-schedule/frontend/src/components/Alliance.tsx new file mode 100644 index 0000000..2f4b56e --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/Alliance.tsx @@ -0,0 +1,73 @@ +// בס"ד +import type React from "react"; +import { targetTeamKey, sliceStart } from "../config/frcConfig"; + +interface AllianceProps { + teamKeys: string[]; + teamNameMap: Map; + allianceColor: "red" | "blue"; + isTargetTeamInAlliance: boolean; +} + +export const Alliance: React.FC = ({ + teamKeys, + teamNameMap, + allianceColor, + isTargetTeamInAlliance, +}) => { + const colorClasses = { + red: { + border: "border-red-400 dark:border-red-500", + bg: "bg-gray-100 dark:bg-gray-700/50", + text: "text-red-700 dark:text-red-400", + badge: "bg-red-700 dark:bg-red-600", + }, + blue: { + border: "border-blue-400 dark:border-blue-500", + bg: "bg-gray-100 dark:bg-gray-700/50", + text: "text-blue-700 dark:text-blue-400", + badge: "bg-blue-700 dark:bg-blue-600", + }, + }; + + const colors = colorClasses[allianceColor]; + + return ( +
+
+ {allianceColor === "red" ? "Red" : "Blue"} Alliance +
+ {teamKeys.map((teamKey) => { + const teamNumber = teamKey.slice(sliceStart); + const teamName = teamNameMap.get(teamKey) ?? "Unknown"; + const isTargetTeam = teamKey === targetTeamKey; + return ( +
+ + {teamNumber} + + + {teamName} + + {isTargetTeam && ( + + YOU + + )} +
+ ); + })} +
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx b/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx new file mode 100644 index 0000000..69d0fa3 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx @@ -0,0 +1,53 @@ +// בס"ד +import type React from "react"; +import type { MatchesSimpleType } from "../endpoints/MatchSimple"; +import { getMatchDisplayName } from "../utils/matchDisplayUtils"; +import { noGap } from "../config/frcConfig"; + +interface CurrentMatchStatusProps { + currentMatch: MatchesSimpleType | undefined; + isFutureEvent: boolean; + allMatchesCount: number; +} + +export const CurrentMatchStatus: React.FC = ({ + currentMatch, + isFutureEvent, + allMatchesCount, +}) => { + return ( +
+

+ {isFutureEvent ? "Upcoming Event" : "Current Field Status"} +

+ {currentMatch !== undefined ? ( +
+ + {getMatchDisplayName(currentMatch)} + + + {isFutureEvent ? "SCHEDULED" : "IN PROGRESS"} + +
+ ) : ( +

+ {allMatchesCount > noGap ? "Event Concluded" : "No matches scheduled"} +

+ )} +
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/EmptyState.tsx b/apps/playoff-schedule/frontend/src/components/EmptyState.tsx new file mode 100644 index 0000000..4073176 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/EmptyState.tsx @@ -0,0 +1,22 @@ +// בס"ד +import type React from "react"; +import { targetTeamNumber } from "../config/frcConfig"; + +interface EmptyStateProps { + isTeamDone: boolean; +} + +export const EmptyState: React.FC = ({ isTeamDone }) => { + return ( +
+ {isTeamDone ? ( +

+ Team {targetTeamNumber} has completed all scheduled matches for this + event (or for today). +

+ ) : ( +

No upcoming matches found for Team {targetTeamNumber}.

+ )} +
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/EventInfoBar.tsx b/apps/playoff-schedule/frontend/src/components/EventInfoBar.tsx new file mode 100644 index 0000000..c7e066c --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/EventInfoBar.tsx @@ -0,0 +1,23 @@ +// בס"ד +import type React from "react"; + +interface EventInfoBarProps { + eventKey: string; + teamCount: number; +} + +export const EventInfoBar: React.FC = ({ + eventKey, + teamCount, +}) => { + return ( +
+ + Event: {eventKey} + + + Teams: {teamCount} + +
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/FinalResults.tsx b/apps/playoff-schedule/frontend/src/components/FinalResults.tsx new file mode 100644 index 0000000..ee668e5 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/FinalResults.tsx @@ -0,0 +1,32 @@ +// בס"ד +import type React from "react"; +import type { RankItem } from "../types"; + +interface FinalResultsProps { + teamRank: RankItem | null; +} + +export const FinalResults: React.FC = ({ teamRank }) => { + return ( +
+ {teamRank ? ( + <> +
+ Final Rank +
+
+ #{teamRank.rank} +
+
+ Record: {teamRank.record.wins}-{teamRank.record.losses}- + {teamRank.record.ties} +
+ + ) : ( +

+ Rank data not available for this event. +

+ )} +
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/Header.tsx b/apps/playoff-schedule/frontend/src/components/Header.tsx new file mode 100644 index 0000000..dde42e4 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/Header.tsx @@ -0,0 +1,48 @@ +// בס"ד +import React from "react"; + +interface HeaderProps { + inputEventKey: string; + onInputChange: (value: string) => void; + onSearchSubmit: (event: React.FormEvent) => void; + searchStatus: "idle" | "searching" | "success" | "error"; +} + +export const Header: React.FC = ({ + inputEventKey, + onInputChange, + onSearchSubmit, + searchStatus, +}) => { + return ( +
+
+

+ GB Playoff schedule +

+
+ onInputChange(event.target.value)} + className="search-input flex-1 rounded-md border-2 border-slate-400 dark:border-slate-500 bg-slate-600 dark:bg-slate-700 p-3 text-base text-white outline-none transition-colors placeholder:text-slate-300 dark:placeholder:text-slate-400 focus:border-blue-400 dark:focus:border-blue-500" + /> + +
+ + {searchStatus === "error" && ( +
+ Event not found. +
+ )} +
+
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/MatchCard.tsx b/apps/playoff-schedule/frontend/src/components/MatchCard.tsx new file mode 100644 index 0000000..9edcad4 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/MatchCard.tsx @@ -0,0 +1,72 @@ +// בס"ד +import type React from "react"; +import type { MatchesSimpleType } from "../endpoints/MatchSimple"; +import { getMatchDisplayName } from "../utils/matchDisplayUtils"; +import { formatMatchTime } from "../utils/matchDisplayUtils"; +import { Alliance } from "./Alliance"; +import { + targetTeamKey, + timeMultiplier, + nextMatchLimit, + noGap, + notFoundIndex, +} from "../config/frcConfig"; + +interface MatchCardProps { + match: MatchesSimpleType; + teamNameMap: Map; + futureMatches: MatchesSimpleType[]; +} + +export const MatchCard: React.FC = ({ + match, + teamNameMap, + futureMatches, +}) => { + const effectiveTime = match.predicted_time ?? match.time ?? undefined; + const predictedDate = formatMatchTime(effectiveTime, timeMultiplier); + + const matchIndex = futureMatches.findIndex((m) => m.key === match.key); + const matchesAway = matchIndex > notFoundIndex ? matchIndex : noGap; + + const isRedAlliance = match.alliances.red.team_keys.includes(targetTeamKey); + + return ( +
+
+
+ + {getMatchDisplayName(match)} + +
+
+
+ {predictedDate} +
+
+ {matchesAway <= noGap ? "PLAYING NOW" : `IN ${matchesAway} MATCHES`} +
+
+
+ +
+ + +
+
+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/components/SectionTitle.tsx b/apps/playoff-schedule/frontend/src/components/SectionTitle.tsx new file mode 100644 index 0000000..9e40028 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/components/SectionTitle.tsx @@ -0,0 +1,17 @@ +// בס"ד +import type React from "react"; +import { targetTeamNumber } from "../config/frcConfig"; + +interface SectionTitleProps { + isEventOver: boolean; +} + +export const SectionTitle: React.FC = ({ isEventOver }) => { + return ( +

+ {isEventOver + ? `Final Results: Team ${targetTeamNumber}` + : `Upcoming: Team ${targetTeamNumber}`} +

+ ); +}; diff --git a/apps/playoff-schedule/frontend/src/config/frcConfig.ts b/apps/playoff-schedule/frontend/src/config/frcConfig.ts index 23930c5..212f3d9 100644 --- a/apps/playoff-schedule/frontend/src/config/frcConfig.ts +++ b/apps/playoff-schedule/frontend/src/config/frcConfig.ts @@ -24,7 +24,14 @@ export const weightQf = 3; export const weightSf = 4; export const weightF = 5; export const weightDefault = 99; -export const defaultLevelWeight =1; -export const levelWeights = {1:weightQm,2:weightEf,3:weightQf,4:weightSf,5:weightF} +export const defaultLevelWeight = 1; +export const levelWeights = { + 1: weightQm, + 2: weightEf, + 3: weightQf, + 4: weightSf, + 5: weightF, +}; export const targetTeamKey = `frc${targetTeamNumber}`; export const backendBaseUrl = `http://localhost:${backendPort}/fetch?url=`; +export const notFoundIndex = -1; diff --git a/apps/playoff-schedule/frontend/src/styles/classes.ts b/apps/playoff-schedule/frontend/src/styles/classes.ts new file mode 100644 index 0000000..bfe614d --- /dev/null +++ b/apps/playoff-schedule/frontend/src/styles/classes.ts @@ -0,0 +1,43 @@ +// בס"ד +// Common CSS class combinations for consistency + +export const classes = { + // Layout + container: "mx-auto max-w-4xl px-5", + mainContent: "mx-auto mt-6 max-w-4xl px-5", + + // Cards + card: "rounded-xl bg-white shadow-sm", + cardHover: "transition-shadow hover:shadow-md", + cardPadding: "p-5", + + // Status badges + badge: "rounded-full px-3 py-1 text-xs font-bold", + badgeBlue: "bg-blue-50 text-blue-500", + badgeOrange: "bg-orange-50 text-orange-700", + badgeRed: "bg-red-50 text-red-800", + + // Text + title: "text-3xl font-normal", + sectionTitle: "mb-5 border-b-2 border-gray-200 pb-2 text-2xl text-gray-800", + subtitle: "text-sm font-bold uppercase tracking-wider", + + // Colors + textGray: "text-gray-900", + textGrayLight: "text-gray-500", + textGrayLighter: "text-gray-400", + + // Borders + borderLeft: "border-l-[6px]", + borderBlue: "border-blue-500", + borderAmber: "border-amber-500", + + // Search input + searchInput: + "flex-1 rounded-md border-2 border-slate-400 bg-slate-600 p-3 text-base text-white outline-none transition-colors placeholder:text-slate-300 focus:border-blue-400", + + // Buttons + button: + "rounded-md px-6 py-3 text-base font-bold transition disabled:opacity-70", + buttonPrimary: "bg-slate-500 text-white hover:bg-slate-400", +} as const; diff --git a/apps/playoff-schedule/frontend/src/types.ts b/apps/playoff-schedule/frontend/src/types.ts new file mode 100644 index 0000000..aa9fcb1 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/types.ts @@ -0,0 +1,46 @@ +// בס"ד + +export interface Alliance { + score: number; + team_keys: string[]; + surrogate_team_keys?: string[]; + dq_team_keys?: string[]; +} + +export interface Alliances { + blue: Alliance; + red: Alliance; +} + +export interface MatchesSimpleType { + key: string; + comp_level: string; + set_number: number; + match_number: number; + alliances: Alliances; + winning_alliance: string; + event_key: string; + time?: number; + predicted_time?: number; + actual_time?: number; +} + +export interface TeamsInEventType { + key: string; + team_number: number; + nickname: string; + name: string; + city: string; + state_prov: string; + country: string; +} + +export interface RankItem { + rank: number; + team_key: string; + record: { wins: number; losses: number; ties: number }; +} + +export interface RankingsResponse { + rankings: RankItem[]; +} diff --git a/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx b/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx index 94de78b..735fc9e 100644 --- a/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx +++ b/apps/playoff-schedule/frontend/src/utils/TypeUtils.tsx @@ -1,3 +1,4 @@ +// בס"ד import * as t from "io-ts"; export const numberOrNull = t.union([t.number, t.null]); //for if type is null export const stringOrNull = t.union([t.string, t.null]); //for if type is null diff --git a/apps/playoff-schedule/frontend/src/utils/apiUtils.ts b/apps/playoff-schedule/frontend/src/utils/apiUtils.ts new file mode 100644 index 0000000..c1114df --- /dev/null +++ b/apps/playoff-schedule/frontend/src/utils/apiUtils.ts @@ -0,0 +1,11 @@ +// בס"ד +import { backendBaseUrl } from "../config/frcConfig"; + +export const fetchFromProxy = async (targetUrl: string): Promise => { + const fullUrl = `${backendBaseUrl}${encodeURIComponent(targetUrl)}`; + const response = await fetch(fullUrl); + + return response.ok + ? (response.json() as Promise) + : Promise.reject(new Error(`HTTP error status: ${response.status}`)); +}; diff --git a/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts b/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts new file mode 100644 index 0000000..ccb39e1 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts @@ -0,0 +1,31 @@ +// בס"ד +import type { MatchesSimpleType } from "../endpoints/MatchSimple"; + +export const getMatchDisplayName = (match: MatchesSimpleType): string => { + const level = match.comp_level.toUpperCase(); + switch (level) { + case "QM": + return `Quals ${match.match_number}`; + case "QF": + return `Quarterfinal ${match.set_number}, Match ${match.match_number}`; + case "SF": + return `Semifinal ${match.set_number}, Match ${match.match_number}`; + case "F": + return `Finals Match ${match.match_number}`; + case "EF": + return `Octofinal ${match.set_number}, Match ${match.match_number}`; + default: + return `${level} ${match.match_number}`; + } +}; + +export const formatMatchTime = ( + time: number | undefined, + timeMultiplier: number +): string => { + if (!time) return "TBD"; + return new Date(time * timeMultiplier).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); +}; diff --git a/apps/playoff-schedule/frontend/src/utils/matchUtils.tsx b/apps/playoff-schedule/frontend/src/utils/matchUtils.tsx deleted file mode 100644 index 5d29fec..0000000 --- a/apps/playoff-schedule/frontend/src/utils/matchUtils.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// בס"ד -import type { MatchesSimpleType } from "../endpoints/MatchSimple"; - -const millisecToSec = 1000; -const startIndex = 0; -const endIndex = 2; - -export const findNextMatches = (allMatches: MatchesSimpleType[]): MatchesSimpleType[] => { -const currentTime = Math.floor(Date.now() / millisecToSec); - -const futureMatches = allMatches.filter(match => { - if (match.actual_time !== null && match.actual_time < currentTime) { - return false; -} - if (match.predicted_time === null) { - return false; -} - return true; -}); - - futureMatches.sort((a, b) => { - return a.predicted_time! - b.predicted_time!; -}); -return futureMatches.slice(startIndex, endIndex); -}; \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts b/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts new file mode 100644 index 0000000..e88bb92 --- /dev/null +++ b/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts @@ -0,0 +1,3 @@ +// בס"ד +export const urlRankings = (eventKey: string) => + `https://www.thebluealliance.com/api/v3/event/${eventKey}/rankings`; From e4acc8b775951192bca31a3770a062218cef04fd Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Sun, 4 Jan 2026 21:03:43 +0200 Subject: [PATCH 5/8] added green to see better our team --- .../frontend/src/Hooks/useMatchProcessing.ts | 34 +++++++++---------- .../frontend/src/components/Alliance.tsx | 4 ++- .../src/components/CurrentMatchStatus.tsx | 8 +++-- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts index 69b462a..787d7c4 100644 --- a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts +++ b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts @@ -25,28 +25,28 @@ export const useMatchProcessing = ( allMatches: MatchesSimpleType[] ): UseMatchProcessingReturn => { return useMemo(() => { - // i will leave this here so you can check the code with different dates. + // i will leave this here so you can check the code with different dates. -// normal one - const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); + // normal one + const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); -// testing 2025isios -// const currentTimeSecs = Math.floor( -// new Date("2025-10-08T13:44:00").getTime() / timeMultiplier -// ); + // testing 2025isios + // const currentTimeSecs = Math.floor( + // new Date("2025-10-08T13:44:00").getTime() / timeMultiplier + // ); -// testing 2025iscmp -// const currentTimeSecs = Math.floor( -// new Date("2025-03-27T10:00:00").getTime() / timeMultiplier -// ); + // testing 2025iscmp + // const currentTimeSecs = Math.floor( + // new Date("2025-03-27T10:00:00").getTime() / timeMultiplier + // ); -// testing 2024iscmp -// const currentTimeSecs = Math.floor( -// new Date("2024-03-21T18:00:00").getTime() / timeMultiplier -// ); + // testing 2024iscmp + // const currentTimeSecs = Math.floor( + // new Date("2024-03-21T15:00:00").getTime() / timeMultiplier + // ); -// testing 2025cmptx -//const currentTimeSecs = Math.floor(new Date("2025-04-19T10:00:00").getTime() / timeMultiplier); + // testing 2025cmptx + //const currentTimeSecs = Math.floor(new Date("2025-04-19T10:00:00").getTime() / timeMultiplier); const futureMatchesArr = allMatches.filter((match) => { const time = getMatchTime(match); diff --git a/apps/playoff-schedule/frontend/src/components/Alliance.tsx b/apps/playoff-schedule/frontend/src/components/Alliance.tsx index 2f4b56e..8b8683b 100644 --- a/apps/playoff-schedule/frontend/src/components/Alliance.tsx +++ b/apps/playoff-schedule/frontend/src/components/Alliance.tsx @@ -55,7 +55,9 @@ export const Alliance: React.FC = ({ {teamNumber} - + {teamName} {isTargetTeam && ( diff --git a/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx b/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx index 69d0fa3..6edd4f5 100644 --- a/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx +++ b/apps/playoff-schedule/frontend/src/components/CurrentMatchStatus.tsx @@ -18,12 +18,16 @@ export const CurrentMatchStatus: React.FC = ({ return (

{isFutureEvent ? "Upcoming Event" : "Current Field Status"} From 544a992c37555bd18949962d5ef933d10c5b0989 Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Wed, 7 Jan 2026 10:50:45 +0200 Subject: [PATCH 6/8] fixed most of the staff yoni told me to --- apps/playoff-schedule/backend/src/main.ts | 36 -------- .../backend/src/routes/index.ts | 5 +- .../backend/src/routes/tba.ts | 73 ++++++--------- .../backend/src/utils/tbaClient.ts | 27 ++++++ apps/playoff-schedule/frontend/src/App.tsx | 8 +- .../frontend/src/Hooks/useEventData.ts | 89 ++++++++++--------- .../frontend/src/Hooks/useMatchProcessing.ts | 10 +-- .../frontend/src/components/Alliance.tsx | 4 +- .../frontend/src/components/MatchCard.tsx | 2 +- .../frontend/src/config/frcConfig.ts | 2 +- .../frontend/src/endpoints/MatchSimple.tsx | 2 +- .../frontend/src/endpoints/TeamsSimple.tsx | 3 +- .../frontend/src/utils/apiUtils.ts | 16 ++-- .../frontend/src/utils/matchDisplayUtils.ts | 2 +- .../frontend/src/utils/rankingsUtils.ts | 2 +- 15 files changed, 134 insertions(+), 147 deletions(-) create mode 100644 apps/playoff-schedule/backend/src/utils/tbaClient.ts diff --git a/apps/playoff-schedule/backend/src/main.ts b/apps/playoff-schedule/backend/src/main.ts index ed1e5be..8b9058c 100644 --- a/apps/playoff-schedule/backend/src/main.ts +++ b/apps/playoff-schedule/backend/src/main.ts @@ -3,7 +3,6 @@ import express from "express"; import { apiRouter } from "./routes"; import cors from "cors"; import dotenv from "dotenv"; -import { StatusCodes } from "http-status-codes"; dotenv.config(); const app = express(); @@ -25,41 +24,6 @@ if (apiKey === undefined) { throw new Error("TBA_API_KEY is not defined."); } -export const fetchData = async (url: string): Promise => { - const response = await fetch(url, { - method: "GET", - headers: { - "X-TBA-Auth-Key": apiKey, - "Content-Type": "application/json", - }, - mode: "cors", - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data; -}; - -app.get("/fetch", async (req, res) => { - try { - const encodedUrl = req.query.url; - if (!encodedUrl || typeof encodedUrl !== "string") { - res.status(StatusCodes.BAD_REQUEST).json({ error: "missing url param" }); - return; - } - const fullUrl = decodeURIComponent(encodedUrl); - - const data = await fetchData(fullUrl); - res.status(StatusCodes.OK).json(data); - } catch (error) { - console.error("Error in /fetch:", error); - res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ error: "Failed to fetch data" }); - } -}); - app.listen(port, () => { console.log(`Production server running at http://localhost:${port}`); }); diff --git a/apps/playoff-schedule/backend/src/routes/index.ts b/apps/playoff-schedule/backend/src/routes/index.ts index b62ad50..a7c0f29 100644 --- a/apps/playoff-schedule/backend/src/routes/index.ts +++ b/apps/playoff-schedule/backend/src/routes/index.ts @@ -1,9 +1,12 @@ // בס"ד import { Router } from "express"; import { StatusCodes } from "http-status-codes"; +import { tbaRouter } from "./tba"; export const apiRouter = Router(); apiRouter.get("/health", (req, res) => { res.status(StatusCodes.OK).send({ message: "Healthy!" }); -}); \ No newline at end of file +}); + +apiRouter.use("/tba", tbaRouter); \ No newline at end of file diff --git a/apps/playoff-schedule/backend/src/routes/tba.ts b/apps/playoff-schedule/backend/src/routes/tba.ts index 270cbbd..59ac2e9 100644 --- a/apps/playoff-schedule/backend/src/routes/tba.ts +++ b/apps/playoff-schedule/backend/src/routes/tba.ts @@ -1,53 +1,38 @@ // בס"ד -import { Router } from "express"; +import { Router, type Request, type Response } from "express"; import { StatusCodes } from "http-status-codes"; +import { fetchTba } from "../utils/tbaClient"; const tbaRouter = Router(); -const getApiKey = (): string => { - const key = process.env.TBA_API_KEY; - if (!key) { - throw new Error("Missing TBA_API_KEY in environment variables"); - } - return key; -}; - -export const fetchData = async (url: string): Promise => { - const response = await fetch(url, { - method: "GET", - headers: { - "X-TBA-Auth-Key": getApiKey(), - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error, status: ${response.status}`); - } - - const data = await response.json(); - return data; -}; - -tbaRouter.get("/fetch", async (req, res) => { - try { - const encodedUrl = req.query.url; - - if (!encodedUrl || typeof encodedUrl !== "string") { - res.status(StatusCodes.BAD_REQUEST).json({ error: "missing url param" }); - return; +const tbaHandler = + (pathBuilder: (req: Request) => string) => + async (req: Request, res: Response): Promise => { + try { + const path = pathBuilder(req); + const data = await fetchTba(path); + res.status(StatusCodes.OK).json(data); + } catch (error) { + console.error("Error in tba handler:", error); + res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json({ error: "Failed to fetch data" }); } + }; + +tbaRouter.get( + "/events/:eventKey/teams", + tbaHandler((req) => `/event/${req.params.eventKey}/teams/simple`) +); - const fullUrl = decodeURIComponent(encodedUrl); +tbaRouter.get( + "/events/:eventKey/matches", + tbaHandler((req) => `/event/${req.params.eventKey}/matches/simple`) +); - const data = await fetchData(fullUrl); - res.status(StatusCodes.OK).json(data); - } catch (error) { - console.error("Error in /tba/fetch:", error); - res - .status(StatusCodes.INTERNAL_SERVER_ERROR) - .json({ error: "Failed to fetch data" }); - } -}); +tbaRouter.get( + "/events/:eventKey/rankings", + tbaHandler((req) => `/event/${req.params.eventKey}/rankings`) +); -export { tbaRouter }; +export { tbaRouter, tbaHandler }; diff --git a/apps/playoff-schedule/backend/src/utils/tbaClient.ts b/apps/playoff-schedule/backend/src/utils/tbaClient.ts new file mode 100644 index 0000000..88498e5 --- /dev/null +++ b/apps/playoff-schedule/backend/src/utils/tbaClient.ts @@ -0,0 +1,27 @@ +// Shared TBA fetcher to avoid duplicating auth + headers +const TBA_API_BASE = "https://www.thebluealliance.com/api/v3"; + +const getApiKey = (): string => { + const key = process.env.TBA_API_KEY; + if (!key) { + throw new Error("Missing TBA_API_KEY in environment variables"); + } + return key; +}; + +export const fetchTba = async (path: string): Promise => { + const response = await fetch(`${TBA_API_BASE}${path}`, { + method: "GET", + headers: { + "X-TBA-Auth-Key": getApiKey(), + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error, status: ${response.status}`); + } + + return response.json() as Promise; +}; + diff --git a/apps/playoff-schedule/frontend/src/App.tsx b/apps/playoff-schedule/frontend/src/App.tsx index 071e8c1..cdfed66 100644 --- a/apps/playoff-schedule/frontend/src/App.tsx +++ b/apps/playoff-schedule/frontend/src/App.tsx @@ -32,9 +32,11 @@ const App: React.FC = () => { } = useMatchProcessing(allMatches); const teamNameMap = useMemo(() => { - const map = new Map(); - teams.forEach((team) => map.set(team.key, team.nickname)); - return map; + const record: Record = {}; + teams.forEach((team) => { + record[team.key] = team.nickname; + }); + return record; }, [teams]); const handleSearchSubmit = (event: React.FormEvent) => { diff --git a/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts b/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts index 6dddf0d..35f0945 100644 --- a/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts +++ b/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts @@ -11,7 +11,7 @@ import { sortMatches } from "../utils/matchUtils"; import type { RankItem, RankingsResponse } from "../types"; import { targetTeamKey, refreshIntervalMs } from "../config/frcConfig"; -type SearchStatus = "idle" | "searching" | "success" | "error"; +export type SearchStatus = "idle" | "searching" | "success" | "error"; interface UseEventDataReturn { teams: TeamsInEventType[]; @@ -38,6 +38,20 @@ export const useEventData = ( setTeamRank(null); }, []); + const fetchEventData = useCallback(async (eventKey: string) => { + const teamsUrl = urlTeamsInEvent(eventKey); + const matchesUrl = urlMatches(eventKey); + const rankingsUrl = urlRankings(eventKey); + + return Promise.all([ + fetchFromProxy(teamsUrl), + fetchFromProxy(matchesUrl), + fetchFromProxy(rankingsUrl).catch(() => ({ + rankings: [], + })), + ]); + }, []); + const performSearch = useCallback( async (eventKey: string) => { if (!eventKey) return; @@ -46,17 +60,8 @@ export const useEventData = ( setActiveEventKey(eventKey); try { - const teamsUrl = urlTeamsInEvent(eventKey); - const matchesUrl = urlMatches(eventKey); - const rankingsUrl = urlRankings(eventKey); - - const [teamsData, matchesData, rankingsData] = await Promise.all([ - fetchFromProxy(teamsUrl), - fetchFromProxy(matchesUrl), - fetchFromProxy(rankingsUrl).catch(() => ({ - rankings: [], - })), - ]); + const [teamsData, matchesData, rankingsData] = + await fetchEventData(eventKey); if (Array.isArray(teamsData) && Array.isArray(matchesData)) { setTeams(teamsData); @@ -74,7 +79,7 @@ export const useEventData = ( setSearchStatus("error"); } }, - [setActiveEventKey, resetSearch] + [setActiveEventKey, resetSearch, fetchEventData] ); useEffect(() => { @@ -84,37 +89,33 @@ export const useEventData = ( }, [activeEventKey, performSearch, searchStatus]); useEffect(() => { - const intervalId: number | undefined = - searchStatus === "success" && activeEventKey - ? window.setInterval(() => { - const matchesUrl = urlMatches(activeEventKey); - const rankingsUrl = urlRankings(activeEventKey); - - void fetchFromProxy(matchesUrl) - .then((data) => { - if (Array.isArray(data)) { - data.sort(sortMatches); - setAllMatches(data); - } - }) - .catch(console.error); - - void fetchFromProxy(rankingsUrl) - .then((data) => { - const myRank = - data.rankings.find((r) => r.team_key === targetTeamKey) ?? - null; - setTeamRank(myRank); - }) - .catch(console.error); - }, refreshIntervalMs) - : undefined; - - return () => { - if (intervalId !== undefined) { - window.clearInterval(intervalId); - } - }; + if (!(searchStatus === "success" && activeEventKey)) { + return; + } + + const intervalId = window.setInterval(() => { + const matchesUrl = urlMatches(activeEventKey); + const rankingsUrl = urlRankings(activeEventKey); + + void fetchFromProxy(matchesUrl) + .then((data) => { + if (Array.isArray(data)) { + data.sort(sortMatches); + setAllMatches(data); + } + }) + .catch(console.error); + + void fetchFromProxy(rankingsUrl) + .then((data) => { + const myRank = + data.rankings.find((r) => r.team_key === targetTeamKey) ?? null; + setTeamRank(myRank); + }) + .catch(console.error); + }, refreshIntervalMs); + + return () => window.clearInterval(intervalId); }, [activeEventKey, searchStatus]); return { diff --git a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts index 787d7c4..710b390 100644 --- a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts +++ b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts @@ -28,17 +28,17 @@ export const useMatchProcessing = ( // i will leave this here so you can check the code with different dates. // normal one - const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); + //const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); // testing 2025isios // const currentTimeSecs = Math.floor( // new Date("2025-10-08T13:44:00").getTime() / timeMultiplier // ); - // testing 2025iscmp - // const currentTimeSecs = Math.floor( - // new Date("2025-03-27T10:00:00").getTime() / timeMultiplier - // ); + //testing 2025iscmp + const currentTimeSecs = Math.floor( + new Date("2025-03-27T10:00:00").getTime() / timeMultiplier + ); // testing 2024iscmp // const currentTimeSecs = Math.floor( diff --git a/apps/playoff-schedule/frontend/src/components/Alliance.tsx b/apps/playoff-schedule/frontend/src/components/Alliance.tsx index 8b8683b..bfbf59f 100644 --- a/apps/playoff-schedule/frontend/src/components/Alliance.tsx +++ b/apps/playoff-schedule/frontend/src/components/Alliance.tsx @@ -4,7 +4,7 @@ import { targetTeamKey, sliceStart } from "../config/frcConfig"; interface AllianceProps { teamKeys: string[]; - teamNameMap: Map; + teamNameMap: Record; allianceColor: "red" | "blue"; isTargetTeamInAlliance: boolean; } @@ -43,7 +43,7 @@ export const Alliance: React.FC = ({

{teamKeys.map((teamKey) => { const teamNumber = teamKey.slice(sliceStart); - const teamName = teamNameMap.get(teamKey) ?? "Unknown"; + const teamName = teamNameMap[teamKey] ?? "Unknown"; const isTargetTeam = teamKey === targetTeamKey; return (
; + teamNameMap: Record; futureMatches: MatchesSimpleType[]; } diff --git a/apps/playoff-schedule/frontend/src/config/frcConfig.ts b/apps/playoff-schedule/frontend/src/config/frcConfig.ts index 212f3d9..e005560 100644 --- a/apps/playoff-schedule/frontend/src/config/frcConfig.ts +++ b/apps/playoff-schedule/frontend/src/config/frcConfig.ts @@ -33,5 +33,5 @@ export const levelWeights = { 5: weightF, }; export const targetTeamKey = `frc${targetTeamNumber}`; -export const backendBaseUrl = `http://localhost:${backendPort}/fetch?url=`; +export const backendBaseUrl = `http://localhost:${backendPort}/api/v1/tba`; export const notFoundIndex = -1; diff --git a/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx index b7dc540..276b0a7 100644 --- a/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx +++ b/apps/playoff-schedule/frontend/src/endpoints/MatchSimple.tsx @@ -23,5 +23,5 @@ export const matchSimple = t.type({ export type MatchesSimpleType = typeof matchSimple._A; export const urlMatches = (eventKey: string) => - `https://www.thebluealliance.com/api/v3/event/${eventKey}/matches/simple`; + `/events/${eventKey}/matches`; diff --git a/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx b/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx index 48984d4..f89d2c8 100644 --- a/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx +++ b/apps/playoff-schedule/frontend/src/endpoints/TeamsSimple.tsx @@ -13,4 +13,5 @@ export const simpleTeamsInEvent = t.type({//did }); export type TeamsInEventType = typeof simpleTeamsInEvent._A; -export const urlTeamsInEvent = (eventKey:string)=>`https://www.thebluealliance.com/api/v3/event/${eventKey}/teams/simple`; \ No newline at end of file +export const urlTeamsInEvent = (eventKey: string) => + `/events/${eventKey}/teams`; \ No newline at end of file diff --git a/apps/playoff-schedule/frontend/src/utils/apiUtils.ts b/apps/playoff-schedule/frontend/src/utils/apiUtils.ts index c1114df..ceebae7 100644 --- a/apps/playoff-schedule/frontend/src/utils/apiUtils.ts +++ b/apps/playoff-schedule/frontend/src/utils/apiUtils.ts @@ -1,11 +1,15 @@ // בס"ד import { backendBaseUrl } from "../config/frcConfig"; -export const fetchFromProxy = async (targetUrl: string): Promise => { - const fullUrl = `${backendBaseUrl}${encodeURIComponent(targetUrl)}`; - const response = await fetch(fullUrl); +const joinUrl = (path: string): string => + `${backendBaseUrl}${path.startsWith("/") ? path : `/${path}`}`; - return response.ok - ? (response.json() as Promise) - : Promise.reject(new Error(`HTTP error status: ${response.status}`)); +export const fetchFromProxy = async (targetPath: string): Promise => { + const fullUrl = joinUrl(targetPath); + return fetch(fullUrl).then((response) => { + if (response.ok) { + return response.json() as Promise; + } + throw new Error(`HTTP error status: ${response.status}`); + }); }; diff --git a/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts b/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts index ccb39e1..7d53d56 100644 --- a/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts +++ b/apps/playoff-schedule/frontend/src/utils/matchDisplayUtils.ts @@ -13,7 +13,7 @@ export const getMatchDisplayName = (match: MatchesSimpleType): string => { case "F": return `Finals Match ${match.match_number}`; case "EF": - return `Octofinal ${match.set_number}, Match ${match.match_number}`; + return `First Round ${match.set_number}, Match ${match.match_number}`; default: return `${level} ${match.match_number}`; } diff --git a/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts b/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts index e88bb92..7f6082f 100644 --- a/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts +++ b/apps/playoff-schedule/frontend/src/utils/rankingsUtils.ts @@ -1,3 +1,3 @@ // בס"ד export const urlRankings = (eventKey: string) => - `https://www.thebluealliance.com/api/v3/event/${eventKey}/rankings`; + `/events/${eventKey}/rankings`; From 0cf47ed66bde4e8937ff6dc2ef39987b4fc017ff Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Wed, 7 Jan 2026 10:51:42 +0200 Subject: [PATCH 7/8] switched to curr time --- .../frontend/src/Hooks/useMatchProcessing.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts index 710b390..2c20bee 100644 --- a/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts +++ b/apps/playoff-schedule/frontend/src/Hooks/useMatchProcessing.ts @@ -28,7 +28,7 @@ export const useMatchProcessing = ( // i will leave this here so you can check the code with different dates. // normal one - //const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); + const currentTimeSecs = Math.floor(Date.now() / timeMultiplier); // testing 2025isios // const currentTimeSecs = Math.floor( @@ -36,9 +36,9 @@ export const useMatchProcessing = ( // ); //testing 2025iscmp - const currentTimeSecs = Math.floor( - new Date("2025-03-27T10:00:00").getTime() / timeMultiplier - ); + // const currentTimeSecs = Math.floor( + // new Date("2025-03-27T10:00:00").getTime() / timeMultiplier + // ); // testing 2024iscmp // const currentTimeSecs = Math.floor( From 266e579a8d91ecf0f324b02c0f6bdc6acbf03a6e Mon Sep 17 00:00:00 2001 From: yoavs8 Date: Thu, 8 Jan 2026 17:00:05 +0200 Subject: [PATCH 8/8] fixed the eslint problem --- apps/playoff-schedule/backend/src/routes/tba.ts | 15 ++++++--------- .../backend/src/utils/tbaClient.ts | 3 +-- apps/playoff-schedule/frontend/src/App.tsx | 3 ++- .../frontend/src/Hooks/LocalStorageHook.tsx | 12 ++++++++---- .../frontend/src/Hooks/useEventData.ts | 8 +++++--- .../frontend/src/components/FinalResults.tsx | 2 +- .../frontend/src/components/Header.tsx | 4 ++-- 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/apps/playoff-schedule/backend/src/routes/tba.ts b/apps/playoff-schedule/backend/src/routes/tba.ts index 59ac2e9..4e918e6 100644 --- a/apps/playoff-schedule/backend/src/routes/tba.ts +++ b/apps/playoff-schedule/backend/src/routes/tba.ts @@ -3,13 +3,12 @@ import { Router, type Request, type Response } from "express"; import { StatusCodes } from "http-status-codes"; import { fetchTba } from "../utils/tbaClient"; -const tbaRouter = Router(); +export const tbaRouter = Router(); -const tbaHandler = - (pathBuilder: (req: Request) => string) => +const createTbaHandler = (name: string, suffix?: string) => async (req: Request, res: Response): Promise => { try { - const path = pathBuilder(req); + const path = `/event/${req.params.eventKey}/${name}${suffix ? `/${suffix}` : ''}`; const data = await fetchTba(path); res.status(StatusCodes.OK).json(data); } catch (error) { @@ -22,17 +21,15 @@ const tbaHandler = tbaRouter.get( "/events/:eventKey/teams", - tbaHandler((req) => `/event/${req.params.eventKey}/teams/simple`) + createTbaHandler("teams", "simple") ); tbaRouter.get( "/events/:eventKey/matches", - tbaHandler((req) => `/event/${req.params.eventKey}/matches/simple`) + createTbaHandler("matches", "simple") ); tbaRouter.get( "/events/:eventKey/rankings", - tbaHandler((req) => `/event/${req.params.eventKey}/rankings`) + createTbaHandler("rankings") ); - -export { tbaRouter, tbaHandler }; diff --git a/apps/playoff-schedule/backend/src/utils/tbaClient.ts b/apps/playoff-schedule/backend/src/utils/tbaClient.ts index 88498e5..7fa5d9d 100644 --- a/apps/playoff-schedule/backend/src/utils/tbaClient.ts +++ b/apps/playoff-schedule/backend/src/utils/tbaClient.ts @@ -10,7 +10,7 @@ const getApiKey = (): string => { }; export const fetchTba = async (path: string): Promise => { - const response = await fetch(`${TBA_API_BASE}${path}`, { + const response = await fetch(TBA_API_BASE + path, { method: "GET", headers: { "X-TBA-Auth-Key": getApiKey(), @@ -24,4 +24,3 @@ export const fetchTba = async (path: string): Promise => { return response.json() as Promise; }; - diff --git a/apps/playoff-schedule/frontend/src/App.tsx b/apps/playoff-schedule/frontend/src/App.tsx index cdfed66..d6d803d 100644 --- a/apps/playoff-schedule/frontend/src/App.tsx +++ b/apps/playoff-schedule/frontend/src/App.tsx @@ -1,5 +1,6 @@ // בס"ד -import React, { useState, useMemo } from "react"; +import type React from "react"; +import { useState, useMemo } from "react"; import useLocalStorage from "./Hooks/LocalStorageHook"; import { useEventData } from "./Hooks/useEventData"; import { useMatchProcessing } from "./Hooks/useMatchProcessing"; diff --git a/apps/playoff-schedule/frontend/src/Hooks/LocalStorageHook.tsx b/apps/playoff-schedule/frontend/src/Hooks/LocalStorageHook.tsx index c5ee1e8..2461286 100644 --- a/apps/playoff-schedule/frontend/src/Hooks/LocalStorageHook.tsx +++ b/apps/playoff-schedule/frontend/src/Hooks/LocalStorageHook.tsx @@ -1,7 +1,10 @@ // בס"ד import { useState } from "react"; -const useLocalStorage = (key: string, initialValue: T): [T, (val: T | ((val: T) => T)) => void] => { +const useLocalStorage = ( + key: string, + initialValue: T +): [T, (val: T | ((val: T) => T)) => void] => { const [storedValue, setStoredValue] = useState(() => { try { if (typeof window === "undefined") { @@ -17,7 +20,8 @@ const useLocalStorage = (key: string, initialValue: T): [T, (val: T | ((val: const setValue = (value: T | ((val: T) => T)) => { try { - const valueToStore = value instanceof Function ? value(storedValue) : value; + const valueToStore = + value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); if (typeof window !== "undefined") { window.localStorage.setItem(key, JSON.stringify(valueToStore)); @@ -28,6 +32,6 @@ const useLocalStorage = (key: string, initialValue: T): [T, (val: T | ((val: }; return [storedValue, setValue]; -} +}; -export default useLocalStorage; \ No newline at end of file +export default useLocalStorage; diff --git a/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts b/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts index 35f0945..b8e64b7 100644 --- a/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts +++ b/apps/playoff-schedule/frontend/src/Hooks/useEventData.ts @@ -90,10 +90,10 @@ export const useEventData = ( useEffect(() => { if (!(searchStatus === "success" && activeEventKey)) { - return; + return undefined; } - const intervalId = window.setInterval(() => { + const intervalId = setInterval(() => { const matchesUrl = urlMatches(activeEventKey); const rankingsUrl = urlRankings(activeEventKey); @@ -115,7 +115,9 @@ export const useEventData = ( .catch(console.error); }, refreshIntervalMs); - return () => window.clearInterval(intervalId); + return () => { + clearInterval(intervalId); + }; }, [activeEventKey, searchStatus]); return { diff --git a/apps/playoff-schedule/frontend/src/components/FinalResults.tsx b/apps/playoff-schedule/frontend/src/components/FinalResults.tsx index ee668e5..bedeb9d 100644 --- a/apps/playoff-schedule/frontend/src/components/FinalResults.tsx +++ b/apps/playoff-schedule/frontend/src/components/FinalResults.tsx @@ -3,7 +3,7 @@ import type React from "react"; import type { RankItem } from "../types"; interface FinalResultsProps { - teamRank: RankItem | null; + teamRank: RankItem|null; } export const FinalResults: React.FC = ({ teamRank }) => { diff --git a/apps/playoff-schedule/frontend/src/components/Header.tsx b/apps/playoff-schedule/frontend/src/components/Header.tsx index dde42e4..f2dafa0 100644 --- a/apps/playoff-schedule/frontend/src/components/Header.tsx +++ b/apps/playoff-schedule/frontend/src/components/Header.tsx @@ -1,5 +1,5 @@ // בס"ד -import React from "react"; +import type React from "react"; interface HeaderProps { inputEventKey: string; @@ -25,7 +25,7 @@ export const Header: React.FC = ({ type="text" placeholder="🔍Event ID (e.g., 2025isios)" value={inputEventKey} - onChange={(event) => onInputChange(event.target.value)} + onChange={(event) => { onInputChange(event.target.value); }} className="search-input flex-1 rounded-md border-2 border-slate-400 dark:border-slate-500 bg-slate-600 dark:bg-slate-700 p-3 text-base text-white outline-none transition-colors placeholder:text-slate-300 dark:placeholder:text-slate-400 focus:border-blue-400 dark:focus:border-blue-500" />