From 0de593c7d30704d9c5d91ac42c7123af53067119 Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Wed, 6 May 2026 15:08:32 +0000 Subject: [PATCH] Updating --- .../shared/credit-application.service.spec.ts | 23 + .../app/shared/credit-application.service.ts | 3 + .../ReserveManagementSystemUI/Dockerfile | 2 + baseball/.env.example | 1 + baseball/.gitignore | 5 + baseball/README.md | 102 + baseball/next-env.d.ts | 6 + baseball/next.config.mjs | 4 + baseball/package-lock.json | 1755 +++++++++++++++++ baseball/package.json | 27 + baseball/src/app/api/_utils.ts | 14 + baseball/src/app/api/dashboard/route.ts | 62 + .../src/app/api/lineups/generate/route.ts | 47 + baseball/src/app/api/lineups/route.ts | 59 + .../src/app/api/pitching-appearances/route.ts | 37 + baseball/src/app/api/pitching-rules/route.ts | 38 + baseball/src/app/api/players/[id]/route.ts | 59 + baseball/src/app/api/players/route.ts | 57 + baseball/src/app/api/seed/route.ts | 15 + baseball/src/app/dashboard/page.tsx | 5 + baseball/src/app/globals.css | 434 ++++ baseball/src/app/layout.tsx | 15 + baseball/src/app/lineup/page.tsx | 5 + baseball/src/app/page.tsx | 5 + baseball/src/app/roster/page.tsx | 5 + baseball/src/app/settings/page.tsx | 5 + baseball/src/components/AppShell.tsx | 50 + baseball/src/components/DashboardClient.tsx | 124 ++ baseball/src/components/EmptyState.tsx | 17 + baseball/src/components/LineupClient.tsx | 249 +++ baseball/src/components/PlayerForm.tsx | 128 ++ baseball/src/components/RosterClient.tsx | 219 ++ baseball/src/components/SectionCard.tsx | 21 + baseball/src/components/SettingsClient.tsx | 128 ++ baseball/src/components/StatusBadge.tsx | 6 + baseball/src/lib/auth.ts | 12 + baseball/src/lib/client.ts | 19 + baseball/src/lib/constants.ts | 15 + baseball/src/lib/db.ts | 35 + baseball/src/lib/lineup.ts | 124 ++ baseball/src/lib/pitching.ts | 71 + baseball/src/lib/seed.ts | 99 + baseball/src/lib/serialize.ts | 3 + baseball/src/lib/types.ts | 111 ++ baseball/src/lib/utils.ts | 47 + baseball/src/lib/validators.ts | 54 + baseball/src/models/Game.ts | 22 + baseball/src/models/Lineup.ts | 48 + baseball/src/models/PitchingAppearance.ts | 24 + baseball/src/models/PitchingRule.ts | 18 + baseball/src/models/Player.ts | 32 + baseball/src/scripts/seed.ts | 14 + baseball/tsconfig.json | 41 + baseball/tsconfig.tsbuildinfo | 1 + 54 files changed, 4522 insertions(+) create mode 100644 baseball/.env.example create mode 100644 baseball/.gitignore create mode 100644 baseball/README.md create mode 100644 baseball/next-env.d.ts create mode 100644 baseball/next.config.mjs create mode 100644 baseball/package-lock.json create mode 100644 baseball/package.json create mode 100644 baseball/src/app/api/_utils.ts create mode 100644 baseball/src/app/api/dashboard/route.ts create mode 100644 baseball/src/app/api/lineups/generate/route.ts create mode 100644 baseball/src/app/api/lineups/route.ts create mode 100644 baseball/src/app/api/pitching-appearances/route.ts create mode 100644 baseball/src/app/api/pitching-rules/route.ts create mode 100644 baseball/src/app/api/players/[id]/route.ts create mode 100644 baseball/src/app/api/players/route.ts create mode 100644 baseball/src/app/api/seed/route.ts create mode 100644 baseball/src/app/dashboard/page.tsx create mode 100644 baseball/src/app/globals.css create mode 100644 baseball/src/app/layout.tsx create mode 100644 baseball/src/app/lineup/page.tsx create mode 100644 baseball/src/app/page.tsx create mode 100644 baseball/src/app/roster/page.tsx create mode 100644 baseball/src/app/settings/page.tsx create mode 100644 baseball/src/components/AppShell.tsx create mode 100644 baseball/src/components/DashboardClient.tsx create mode 100644 baseball/src/components/EmptyState.tsx create mode 100644 baseball/src/components/LineupClient.tsx create mode 100644 baseball/src/components/PlayerForm.tsx create mode 100644 baseball/src/components/RosterClient.tsx create mode 100644 baseball/src/components/SectionCard.tsx create mode 100644 baseball/src/components/SettingsClient.tsx create mode 100644 baseball/src/components/StatusBadge.tsx create mode 100644 baseball/src/lib/auth.ts create mode 100644 baseball/src/lib/client.ts create mode 100644 baseball/src/lib/constants.ts create mode 100644 baseball/src/lib/db.ts create mode 100644 baseball/src/lib/lineup.ts create mode 100644 baseball/src/lib/pitching.ts create mode 100644 baseball/src/lib/seed.ts create mode 100644 baseball/src/lib/serialize.ts create mode 100644 baseball/src/lib/types.ts create mode 100644 baseball/src/lib/utils.ts create mode 100644 baseball/src/lib/validators.ts create mode 100644 baseball/src/models/Game.ts create mode 100644 baseball/src/models/Lineup.ts create mode 100644 baseball/src/models/PitchingAppearance.ts create mode 100644 baseball/src/models/PitchingRule.ts create mode 100644 baseball/src/models/Player.ts create mode 100644 baseball/src/scripts/seed.ts create mode 100644 baseball/tsconfig.json create mode 100644 baseball/tsconfig.tsbuildinfo diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts index 07787b2..903178e 100644 --- a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts @@ -374,6 +374,29 @@ describe('CreditApplicationService', () => { }); + it('should clear stale co-applicant spouse state when switching back to a single applicant', () => { + const marriedPrimaryApplication: any = JSON.parse(JSON.stringify(primaryAndCoApplicationAddressSameApplication)); + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(marriedPrimaryApplication); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + + const memberInfo: MembershipInformation = { + limitRequested: marriedPrimaryApplication.limitRequested, + applicantType: CreditApplicationValues.CHOICE_NO, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_YES, + confirmJointCredit: null, + coApplicantLivesInMaritalPropertyState: null, + purposeOfLoanSignature: null, + agreementRequirement: undefined + }; + + creditApplicationService.saveMemberInformation(memberInfo); + + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers.length).toBe(1); + expect(creditApplicationService.getFullCreditApplication().hasCoApplicantSpouse).toBeNull(); + expect(creditApplicationService.getFullCreditApplication().coApplicantLivesInMaritalPropertyState).toBeNull(); + expect(creditApplicationService.getFullCreditApplication().confirmJointCredit).toBeNull(); + }); + it('should set Estimated Value & Property Type defaults on HELOC Rate Mod loan type', () => { const loanConfigurationService = TestBed.get(ConfigurationService); loanConfigurationService.saveConfiguration(DefaultSettings as UiSettings); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts index 8d76cb3..3cfd880 100644 --- a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts @@ -368,6 +368,9 @@ export class CreditApplicationService { if (creditApplication.hasCoApplicant === CreditApplicationValues.CHOICE_NO && creditApplication.fullInfoBorrowers.length > 1) { creditApplication.fullInfoBorrowers.pop(); + creditApplication.hasCoApplicantSpouse = null; + creditApplication.coApplicantLivesInMaritalPropertyState = null; + creditApplication.confirmJointCredit = null; } if (memberInfo.applicantType === CreditApplicationValues.CHOICE_YES && memberInfo.coApplicantLivesInMaritalPropertyState === CreditApplicationValues.CHOICE_YES) { diff --git a/LCU.WebApps.Indirect.ReserveManagementSystem/WebApp/ReserveManagementSystemUI/Dockerfile b/LCU.WebApps.Indirect.ReserveManagementSystem/WebApp/ReserveManagementSystemUI/Dockerfile index a3c02b2..fdc9421 100644 --- a/LCU.WebApps.Indirect.ReserveManagementSystem/WebApp/ReserveManagementSystemUI/Dockerfile +++ b/LCU.WebApps.Indirect.ReserveManagementSystem/WebApp/ReserveManagementSystemUI/Dockerfile @@ -31,6 +31,8 @@ RUN dotnet publish -c Release -o /app/publish FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine RUN apk add --no-cache icu-libs && apk add --no-cache tzdata; ENV TZ=America/Chicago +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false ENV ASPNETCORE_URLS=http://+:80 diff --git a/baseball/.env.example b/baseball/.env.example new file mode 100644 index 0000000..5a0161f --- /dev/null +++ b/baseball/.env.example @@ -0,0 +1 @@ +MONGODB_URI=mongodb://127.0.0.1:27017/little-league-roster diff --git a/baseball/.gitignore b/baseball/.gitignore new file mode 100644 index 0000000..e27b3f0 --- /dev/null +++ b/baseball/.gitignore @@ -0,0 +1,5 @@ +.next +node_modules +.env +dist +coverage diff --git a/baseball/README.md b/baseball/README.md new file mode 100644 index 0000000..451147f --- /dev/null +++ b/baseball/README.md @@ -0,0 +1,102 @@ +# Little League Roster Manager + +Mobile-friendly roster and lineup planning app for Little League baseball. It includes roster management, pitcher rest tracking, configurable rest rules, and a game-day lineup generator. + +## Stack + +- Next.js App Router with React and TypeScript +- MongoDB with Mongoose +- Node.js API routes + +## Features + +- Manage players with positions, handedness, notes, and active status +- Log pitching appearances and pitch counts by game date +- Configure pitch-count-based rest rules +- See pitcher availability as `Available`, `Needs Rest`, or `Unknown` +- Generate suggested batting orders and defensive assignments +- Manually adjust generated lineups before saving +- Seed sample data for a quick demo +- Placeholder auth utility for future expansion + +## Setup + +1. Install dependencies: + +```bash +cd baseball +npm install +``` + +2. Create environment variables: + +```bash +cp .env.example .env +``` + +3. Update `MONGODB_URI` in `.env` if needed. + +Example: + +```env +MONGODB_URI=mongodb://127.0.0.1:27017/little-league-roster +``` + +4. Start the app: + +```bash +npm run dev +``` + +5. Open `http://localhost:3000`. + +## Sample Data + +You can load sample data in two ways: + +- Click `Load Sample Team` on the dashboard +- Run: + +```bash +npm run seed +``` + +The seed script adds: + +- Sample players +- Default pitching rest rules +- Sample pitching appearances +- One sample saved game/lineup + +## Scripts + +- `npm run dev` starts the development server +- `npm run build` builds the app +- `npm run start` runs the production build +- `npm run seed` seeds the database if it is empty + +## Data Models + +- `Player` +- `PitchingAppearance` +- `PitchingRule` +- `Game` +- `Lineup` + +## Folder Structure + +```text +baseball/ + src/ + app/ Next.js pages and API routes + components/ Reusable UI and screen components + lib/ DB, validation, client helpers, lineup logic + models/ Mongoose schemas + scripts/ Seed script +``` + +## Notes + +- No authentication is enforced yet. `src/lib/auth.ts` provides a placeholder for future auth integration. +- API routes return basic validation and server errors in a simple JSON format. +- Pitcher eligibility is based on the most recent pitching appearance and the configured rule table. diff --git a/baseball/next-env.d.ts b/baseball/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/baseball/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/baseball/next.config.mjs b/baseball/next.config.mjs new file mode 100644 index 0000000..4678774 --- /dev/null +++ b/baseball/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/baseball/package-lock.json b/baseball/package-lock.json new file mode 100644 index 0000000..03cab6f --- /dev/null +++ b/baseball/package-lock.json @@ -0,0 +1,1755 @@ +{ + "name": "little-league-roster-manager", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "little-league-roster-manager", + "version": "0.1.0", + "dependencies": { + "clsx": "^2.1.1", + "mongoose": "^8.14.3", + "next": "^15.3.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "zod": "^3.24.4" + }, + "devDependencies": { + "@types/node": "^22.15.17", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.4", + "tsx": "^4.19.4", + "typescript": "^5.8.3" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz", + "integrity": "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@next/env": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.15.tgz", + "integrity": "sha512-vcmyu5/MyFzN7CdqRHO3uHO44p/QPCZkuTUXroeUmhNP8bL5PHFEhik22JUazt+CDDoD6EpBYRCaS2pISL+/hg==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.15.tgz", + "integrity": "sha512-6PvFO2Tzt10GFK2Ro9tAVEtacMqRmTarYMFKAnV2vYMdwWc73xzmDQyAV7SwEdMhzmiRoo7+m88DuiXlJlGeaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.15.tgz", + "integrity": "sha512-G+YNV+z6FDZTp/+IdGyIMFqalBTaQSnvAA+X/hrt+eaTRFSznRMz9K7rTmzvM6tDmKegNtyzgufZW0HwVzEqaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.15.tgz", + "integrity": "sha512-eVkrMcVIBqGfXB+QUC7jjZ94Z6uX/dNStbQFabewAnk13Uy18Igd1YZ/GtPRzdhtm7QwC0e6o7zOQecul4iC1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.15.tgz", + "integrity": "sha512-RwSHKMQ7InLy5GfkY2/n5PcFycKA08qI1VST78n09nN36nUPqCvGSMiLXlfUmzmpQpF6XeBYP2KRWHi0UW3uNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.15.tgz", + "integrity": "sha512-nplqvY86LakS+eeiuWsNWvfmK8pFcOEW7ZtVRt4QH70lL+0x6LG/m1OpJ/tvrbwjmR8HH9/fH2jzW1GlL03TIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.15.tgz", + "integrity": "sha512-eAgl9NKQ84/sww0v81DQINl/vL2IBxD7sMybd0cWRw6wqgouVI53brVRBrggqBRP/NWeIAE1dm5cbKYoiMlqDQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.15.tgz", + "integrity": "sha512-GJVZC86lzSquh0MtvZT+L7G8+jMnJcldloOjA8Kf3wXvBrvb6OGe2MzPuALxFshSm/IpwUtD2mIoof39ymf52A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.15.tgz", + "integrity": "sha512-nFucjVdwlFqxh/JG3hWSJ4p8+YJV7Ii8aPDuBQULB6DzUF4UNZETXLfEUk+oI2zEznWWULPt7MeuTE6xtK1HSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "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": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/csstype": { + "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" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.1.tgz", + "integrity": "sha512-gHSPD8qEwRmiXapK17hEnFWZdcFENMegHTcw5XIIg2+7R8eXQvdwSiMpD/A2oG8tKzFLLHyRXd8/eaDPAVwZgQ==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "15.5.15", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz", + "integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.15", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.15", + "@next/swc-darwin-x64": "15.5.15", + "@next/swc-linux-arm64-gnu": "15.5.15", + "@next/swc-linux-arm64-musl": "15.5.15", + "@next/swc-linux-x64-gnu": "15.5.15", + "@next/swc-linux-x64-musl": "15.5.15", + "@next/swc-win32-arm64-msvc": "15.5.15", + "@next/swc-win32-x64-msvc": "15.5.15", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "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" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/baseball/package.json b/baseball/package.json new file mode 100644 index 0000000..88e43bb --- /dev/null +++ b/baseball/package.json @@ -0,0 +1,27 @@ +{ + "name": "little-league-roster-manager", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "seed": "node --import tsx src/scripts/seed.ts" + }, + "dependencies": { + "clsx": "^2.1.1", + "mongoose": "^8.14.3", + "next": "^15.3.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "zod": "^3.24.4" + }, + "devDependencies": { + "@types/node": "^22.15.17", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.4", + "tsx": "^4.19.4", + "typescript": "^5.8.3" + } +} diff --git a/baseball/src/app/api/_utils.ts b/baseball/src/app/api/_utils.ts new file mode 100644 index 0000000..05877ee --- /dev/null +++ b/baseball/src/app/api/_utils.ts @@ -0,0 +1,14 @@ +import { NextResponse } from "next/server"; +import { ZodError } from "zod"; + +export function handleApiError(error: unknown) { + if (error instanceof ZodError) { + return NextResponse.json({ error: error.issues[0]?.message ?? "Validation failed." }, { status: 400 }); + } + + if (error instanceof Error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ error: "Unexpected server error." }, { status: 500 }); +} diff --git a/baseball/src/app/api/dashboard/route.ts b/baseball/src/app/api/dashboard/route.ts new file mode 100644 index 0000000..54f8678 --- /dev/null +++ b/baseball/src/app/api/dashboard/route.ts @@ -0,0 +1,62 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { buildPitcherAvailabilityMap } from "@/lib/pitching"; +import { serialize } from "@/lib/serialize"; +import type { DashboardResponse } from "@/lib/types"; +import { formatPlayerName, toDateOnly } from "@/lib/utils"; +import { PitchingAppearanceModel } from "@/models/PitchingAppearance"; +import { PitchingRuleModel } from "@/models/PitchingRule"; +import { PlayerModel } from "@/models/Player"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + try { + await connectToDatabase(); + const { searchParams } = new URL(request.url); + const gameDate = searchParams.get("gameDate") ?? toDateOnly(new Date()); + + const [players, appearances, rules] = await Promise.all([ + PlayerModel.find().sort({ jerseyNumber: 1 }).lean(), + PitchingAppearanceModel.find().lean(), + PitchingRuleModel.find().sort({ sortOrder: 1 }).lean() + ]); + + const availabilityMap = buildPitcherAvailabilityMap( + players.map((player) => ({ ...player, _id: String(player._id) })), + appearances.map((appearance) => ({ ...appearance, playerId: String(appearance.playerId) })), + rules, + gameDate + ); + + const response: DashboardResponse = { + rosterCount: players.length, + activePlayers: players.filter((player) => player.isActive).length, + availablePitchers: 0, + needsRest: 0, + unknownPitchers: 0, + recentPitching: players.map((player) => { + const summary = availabilityMap[String(player._id)]; + if (summary.status === "available") response.availablePitchers += 1; + if (summary.status === "needs-rest") response.needsRest += 1; + if (summary.status === "unknown") response.unknownPitchers += 1; + + return { + playerId: String(player._id), + playerName: formatPlayerName(player.firstName, player.lastName), + status: summary.status, + daysSinceLastAppearance: summary.daysSinceLastAppearance, + requiredRestDays: summary.requiredRestDays, + lastPitchedDate: summary.lastPitchedDate, + pitchCount: summary.lastPitchCount + }; + }) + }; + + return NextResponse.json(serialize(response)); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/lineups/generate/route.ts b/baseball/src/app/api/lineups/generate/route.ts new file mode 100644 index 0000000..50d2028 --- /dev/null +++ b/baseball/src/app/api/lineups/generate/route.ts @@ -0,0 +1,47 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { generateLineup } from "@/lib/lineup"; +import { serialize } from "@/lib/serialize"; +import type { Position } from "@/lib/types"; +import { generateLineupSchema } from "@/lib/validators"; +import { PitchingAppearanceModel } from "@/models/PitchingAppearance"; +import { PitchingRuleModel } from "@/models/PitchingRule"; +import { PlayerModel } from "@/models/Player"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + try { + await connectToDatabase(); + const payload = generateLineupSchema.parse(await request.json()); + + const [players, rules, appearances] = await Promise.all([ + PlayerModel.find({ _id: { $in: payload.playerIds } }).lean(), + PitchingRuleModel.find().sort({ sortOrder: 1 }).lean(), + PitchingAppearanceModel.find({ playerId: { $in: payload.playerIds } }).lean() + ]); + + const lineup = generateLineup({ + players: players.map((player) => ({ + ...player, + _id: String(player._id), + primaryPosition: player.primaryPosition as Position, + secondaryPosition: (player.secondaryPosition ?? null) as Position | null, + tertiaryPosition: (player.tertiaryPosition ?? null) as Position | null + })), + rules, + appearances: appearances.map((appearance) => ({ + ...appearance, + playerId: String(appearance.playerId) + })), + gameDate: payload.gameDate, + startingPitcherId: payload.startingPitcherId ?? null + }); + + return NextResponse.json(serialize(lineup)); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/lineups/route.ts b/baseball/src/app/api/lineups/route.ts new file mode 100644 index 0000000..cead9cf --- /dev/null +++ b/baseball/src/app/api/lineups/route.ts @@ -0,0 +1,59 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { serialize } from "@/lib/serialize"; +import { saveLineupSchema } from "@/lib/validators"; +import { GameModel } from "@/models/Game"; +import { LineupModel } from "@/models/Lineup"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + try { + await connectToDatabase(); + const { searchParams } = new URL(request.url); + const gameDate = searchParams.get("gameDate"); + const filter = gameDate ? { gameDate: new Date(gameDate) } : {}; + const lineups = await LineupModel.find(filter).sort({ gameDate: -1 }).lean(); + return NextResponse.json(serialize(lineups)); + } catch (error) { + return handleApiError(error); + } +} + +export async function POST(request: Request) { + try { + await connectToDatabase(); + const payload = saveLineupSchema.parse(await request.json()); + const lineupPayload = { + gameDate: new Date(payload.gameDate), + playerIds: payload.playerIds, + startingPitcherId: payload.startingPitcherId ?? null, + battingOrder: payload.battingOrder, + defensiveAssignments: payload.defensiveAssignments, + bench: payload.bench, + notes: payload.notes + }; + + const lineup = await LineupModel.findOneAndUpdate({ gameDate: lineupPayload.gameDate }, lineupPayload, { + upsert: true, + new: true, + runValidators: true + }).lean(); + + await GameModel.findOneAndUpdate( + { gameDate: lineupPayload.gameDate }, + { + gameDate: lineupPayload.gameDate, + activePlayerIds: payload.playerIds, + startingPitcherId: payload.startingPitcherId ?? null + }, + { upsert: true, new: true } + ); + + return NextResponse.json(serialize(lineup), { status: 201 }); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/pitching-appearances/route.ts b/baseball/src/app/api/pitching-appearances/route.ts new file mode 100644 index 0000000..b58f2b4 --- /dev/null +++ b/baseball/src/app/api/pitching-appearances/route.ts @@ -0,0 +1,37 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { serialize } from "@/lib/serialize"; +import { pitchingAppearanceSchema } from "@/lib/validators"; +import { PitchingAppearanceModel } from "@/models/PitchingAppearance"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + try { + await connectToDatabase(); + const { searchParams } = new URL(request.url); + const playerId = searchParams.get("playerId"); + const filter = playerId ? { playerId } : {}; + const appearances = await PitchingAppearanceModel.find(filter).sort({ gameDate: -1 }).lean(); + return NextResponse.json(serialize(appearances)); + } catch (error) { + return handleApiError(error); + } +} + +export async function POST(request: Request) { + try { + await connectToDatabase(); + const payload = pitchingAppearanceSchema.parse(await request.json()); + const appearance = await PitchingAppearanceModel.create({ + ...payload, + gameDate: new Date(payload.gameDate) + }); + + return NextResponse.json(serialize(appearance), { status: 201 }); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/pitching-rules/route.ts b/baseball/src/app/api/pitching-rules/route.ts new file mode 100644 index 0000000..417ce53 --- /dev/null +++ b/baseball/src/app/api/pitching-rules/route.ts @@ -0,0 +1,38 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { serialize } from "@/lib/serialize"; +import { pitchingRulesSchema } from "@/lib/validators"; +import { PitchingRuleModel } from "@/models/PitchingRule"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + await connectToDatabase(); + const rules = await PitchingRuleModel.find().sort({ sortOrder: 1 }).lean(); + return NextResponse.json(serialize(rules)); + } catch (error) { + return handleApiError(error); + } +} + +export async function PUT(request: Request) { + try { + await connectToDatabase(); + const payload = pitchingRulesSchema.parse(await request.json()); + + await PitchingRuleModel.deleteMany({}); + const rules = await PitchingRuleModel.insertMany( + payload.map((rule, index) => ({ + ...rule, + sortOrder: index + })) + ); + + return NextResponse.json(serialize(rules)); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/players/[id]/route.ts b/baseball/src/app/api/players/[id]/route.ts new file mode 100644 index 0000000..59bb852 --- /dev/null +++ b/baseball/src/app/api/players/[id]/route.ts @@ -0,0 +1,59 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { serialize } from "@/lib/serialize"; +import { playerSchema } from "@/lib/validators"; +import { LineupModel } from "@/models/Lineup"; +import { PitchingAppearanceModel } from "@/models/PitchingAppearance"; +import { PlayerModel } from "@/models/Player"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function PATCH(request: Request, { params }: { params: Promise<{ id: string }> }) { + try { + await connectToDatabase(); + const { id } = await params; + const payload = playerSchema.parse(await request.json()); + const player = await PlayerModel.findByIdAndUpdate(id, payload, { new: true, runValidators: true }).lean(); + + if (!player) { + return NextResponse.json({ error: "Player not found." }, { status: 404 }); + } + + return NextResponse.json(serialize(player)); + } catch (error) { + return handleApiError(error); + } +} + +export async function DELETE(_request: Request, { params }: { params: Promise<{ id: string }> }) { + try { + await connectToDatabase(); + const { id } = await params; + + await Promise.all([ + PitchingAppearanceModel.deleteMany({ playerId: id }), + LineupModel.updateMany( + {}, + { + $pull: { + playerIds: id, + bench: id, + battingOrder: { playerId: id }, + defensiveAssignments: { playerId: id } + } + } + ) + ]); + + const player = await PlayerModel.findByIdAndDelete(id).lean(); + if (!player) { + return NextResponse.json({ error: "Player not found." }, { status: 404 }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/players/route.ts b/baseball/src/app/api/players/route.ts new file mode 100644 index 0000000..4ff7c0f --- /dev/null +++ b/baseball/src/app/api/players/route.ts @@ -0,0 +1,57 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { connectToDatabase } from "@/lib/db"; +import { buildPitcherAvailabilityMap } from "@/lib/pitching"; +import { serialize } from "@/lib/serialize"; +import { toDateOnly } from "@/lib/utils"; +import { playerSchema } from "@/lib/validators"; +import { PitchingAppearanceModel } from "@/models/PitchingAppearance"; +import { PitchingRuleModel } from "@/models/PitchingRule"; +import { PlayerModel } from "@/models/Player"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + try { + await connectToDatabase(); + const { searchParams } = new URL(request.url); + const gameDate = searchParams.get("gameDate") ?? toDateOnly(new Date()); + + const [players, rules, appearances] = await Promise.all([ + PlayerModel.find().sort({ jerseyNumber: 1, lastName: 1 }).lean(), + PitchingRuleModel.find().sort({ sortOrder: 1 }).lean(), + PitchingAppearanceModel.find().lean() + ]); + + const availabilityMap = buildPitcherAvailabilityMap( + players.map((player) => ({ ...player, _id: String(player._id) })), + appearances.map((appearance) => ({ ...appearance, playerId: String(appearance.playerId) })), + rules, + gameDate + ); + + return NextResponse.json( + serialize( + players.map((player) => ({ + ...player, + _id: String(player._id), + pitcherAvailability: availabilityMap[String(player._id)] + })) + ) + ); + } catch (error) { + return handleApiError(error); + } +} + +export async function POST(request: Request) { + try { + await connectToDatabase(); + const payload = playerSchema.parse(await request.json()); + const player = await PlayerModel.create(payload); + return NextResponse.json(serialize(player), { status: 201 }); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/api/seed/route.ts b/baseball/src/app/api/seed/route.ts new file mode 100644 index 0000000..04f8c10 --- /dev/null +++ b/baseball/src/app/api/seed/route.ts @@ -0,0 +1,15 @@ +import { NextResponse } from "next/server"; +import { handleApiError } from "@/app/api/_utils"; +import { seedDatabase } from "@/lib/seed"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function POST() { + try { + const result = await seedDatabase(); + return NextResponse.json(result); + } catch (error) { + return handleApiError(error); + } +} diff --git a/baseball/src/app/dashboard/page.tsx b/baseball/src/app/dashboard/page.tsx new file mode 100644 index 0000000..18a20c5 --- /dev/null +++ b/baseball/src/app/dashboard/page.tsx @@ -0,0 +1,5 @@ +import { DashboardClient } from "@/components/DashboardClient"; + +export default function DashboardPage() { + return ; +} diff --git a/baseball/src/app/globals.css b/baseball/src/app/globals.css new file mode 100644 index 0000000..8771e83 --- /dev/null +++ b/baseball/src/app/globals.css @@ -0,0 +1,434 @@ +:root { + --bg: #f4efe3; + --surface: rgba(255, 252, 246, 0.94); + --surface-strong: #fffdf8; + --ink: #152033; + --muted: #5e687c; + --line: rgba(21, 32, 51, 0.1); + --accent: #0d6a5b; + --accent-strong: #094f44; + --warning: #b87510; + --danger: #b53a2c; + --success: #177245; + --shadow: 0 20px 45px rgba(21, 32, 51, 0.12); + --radius-lg: 24px; + --radius-md: 18px; + --radius-sm: 12px; +} + +* { + box-sizing: border-box; +} + +html { + font-size: 16px; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--ink); + background: + radial-gradient(circle at top left, rgba(13, 106, 91, 0.2), transparent 32%), + radial-gradient(circle at top right, rgba(184, 117, 16, 0.18), transparent 28%), + linear-gradient(180deg, #f8f2e7 0%, var(--bg) 48%, #efe7d5 100%); + font-family: Georgia, "Times New Roman", serif; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +.app-shell { + width: min(1120px, calc(100% - 24px)); + margin: 0 auto; + padding: 24px 0 64px; +} + +.hero { + display: flex; + justify-content: space-between; + gap: 20px; + padding: 28px; + border-radius: 28px; + background: linear-gradient(135deg, rgba(14, 65, 92, 0.96), rgba(13, 106, 91, 0.92)); + color: white; + box-shadow: var(--shadow); +} + +.hero h1 { + margin: 0; + font-size: clamp(2rem, 5vw, 3.4rem); + line-height: 1; +} + +.hero-copy, +.eyebrow { + margin: 0; +} + +.hero-copy { + margin-top: 12px; + max-width: 50rem; + color: rgba(255, 255, 255, 0.86); +} + +.eyebrow { + margin-bottom: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.72); +} + +.hero-actions { + display: flex; + align-items: flex-start; +} + +.tabs { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; + margin: 18px 0 24px; +} + +.tab { + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.72); + border-radius: 999px; + padding: 12px 14px; + text-align: center; + color: var(--muted); +} + +.tab-active { + background: var(--surface-strong); + color: var(--ink); + box-shadow: var(--shadow); +} + +.page-grid { + display: grid; + gap: 18px; +} + +.section-card { + background: var(--surface); + border: 1px solid rgba(255, 255, 255, 0.72); + border-radius: var(--radius-lg); + padding: 24px; + box-shadow: var(--shadow); + backdrop-filter: blur(14px); +} + +.section-heading h2, +.list-row h3, +.empty-state h3, +.player-card h3 { + margin: 0; +} + +.section-heading p, +.empty-state p, +.list-row p, +.muted-copy { + margin: 8px 0 0; + color: var(--muted); +} + +.stats-grid, +.card-grid { + display: grid; + gap: 16px; +} + +.stats-grid { + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); +} + +.stat-card { + padding: 18px; + border-radius: var(--radius-md); + background: var(--surface-strong); + border: 1px solid var(--line); +} + +.stat-card span { + display: block; + color: var(--muted); +} + +.stat-card strong { + display: block; + margin-top: 10px; + font-size: 2rem; +} + +.stat-card-success strong { + color: var(--success); +} + +.stat-card-warning strong { + color: var(--warning); +} + +.list-stack { + display: grid; + gap: 12px; +} + +.list-row, +.picker-card { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + padding: 14px 16px; + background: rgba(255, 255, 255, 0.7); +} + +.list-meta { + display: grid; + justify-items: end; + gap: 6px; +} + +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 108px; + padding: 8px 12px; + border-radius: 999px; + font-size: 0.85rem; + font-weight: 700; +} + +.badge-available { + background: rgba(23, 114, 69, 0.12); + color: var(--success); +} + +.badge-needs-rest { + background: rgba(181, 58, 44, 0.12); + color: var(--danger); +} + +.badge-unknown { + background: rgba(94, 104, 124, 0.12); + color: var(--muted); +} + +.empty-state { + padding: 28px; + border: 1px dashed var(--line); + border-radius: var(--radius-md); + text-align: center; +} + +.banner { + padding: 14px 16px; + border-radius: var(--radius-sm); + margin-bottom: 12px; +} + +.banner-success { + background: rgba(23, 114, 69, 0.12); + color: var(--success); +} + +.banner-error { + background: rgba(181, 58, 44, 0.12); + color: var(--danger); +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.form-grid label, +.rule-table th, +.rule-table td { + font-size: 0.95rem; +} + +.form-grid label span { + display: block; + margin-bottom: 8px; + color: var(--muted); +} + +.full-width { + grid-column: 1 / -1; +} + +.form-grid input, +.form-grid select, +.form-grid textarea, +.rule-table input, +.list-row select { + width: 100%; + border: 1px solid var(--line); + border-radius: 14px; + background: white; + padding: 12px 14px; + color: var(--ink); +} + +.checkbox-row { + display: flex; + align-items: center; + gap: 10px; +} + +.checkbox-row span { + margin: 0; +} + +.checkbox-row input { + width: auto; +} + +.button { + border: 0; + border-radius: 999px; + padding: 12px 18px; + background: var(--accent); + color: white; + font-weight: 700; +} + +.button:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.button-secondary { + background: white; + color: var(--ink); + border: 1px solid var(--line); +} + +.button-danger { + background: var(--danger); +} + +.player-card { + display: grid; + gap: 12px; + padding: 18px; + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.72); + border: 1px solid var(--line); +} + +.card-grid { + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); +} + +.player-card-head { + display: flex; + justify-content: space-between; + gap: 12px; +} + +.player-card-head p, +.player-notes, +.player-meta dt { + margin: 6px 0 0; + color: var(--muted); +} + +.player-meta { + display: grid; + gap: 10px; + margin: 0; +} + +.player-meta dd { + margin: 4px 0 0; +} + +.action-row { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.table-wrap { + overflow-x: auto; +} + +.rule-table { + width: 100%; + border-collapse: collapse; +} + +.rule-table th, +.rule-table td { + text-align: left; + padding: 12px; + border-bottom: 1px solid var(--line); +} + +.picker-grid { + display: grid; + gap: 12px; + margin-top: 18px; +} + +.picker-card input { + width: auto; +} + +.note-block { + margin-top: 16px; + border-left: 4px solid var(--warning); + padding-left: 14px; + color: var(--muted); +} + +@media (max-width: 760px) { + .app-shell { + width: min(100% - 16px, 1120px); + } + + .hero, + .tabs, + .form-grid { + grid-template-columns: 1fr; + } + + .hero, + .list-row, + .picker-card, + .player-card-head { + flex-direction: column; + align-items: stretch; + } + + .tabs { + display: grid; + } + + .list-meta { + justify-items: start; + } +} diff --git a/baseball/src/app/layout.tsx b/baseball/src/app/layout.tsx new file mode 100644 index 0000000..897c34a --- /dev/null +++ b/baseball/src/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "Little League Roster Manager", + description: "Roster, pitching availability, and lineup planning for game day." +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/baseball/src/app/lineup/page.tsx b/baseball/src/app/lineup/page.tsx new file mode 100644 index 0000000..9277c6e --- /dev/null +++ b/baseball/src/app/lineup/page.tsx @@ -0,0 +1,5 @@ +import { LineupClient } from "@/components/LineupClient"; + +export default function LineupPage() { + return ; +} diff --git a/baseball/src/app/page.tsx b/baseball/src/app/page.tsx new file mode 100644 index 0000000..9ef1235 --- /dev/null +++ b/baseball/src/app/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function HomePage() { + redirect("/dashboard"); +} diff --git a/baseball/src/app/roster/page.tsx b/baseball/src/app/roster/page.tsx new file mode 100644 index 0000000..a54e609 --- /dev/null +++ b/baseball/src/app/roster/page.tsx @@ -0,0 +1,5 @@ +import { RosterClient } from "@/components/RosterClient"; + +export default function RosterPage() { + return ; +} diff --git a/baseball/src/app/settings/page.tsx b/baseball/src/app/settings/page.tsx new file mode 100644 index 0000000..e1b92df --- /dev/null +++ b/baseball/src/app/settings/page.tsx @@ -0,0 +1,5 @@ +import { SettingsClient } from "@/components/SettingsClient"; + +export default function SettingsPage() { + return ; +} diff --git a/baseball/src/components/AppShell.tsx b/baseball/src/components/AppShell.tsx new file mode 100644 index 0000000..59e229a --- /dev/null +++ b/baseball/src/components/AppShell.tsx @@ -0,0 +1,50 @@ +"use client"; + +import Link from "next/link"; +import type { Route } from "next"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; + +const navItems = [ + { href: "/dashboard", label: "Dashboard" }, + { href: "/roster", label: "Roster" }, + { href: "/lineup", label: "Generate Lineup" }, + { href: "/settings", label: "Pitching Rules" } +]; + +export function AppShell({ + title, + subtitle, + children, + actions +}: { + title: string; + subtitle: string; + children: React.ReactNode; + actions?: React.ReactNode; +}) { + const pathname = usePathname(); + + return ( +
+
+
+

Little League Coach Console

+

{title}

+

{subtitle}

+
+ {actions ?
{actions}
: null} +
+ + + +
{children}
+
+ ); +} diff --git a/baseball/src/components/DashboardClient.tsx b/baseball/src/components/DashboardClient.tsx new file mode 100644 index 0000000..8c8626b --- /dev/null +++ b/baseball/src/components/DashboardClient.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { AppShell } from "@/components/AppShell"; +import { EmptyState } from "@/components/EmptyState"; +import { SectionCard } from "@/components/SectionCard"; +import { StatusBadge } from "@/components/StatusBadge"; +import { fetchJson } from "@/lib/client"; +import type { DashboardResponse } from "@/lib/types"; + +export function DashboardClient() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [message, setMessage] = useState(null); + + async function loadDashboard() { + setLoading(true); + setError(null); + + try { + const response = await fetchJson("/api/dashboard"); + setData(response); + } catch (loadError) { + setError(loadError instanceof Error ? loadError.message : "Failed to load dashboard."); + } finally { + setLoading(false); + } + } + + async function seedSampleData() { + try { + const response = await fetchJson<{ seeded: boolean; reason?: string }>("/api/seed", { method: "POST" }); + setMessage(response.seeded ? "Sample team loaded." : response.reason ?? "Seed skipped."); + await loadDashboard(); + } catch (seedError) { + setError(seedError instanceof Error ? seedError.message : "Failed to seed data."); + } + } + + useEffect(() => { + void loadDashboard(); + }, []); + + return ( + void seedSampleData()}> + Load Sample Team + + } + > + {message ?
{message}
: null} + {error ?
{error}
: null} + + {loading ? ( + + ) : data && data.rosterCount > 0 ? ( + <> +
+
+ Rostered Players + {data.rosterCount} +
+
+ Active Today + {data.activePlayers} +
+
+ Pitchers Available + {data.availablePitchers} +
+
+ Need Rest + {data.needsRest} +
+
+ + +
+ {data.recentPitching.map((entry) => ( +
+
+

{entry.playerName}

+

+ {entry.lastPitchedDate + ? `${entry.pitchCount} pitches on ${entry.lastPitchedDate}` + : "No pitching history recorded yet."} +

+
+
+ + + {entry.daysSinceLastAppearance === null + ? "No history" + : `${entry.daysSinceLastAppearance} day(s) since last outing`} + +
+
+ ))} +
+
+ + ) : ( + + void seedSampleData()}> + Load Sample Team + + } + /> + + )} +
+ ); +} diff --git a/baseball/src/components/EmptyState.tsx b/baseball/src/components/EmptyState.tsx new file mode 100644 index 0000000..15c509c --- /dev/null +++ b/baseball/src/components/EmptyState.tsx @@ -0,0 +1,17 @@ +export function EmptyState({ + title, + description, + action +}: { + title: string; + description: string; + action?: React.ReactNode; +}) { + return ( +
+

{title}

+

{description}

+ {action} +
+ ); +} diff --git a/baseball/src/components/LineupClient.tsx b/baseball/src/components/LineupClient.tsx new file mode 100644 index 0000000..3f02368 --- /dev/null +++ b/baseball/src/components/LineupClient.tsx @@ -0,0 +1,249 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { AppShell } from "@/components/AppShell"; +import { EmptyState } from "@/components/EmptyState"; +import { SectionCard } from "@/components/SectionCard"; +import { StatusBadge } from "@/components/StatusBadge"; +import { FIELD_POSITIONS } from "@/lib/constants"; +import { fetchJson } from "@/lib/client"; +import { formatPlayerName } from "@/lib/utils"; +import type { GeneratedLineup, LineupRecord, PlayerRecord } from "@/lib/types"; + +export function LineupClient() { + const [gameDate, setGameDate] = useState(new Date().toISOString().slice(0, 10)); + const [players, setPlayers] = useState([]); + const [selectedPlayerIds, setSelectedPlayerIds] = useState([]); + const [startingPitcherId, setStartingPitcherId] = useState(""); + const [showAllPitchers, setShowAllPitchers] = useState(false); + const [generated, setGenerated] = useState(null); + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + + async function loadPlayers(targetDate: string) { + try { + const response = await fetchJson(`/api/players?gameDate=${targetDate}`); + setPlayers(response); + const activeIds = response.filter((player) => player.isActive).map((player) => player._id); + setSelectedPlayerIds(activeIds); + const firstEligible = response.find((player) => player.pitcherAvailability.status === "available"); + setStartingPitcherId(firstEligible?._id ?? response[0]?._id ?? ""); + } catch (loadError) { + setError(loadError instanceof Error ? loadError.message : "Failed to load players."); + } + } + + useEffect(() => { + void loadPlayers(gameDate); + }, [gameDate]); + + const selectablePitchers = useMemo( + () => + players.filter((player) => + selectedPlayerIds.includes(player._id) && + (showAllPitchers || player.pitcherAvailability.status !== "needs-rest") + ), + [players, selectedPlayerIds, showAllPitchers] + ); + + const selectedPlayers = useMemo( + () => players.filter((player) => selectedPlayerIds.includes(player._id)), + [players, selectedPlayerIds] + ); + + function updateGenerated(next: GeneratedLineup | null) { + setGenerated(next); + } + + async function generate() { + try { + const response = await fetchJson("/api/lineups/generate", { + method: "POST", + body: JSON.stringify({ + gameDate, + playerIds: selectedPlayerIds, + startingPitcherId: startingPitcherId || null + }) + }); + setGenerated(response); + setMessage("Suggested lineup generated."); + } catch (generateError) { + setError(generateError instanceof Error ? generateError.message : "Unable to generate lineup."); + } + } + + async function save() { + if (!generated) return; + + try { + await fetchJson("/api/lineups", { + method: "POST", + body: JSON.stringify({ + ...generated, + playerIds: selectedPlayerIds + }) + }); + setMessage("Lineup saved."); + } catch (saveError) { + setError(saveError instanceof Error ? saveError.message : "Unable to save lineup."); + } + } + + return ( + + {message ?
{message}
: null} + {error ?
{error}
: null} + + +
+ + + +
+ + +
+
+ +
+ {players.map((player) => ( + + ))} +
+
+ + {generated ? ( + <> + +
+ {generated.battingOrder.map((slot, index) => ( +
+
+

Spot {index + 1}

+
+ +
+ ))} +
+
+ + +
+ {FIELD_POSITIONS.map((position) => { + const assignment = generated.defensiveAssignments.find((entry) => entry.position === position); + return ( +
+
+

{position}

+
+ +
+ ); + })} +
+ {generated.notes.length > 0 ? ( +
+ {generated.notes.map((note) => ( +

{note}

+ ))} +
+ ) : null} +
+ + ) : ( + + + + )} +
+ ); +} diff --git a/baseball/src/components/PlayerForm.tsx b/baseball/src/components/PlayerForm.tsx new file mode 100644 index 0000000..05078cf --- /dev/null +++ b/baseball/src/components/PlayerForm.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { HANDEDNESS_OPTIONS, POSITIONS } from "@/lib/constants"; +import type { PlayerInput, PlayerRecord } from "@/lib/types"; + +const defaultForm: PlayerInput = { + firstName: "", + lastName: "", + jerseyNumber: 0, + batsThrows: "R/R", + primaryPosition: "P", + secondaryPosition: "C", + tertiaryPosition: "1B", + isActive: true, + notes: "" +}; + +export function emptyPlayerForm() { + return { ...defaultForm }; +} + +export function toPlayerForm(player: PlayerRecord): PlayerInput { + return { + firstName: player.firstName, + lastName: player.lastName, + jerseyNumber: player.jerseyNumber, + batsThrows: player.batsThrows, + primaryPosition: player.primaryPosition, + secondaryPosition: player.secondaryPosition ?? null, + tertiaryPosition: player.tertiaryPosition ?? null, + isActive: player.isActive, + notes: player.notes ?? "" + }; +} + +export function PlayerForm({ + value, + onChange, + onSubmit, + submitLabel +}: { + value: PlayerInput; + onChange: (next: PlayerInput) => void; + onSubmit: () => void; + submitLabel: string; +}) { + return ( +
+ + + + + + + + +