diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9fa815b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +# Ignore certain directories +.git/ +node_modules/ + +# Ignore certain files +Dockerfile* +docker-compose* +compose* +README* diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint.yaml similarity index 53% rename from .github/workflows/lint-test.yaml rename to .github/workflows/lint.yaml index 4a5a32e..357995f 100644 --- a/.github/workflows/lint-test.yaml +++ b/.github/workflows/lint.yaml @@ -1,4 +1,4 @@ -name: Lint and Test +name: Lint on: # Manual Invocation @@ -7,12 +7,9 @@ on: # Invocation from workflow workflow_call: -env: - CI: true - jobs: - lint: - name: Lint + lint-be: + name: Lint Backend runs-on: ubuntu-latest timeout-minutes: 10 defaults: @@ -31,8 +28,8 @@ jobs: - name: Run lint uses: golangci/golangci-lint-action@v8 - test: - name: Test + lint-fe: + name: Lint Frontend runs-on: ubuntu-latest timeout-minutes: 10 defaults: @@ -43,20 +40,22 @@ jobs: - name: Checkout repo uses: actions/checkout@v5 - - name: Setup go - uses: actions/setup-go@v6 + - name: Setup pnpm + uses: pnpm/action-setup@v4 with: - go-version-file: go.mod + package_json_file: frontend/package.json - - name: Setup task - uses: arduino/setup-task@v2 + - name: Setup node + uses: actions/setup-node@v6 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Download dependencies - run: go mod download - - - name: Run test - run: task test - env: - TWICKETS_API_KEY: ${{ secrets.TWICKETS_API_KEY }} + node-version-file: frontend/package.json + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + + - name: Install dependencies + working-directory: frontend + run: pnpm install + + - name: Run lint + working-directory: frontend + run: pnpm run lint diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 33bc689..0a69ec9 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -6,15 +6,20 @@ on: - main jobs: - lint-test: - name: Lint and Test - uses: ./.github/workflows/lint-test.yaml + lint: + name: Lint + uses: ./.github/workflows/lint.yaml + secrets: inherit + + test: + name: Test + uses: ./.github/workflows/test.yaml secrets: inherit build-push: name: Build and Push uses: ./.github/workflows/build-push.yaml - needs: [lint-test] + needs: [lint, test] secrets: inherit with: ref: ${{ github.sha }} diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 8e033d4..41637cd 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -6,7 +6,12 @@ on: - main jobs: - lint-test: - name: Lint and Test - uses: ./.github/workflows/lint-test.yaml + lint: + name: Lint + uses: ./.github/workflows/lint.yaml + secrets: inherit + + test: + name: Test + uses: ./.github/workflows/test.yaml secrets: inherit diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..8d7d457 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,35 @@ +name: Test + +on: + # Manual Invocation + workflow_dispatch: + + # Invocation from workflow + workflow_call: + +jobs: + test-backend: + name: Test Backend + runs-on: ubuntu-latest + timeout-minutes: 10 + defaults: + run: + shell: bash + + steps: + - name: Checkout repo + uses: actions/checkout@v5 + + - name: Setup go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Install dependencies + run: go mod download + + - name: Run test + run: go test ./... + env: + TWICKETS_API_KEY: ${{ secrets.TWICKETS_API_KEY }} + CI: true diff --git a/.gitignore b/.gitignore index 089c45c..5e32a4d 100644 --- a/.gitignore +++ b/.gitignore @@ -70,11 +70,6 @@ web_modules/ # Yarn Integrity file .yarn-integrity -# dotenv environment variable files -.env -.env.* -!.env.example - # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache diff --git a/.vscode/launch.json b/.vscode/launch.json index de86ef3..cc891b1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Twitchets", + "name": "Backend", "type": "go", "request": "launch", "mode": "auto", diff --git a/.vscode/settings.json b/.vscode/settings.json index 60c7172..d72c285 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,9 @@ "go.toolsManagement.autoUpdate": true, "go.buildTags": "", + // ---- Javascript Settings ---- + "biome.configurationPath": "frontend/biome.json", + // Code Spell Checker "cSpell.words": ["twickets", "unmarshaller"] } diff --git a/Dockerfile b/Dockerfile index f5990dc..18f5556 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,34 @@ -# Builder Image -FROM golang:1.24 AS builder +# Frontend builder Image +FROM node:25-slim AS frontend-builder -WORKDIR /twitchets +WORKDIR /app +# Setup pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + +# Install dependencies +COPY ./frontend/package.json . +COPY ./frontend/pnpm-lock.yaml . +RUN pnpm install --prod --frozen-lockfile + +# Build +COPY ./frontend . +RUN pnpm run build + +# Backend builder Image +FROM golang:1.24 AS backend-builder + +WORKDIR /app + +# Install dependencies COPY go.mod go.sum ./ RUN go mod download +# Build COPY . . +COPY --from=frontend-builder /app/dist ./frontend/dist RUN go build -v -o ./bin/ . # Distribution Image @@ -14,10 +36,8 @@ FROM alpine:latest RUN apk add --no-cache libc6-compat -COPY --from=builder /twitchets/bin/twitchets /usr/bin/twitchets - -WORKDIR /twitchets +COPY --from=backend-builder /app/bin/twitchets /usr/bin/twitchets -EXPOSE 5656 +EXPOSE 9000 ENTRYPOINT ["/usr/bin/twitchets"] diff --git a/README.md b/README.md index 46ef1f6..4d94da7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ Powered by [twigots](https://github.com/ahobsonsayers/twigots), a Go package to - No need to have the Twickets app or an account - Choose from various notification services (Telegram, Ntfy, Gotify currently supported) +### And a fancy configuration UI! + +![frontend](assets/frontend.mp4) + ## Getting an API Key To use this tool, you will need a Twickets API key. Although Twickets doesn't provide a free API, you can easily obtain a key by following these steps: diff --git a/Taskfile.yaml b/Taskfile.yaml index c5d05ef..a113ce1 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -3,19 +3,48 @@ version: "3" tasks: format: + - task: format:be + - task: format:fe + + format:be: cmds: - go tool gofumpt -l -w . - lint: + format:fe: + dir: frontend + cmds: + - pnpm run format + + lint:be: cmds: - go tool golangci-lint run --fix ./... + lint: + - task: lint:be + - task: lint:fe + + lint:fe: + dir: frontend + cmds: + - pnpm run lint + generate: + - task: build:be + - task: build:fe + + generate:be: aliases: - - gen + - gen:be cmds: - go generate ./... + generate:fe: + dir: frontend + aliases: + - gen:fe + cmds: + - pnpm run generate + test: cmds: - go test ./... @@ -26,17 +55,31 @@ tasks: - task: test build: + - task: build:fe + - task: build:be + + build:be: cmds: - go build -v -o ./bin/ . + build:fe: + dir: frontend + cmds: + - run build + build:docker: cmds: - docker build . -t arranhs/twitchets - run: + run:be: cmds: - go run . + run:fe: + dir: frontend + cmds: + - pnpm run dev + run:docker: deps: [build:docker] cmds: diff --git a/assets/frontend.mp4 b/assets/frontend.mp4 new file mode 100644 index 0000000..a1fa037 Binary files /dev/null and b/assets/frontend.mp4 differ diff --git a/config.example.yaml b/config.example.yaml index 027dce3..1908a19 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -32,11 +32,11 @@ global: regions: - GBLO # London only - # Event name similarity matching (0.0 - 1.0) + # Required event name similarity (0.0 - 1.0) # Default: 0.9 (allows for minor naming differences) eventSimilarity: 0.9 - # Minimum number of tickets required in listing + # Required Number of tickets # Default: Any number of tickets numTickets: 2 # Exactly two tickets diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..6cadbb7 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,2 @@ +# API Base URL is set in development so it take priority over window.location.origin +VITE_API_BASE_URL=http://localhost:9000/api diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..a8c649c --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,2 @@ +# API Base URL is not set in production so window.location.origin is used +VITE_API_BASE_URL= diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..ae11835 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,5 @@ +# Ignore certain directories +src/components/ui/ + +# Ignore certain files +src/types/openapi.d.ts diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..8a0a5c7 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "tailwindStylesheet": "./frontend/src/index.css", + "plugins": [ + "@trivago/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss" + ] +} diff --git a/frontend/biome.json b/frontend/biome.json new file mode 100644 index 0000000..cc0be5c --- /dev/null +++ b/frontend/biome.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json", + "files": { + "includes": ["**", "!src/components/ui", "!dist"], + "ignoreUnknown": true + }, + "formatter": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "assist": { + "actions": { + "source": { + "organizeImports": "off" + } + } + } +} diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..13e1db0 --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/frontend/dist/.gitkeep b/frontend/dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/embed.go b/frontend/embed.go new file mode 100644 index 0000000..a0126b0 --- /dev/null +++ b/frontend/embed.go @@ -0,0 +1,12 @@ +package frontend + +import ( + "embed" + "io/fs" +) + +//go:embed dist/* +var distFS embed.FS + +// DistDirFS contains the embedded dist directory files (without the "dist" prefix) +var DistFS, _ = fs.Sub(distFS, "dist") diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..2e84ce6 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Twitchets + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..bd5956a --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,56 @@ +{ + "name": "twitchets", + "private": true, + "version": "1.0.0", + "type": "module", + "engines": { + "node": "^25" + }, + "packageManager": "pnpm@10.13.1", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "biome lint . --fix --unsafe", + "format": "prettier . -w", + "generate": "openapi-typescript ../schema/server.openapi.yaml ../schema/models.openapi.yaml -o src/types/openapi.d.ts", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.1.11", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "immer": "^10.1.1", + "lodash": "^4.17.21", + "lucide-react": "^0.536.0", + "openapi-fetch": "^0.15.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-number-format": "^5.4.4", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.11" + }, + "devDependencies": { + "@biomejs/biome": "2.1.3", + "@trivago/prettier-plugin-sort-imports": "^5.2.2", + "@types/lodash": "^4.17.20", + "@types/node": "^24.1.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "globals": "^16.3.0", + "openapi-typescript": "^7.10.1", + "prettier": "^3.6.2", + "prettier-plugin-tailwindcss": "^0.6.14", + "tw-animate-css": "^1.3.6", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..b5ed4bd --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,3697 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + "@radix-ui/react-alert-dialog": + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-checkbox": + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-collapsible": + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-label": + specifier: ^2.1.7 + version: 2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-select": + specifier: ^2.2.5 + version: 2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-separator": + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-tooltip": + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@tailwindcss/vite": + specifier: ^4.1.11 + version: 4.1.11(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + immer: + specifier: ^10.1.1 + version: 10.1.1 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + lucide-react: + specifier: ^0.536.0 + version: 0.536.0(react@19.1.1) + openapi-fetch: + specifier: ^0.15.0 + version: 0.15.0 + react: + specifier: ^19.1.0 + version: 19.1.1 + react-dom: + specifier: ^19.1.0 + version: 19.1.1(react@19.1.1) + react-number-format: + specifier: ^5.4.4 + version: 5.4.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwindcss: + specifier: ^4.1.11 + version: 4.1.11 + devDependencies: + "@biomejs/biome": + specifier: 2.1.3 + version: 2.1.3 + "@trivago/prettier-plugin-sort-imports": + specifier: ^5.2.2 + version: 5.2.2(prettier@3.6.2) + "@types/lodash": + specifier: ^4.17.20 + version: 4.17.20 + "@types/node": + specifier: ^24.1.0 + version: 24.1.0 + "@types/react": + specifier: ^19.1.8 + version: 19.1.9 + "@types/react-dom": + specifier: ^19.1.6 + version: 19.1.7(@types/react@19.1.9) + "@vitejs/plugin-react": + specifier: ^4.6.0 + version: 4.7.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)) + globals: + specifier: ^16.3.0 + version: 16.3.0 + openapi-typescript: + specifier: ^7.10.1 + version: 7.10.1(typescript@5.8.3) + prettier: + specifier: ^3.6.2 + version: 3.6.2 + prettier-plugin-tailwindcss: + specifier: ^0.6.14 + version: 0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2))(prettier@3.6.2) + tw-animate-css: + specifier: ^1.3.6 + version: 1.3.6 + typescript: + specifier: ~5.8.3 + version: 5.8.3 + vite: + specifier: ^7.0.4 + version: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1) + +packages: + "@ampproject/remapping@2.3.0": + resolution: + { + integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, + } + engines: { node: ">=6.0.0" } + + "@babel/code-frame@7.27.1": + resolution: + { + integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==, + } + engines: { node: ">=6.9.0" } + + "@babel/compat-data@7.28.0": + resolution: + { + integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==, + } + engines: { node: ">=6.9.0" } + + "@babel/core@7.28.0": + resolution: + { + integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==, + } + engines: { node: ">=6.9.0" } + + "@babel/generator@7.28.0": + resolution: + { + integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-compilation-targets@7.27.2": + resolution: + { + integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-globals@7.28.0": + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-imports@7.27.1": + resolution: + { + integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-transforms@7.27.3": + resolution: + { + integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-plugin-utils@7.27.1": + resolution: + { + integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.27.1": + resolution: + { + integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-option@7.27.1": + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helpers@7.28.2": + resolution: + { + integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==, + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.28.0": + resolution: + { + integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==, + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/plugin-transform-react-jsx-self@7.27.1": + resolution: + { + integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-transform-react-jsx-source@7.27.1": + resolution: + { + integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/template@7.27.2": + resolution: + { + integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==, + } + engines: { node: ">=6.9.0" } + + "@babel/traverse@7.28.0": + resolution: + { + integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==, + } + engines: { node: ">=6.9.0" } + + "@babel/types@7.28.2": + resolution: + { + integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==, + } + engines: { node: ">=6.9.0" } + + "@biomejs/biome@2.1.3": + resolution: + { + integrity: sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w==, + } + engines: { node: ">=14.21.3" } + hasBin: true + + "@biomejs/cli-darwin-arm64@2.1.3": + resolution: + { + integrity: sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA==, + } + engines: { node: ">=14.21.3" } + cpu: [arm64] + os: [darwin] + + "@biomejs/cli-darwin-x64@2.1.3": + resolution: + { + integrity: sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw==, + } + engines: { node: ">=14.21.3" } + cpu: [x64] + os: [darwin] + + "@biomejs/cli-linux-arm64-musl@2.1.3": + resolution: + { + integrity: sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w==, + } + engines: { node: ">=14.21.3" } + cpu: [arm64] + os: [linux] + + "@biomejs/cli-linux-arm64@2.1.3": + resolution: + { + integrity: sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA==, + } + engines: { node: ">=14.21.3" } + cpu: [arm64] + os: [linux] + + "@biomejs/cli-linux-x64-musl@2.1.3": + resolution: + { + integrity: sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew==, + } + engines: { node: ">=14.21.3" } + cpu: [x64] + os: [linux] + + "@biomejs/cli-linux-x64@2.1.3": + resolution: + { + integrity: sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA==, + } + engines: { node: ">=14.21.3" } + cpu: [x64] + os: [linux] + + "@biomejs/cli-win32-arm64@2.1.3": + resolution: + { + integrity: sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ==, + } + engines: { node: ">=14.21.3" } + cpu: [arm64] + os: [win32] + + "@biomejs/cli-win32-x64@2.1.3": + resolution: + { + integrity: sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g==, + } + engines: { node: ">=14.21.3" } + cpu: [x64] + os: [win32] + + "@esbuild/aix-ppc64@0.25.8": + resolution: + { + integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.25.8": + resolution: + { + integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.25.8": + resolution: + { + integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.25.8": + resolution: + { + integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.25.8": + resolution: + { + integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.8": + resolution: + { + integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.25.8": + resolution: + { + integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.8": + resolution: + { + integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.25.8": + resolution: + { + integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.25.8": + resolution: + { + integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.25.8": + resolution: + { + integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.25.8": + resolution: + { + integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.25.8": + resolution: + { + integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.25.8": + resolution: + { + integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.8": + resolution: + { + integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.25.8": + resolution: + { + integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.25.8": + resolution: + { + integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.25.8": + resolution: + { + integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.8": + resolution: + { + integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.25.8": + resolution: + { + integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.8": + resolution: + { + integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/openharmony-arm64@0.25.8": + resolution: + { + integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openharmony] + + "@esbuild/sunos-x64@0.25.8": + resolution: + { + integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.25.8": + resolution: + { + integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.25.8": + resolution: + { + integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.25.8": + resolution: + { + integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@floating-ui/core@1.7.3": + resolution: + { + integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==, + } + + "@floating-ui/dom@1.7.3": + resolution: + { + integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==, + } + + "@floating-ui/react-dom@2.1.5": + resolution: + { + integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==, + } + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + + "@floating-ui/utils@0.2.10": + resolution: + { + integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, + } + + "@isaacs/fs-minipass@4.0.1": + resolution: + { + integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==, + } + engines: { node: ">=18.0.0" } + + "@jridgewell/gen-mapping@0.3.12": + resolution: + { + integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==, + } + + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/sourcemap-codec@1.5.4": + resolution: + { + integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==, + } + + "@jridgewell/trace-mapping@0.3.29": + resolution: + { + integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==, + } + + "@radix-ui/number@1.1.1": + resolution: + { + integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==, + } + + "@radix-ui/primitive@1.1.2": + resolution: + { + integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==, + } + + "@radix-ui/primitive@1.1.3": + resolution: + { + integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==, + } + + "@radix-ui/react-alert-dialog@1.1.15": + resolution: + { + integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-arrow@1.1.7": + resolution: + { + integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-checkbox@1.3.3": + resolution: + { + integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-collapsible@1.1.11": + resolution: + { + integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-collection@1.1.7": + resolution: + { + integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-compose-refs@1.1.2": + resolution: + { + integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-context@1.1.2": + resolution: + { + integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-dialog@1.1.15": + resolution: + { + integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-direction@1.1.1": + resolution: + { + integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-dismissable-layer@1.1.10": + resolution: + { + integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-dismissable-layer@1.1.11": + resolution: + { + integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-focus-guards@1.1.2": + resolution: + { + integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-focus-guards@1.1.3": + resolution: + { + integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-focus-scope@1.1.7": + resolution: + { + integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-id@1.1.1": + resolution: + { + integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-label@2.1.7": + resolution: + { + integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-popper@1.2.7": + resolution: + { + integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-popper@1.2.8": + resolution: + { + integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-portal@1.1.9": + resolution: + { + integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-presence@1.1.4": + resolution: + { + integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-presence@1.1.5": + resolution: + { + integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-primitive@2.1.3": + resolution: + { + integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-select@2.2.5": + resolution: + { + integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-separator@1.1.7": + resolution: + { + integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-slot@1.2.3": + resolution: + { + integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-tooltip@1.2.8": + resolution: + { + integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-use-callback-ref@1.1.1": + resolution: + { + integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-controllable-state@1.2.2": + resolution: + { + integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-effect-event@0.0.2": + resolution: + { + integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-escape-keydown@1.1.1": + resolution: + { + integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-layout-effect@1.1.1": + resolution: + { + integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-previous@1.1.1": + resolution: + { + integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-rect@1.1.1": + resolution: + { + integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-size@1.1.1": + resolution: + { + integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-visually-hidden@1.2.3": + resolution: + { + integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/rect@1.1.1": + resolution: + { + integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, + } + + "@redocly/ajv@8.17.1": + resolution: + { + integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==, + } + + "@redocly/config@0.22.2": + resolution: + { + integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==, + } + + "@redocly/openapi-core@1.34.5": + resolution: + { + integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==, + } + engines: { node: ">=18.17.0", npm: ">=9.5.0" } + + "@rolldown/pluginutils@1.0.0-beta.27": + resolution: + { + integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==, + } + + "@rollup/rollup-android-arm-eabi@4.45.3": + resolution: + { + integrity: sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA==, + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.45.3": + resolution: + { + integrity: sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ==, + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.45.3": + resolution: + { + integrity: sha512-6CfLF3eqKhCdhK0GUnR5ZS99OFz+dtOeB/uePznLKxjCsk5QjT/V0eSEBb4vj+o/ri3i35MseSEQHCLLAgClVw==, + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.45.3": + resolution: + { + integrity: sha512-QLWyWmAJG9elNTNLdcSXUT/M+J7DhEmvs1XPHYcgYkse3UHf9iWTJ+yTPlKMIetiQnNi+cNp+gY4gvjDpREfKw==, + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.45.3": + resolution: + { + integrity: sha512-ZOvBq+5nL0yrZIEo1eq6r7MPvkJ8kC1XATS/yHvcq3WbDNKNKBQ1uIF4hicyzDMoJt72G+sn1nKsFXpifZyRDA==, + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.45.3": + resolution: + { + integrity: sha512-AYvGR07wecEnyYSovyJ71pTOulbNvsrpRpK6i/IM1b0UGX1vFx51afYuPYPxnvE9aCl5xPnhQicEvdIMxClRgQ==, + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.45.3": + resolution: + { + integrity: sha512-Yx8Cp38tfRRToVLuIWzBHV25/QPzpUreOPIiUuNV7KahNPurYg2pYQ4l7aYnvpvklO1riX4643bXLvDsYSBIrA==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm-musleabihf@4.45.3": + resolution: + { + integrity: sha512-4dIYRNxlXGDKnO6qgcda6LxnObPO6r1OBU9HG8F9pAnHHLtfbiOqCzDvkeHknx+5mfFVH4tWOl+h+cHylwsPWA==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm64-gnu@4.45.3": + resolution: + { + integrity: sha512-M6uVlWKmhLN7LguLDu6396K1W5IBlAaRonjlHQgc3s4dOGceu0FeBuvbXiUPYvup/6b5Ln7IEX7XNm68DN4vrg==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-arm64-musl@4.45.3": + resolution: + { + integrity: sha512-emaYiOTQJUd6fC9a6jcw9zIWtzaUiuBC+vomggaM4In2iOra/lA6IMHlqZqQZr08NYXrOPMVigreLMeSAwv3Uw==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-loongarch64-gnu@4.45.3": + resolution: + { + integrity: sha512-3P77T5AQ4UfVRJSrTKLiUZDJ6XsxeP80027bp6mOFh8sevSD038mYuIYFiUtrSJxxgFb+NgRJFF9oIa0rlUsmg==, + } + cpu: [loong64] + os: [linux] + + "@rollup/rollup-linux-ppc64-gnu@4.45.3": + resolution: + { + integrity: sha512-/VPH3ZVeSlmCBPhZdx/+4dMXDjaGMhDsWOBo9EwSkGbw2+OAqaslL53Ao2OqCxR0GgYjmmssJ+OoG+qYGE7IBg==, + } + cpu: [ppc64] + os: [linux] + + "@rollup/rollup-linux-riscv64-gnu@4.45.3": + resolution: + { + integrity: sha512-Hs5if0PjROl1MGMmZX3xMAIfqcGxQE2SJWUr/CpDQsOQn43Wq4IvXXxUMWtiY/BrzdqCCJlRgJ5DKxzS3qWkCw==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-riscv64-musl@4.45.3": + resolution: + { + integrity: sha512-Qm0WOwh3Lk388+HJFl1ILGbd2iOoQf6yl4fdGqOjBzEA+5JYbLcwd+sGsZjs5pkt8Cr/1G42EiXmlRp9ZeTvFA==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-s390x-gnu@4.45.3": + resolution: + { + integrity: sha512-VJdknTaYw+TqXzlh9c7vaVMh/fV2sU8Khfk4a9vAdYXJawpjf6z3U1k7vDWx2IQ9ZOPoOPxgVpDfYOYhxD7QUA==, + } + cpu: [s390x] + os: [linux] + + "@rollup/rollup-linux-x64-gnu@4.45.3": + resolution: + { + integrity: sha512-SUDXU5YabLAMl86FpupSQQEWzVG8X0HM+Q/famnJusbPiUgQnTGuSxtxg4UAYgv1ZmRV1nioYYXsgtSokU/7+Q==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-linux-x64-musl@4.45.3": + resolution: + { + integrity: sha512-ezmqknOUFgZMN6wW+Avlo4sXF3Frswd+ncrwMz4duyZ5Eqd+dAYgJ+A1MY+12LNZ7XDhCiijJceueYvtnzdviw==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-win32-arm64-msvc@4.45.3": + resolution: + { + integrity: sha512-1YfXoUEE++gIW66zNB9Twd0Ua5xCXpfYppFUxVT/Io5ZT3fO6Se+C/Jvmh3usaIHHyi53t3kpfjydO2GAy5eBA==, + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.45.3": + resolution: + { + integrity: sha512-Iok2YA3PvC163rVZf2Zy81A0g88IUcSPeU5pOilcbICXre2EP1mxn1Db/l09Z/SK1vdSLtpJXAnwGuMOyf5O9g==, + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.45.3": + resolution: + { + integrity: sha512-HwHCH5GQTOeGYP5wBEBXFVhfQecwRl24Rugoqhh8YwGarsU09bHhOKuqlyW4ZolZCan3eTUax7UJbGSmKSM51A==, + } + cpu: [x64] + os: [win32] + + "@tailwindcss/node@4.1.11": + resolution: + { + integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==, + } + + "@tailwindcss/oxide-android-arm64@4.1.11": + resolution: + { + integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [android] + + "@tailwindcss/oxide-darwin-arm64@4.1.11": + resolution: + { + integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [darwin] + + "@tailwindcss/oxide-darwin-x64@4.1.11": + resolution: + { + integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [darwin] + + "@tailwindcss/oxide-freebsd-x64@4.1.11": + resolution: + { + integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [freebsd] + + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": + resolution: + { + integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==, + } + engines: { node: ">= 10" } + cpu: [arm] + os: [linux] + + "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": + resolution: + { + integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [linux] + + "@tailwindcss/oxide-linux-arm64-musl@4.1.11": + resolution: + { + integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [linux] + + "@tailwindcss/oxide-linux-x64-gnu@4.1.11": + resolution: + { + integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@tailwindcss/oxide-linux-x64-musl@4.1.11": + resolution: + { + integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@tailwindcss/oxide-wasm32-wasi@4.1.11": + resolution: + { + integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==, + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + bundledDependencies: + - "@napi-rs/wasm-runtime" + - "@emnapi/core" + - "@emnapi/runtime" + - "@tybys/wasm-util" + - "@emnapi/wasi-threads" + - tslib + + "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": + resolution: + { + integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [win32] + + "@tailwindcss/oxide-win32-x64-msvc@4.1.11": + resolution: + { + integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [win32] + + "@tailwindcss/oxide@4.1.11": + resolution: + { + integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==, + } + engines: { node: ">= 10" } + + "@tailwindcss/vite@4.1.11": + resolution: + { + integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==, + } + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + "@trivago/prettier-plugin-sort-imports@5.2.2": + resolution: + { + integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==, + } + engines: { node: ">18.12" } + peerDependencies: + "@vue/compiler-sfc": 3.x + prettier: 2.x - 3.x + prettier-plugin-svelte: 3.x + svelte: 4.x || 5.x + peerDependenciesMeta: + "@vue/compiler-sfc": + optional: true + prettier-plugin-svelte: + optional: true + svelte: + optional: true + + "@types/babel__core@7.20.5": + resolution: + { + integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, + } + + "@types/babel__generator@7.27.0": + resolution: + { + integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, + } + + "@types/babel__template@7.4.4": + resolution: + { + integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, + } + + "@types/babel__traverse@7.28.0": + resolution: + { + integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==, + } + + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + "@types/lodash@4.17.20": + resolution: + { + integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==, + } + + "@types/node@24.1.0": + resolution: + { + integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==, + } + + "@types/react-dom@19.1.7": + resolution: + { + integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==, + } + peerDependencies: + "@types/react": ^19.0.0 + + "@types/react@19.1.9": + resolution: + { + integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==, + } + + "@vitejs/plugin-react@4.7.0": + resolution: + { + integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, + } + engines: { node: ">= 14" } + + ansi-colors@4.1.3: + resolution: + { + integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, + } + engines: { node: ">=6" } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + aria-hidden@1.2.6: + resolution: + { + integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==, + } + engines: { node: ">=10" } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, + } + + browserslist@4.25.1: + resolution: + { + integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + caniuse-lite@1.0.30001731: + resolution: + { + integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==, + } + + change-case@5.4.4: + resolution: + { + integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==, + } + + chownr@3.0.0: + resolution: + { + integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==, + } + engines: { node: ">=18" } + + class-variance-authority@0.7.1: + resolution: + { + integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==, + } + + clsx@2.1.1: + resolution: + { + integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, + } + engines: { node: ">=6" } + + colorette@1.4.0: + resolution: + { + integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==, + } + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + + csstype@3.1.3: + resolution: + { + integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, + } + + debug@4.4.1: + resolution: + { + integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.0.4: + resolution: + { + integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==, + } + engines: { node: ">=8" } + + detect-node-es@1.1.0: + resolution: + { + integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, + } + + electron-to-chromium@1.5.194: + resolution: + { + integrity: sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==, + } + + enhanced-resolve@5.18.2: + resolution: + { + integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==, + } + engines: { node: ">=10.13.0" } + + esbuild@0.25.8: + resolution: + { + integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==, + } + engines: { node: ">=18" } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: ">=6" } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + + fdir@6.4.6: + resolution: + { + integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==, + } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, + } + engines: { node: ">=6.9.0" } + + get-nonce@1.0.1: + resolution: + { + integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==, + } + engines: { node: ">=6" } + + globals@16.3.0: + resolution: + { + integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==, + } + engines: { node: ">=18" } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, + } + engines: { node: ">= 14" } + + immer@10.1.1: + resolution: + { + integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==, + } + + index-to-position@1.2.0: + resolution: + { + integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==, + } + engines: { node: ">=18" } + + javascript-natural-sort@0.7.1: + resolution: + { + integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==, + } + + jiti@2.5.1: + resolution: + { + integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==, + } + hasBin: true + + js-levenshtein@1.1.6: + resolution: + { + integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==, + } + engines: { node: ">=0.10.0" } + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-yaml@4.1.1: + resolution: + { + integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, + } + hasBin: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, + } + engines: { node: ">=6" } + hasBin: true + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, + } + + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, + } + engines: { node: ">=6" } + hasBin: true + + lightningcss-darwin-arm64@1.30.1: + resolution: + { + integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: + { + integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: + { + integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: + { + integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: + { + integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: + { + integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: + { + integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: + { + integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: + { + integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: + { + integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: + { + integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==, + } + engines: { node: ">= 12.0.0" } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + } + + lucide-react@0.536.0: + resolution: + { + integrity: sha512-2PgvNa9v+qz4Jt/ni8vPLt4jwoFybXHuubQT8fv4iCW5TjDxkbZjNZZHa485ad73NSEn/jdsEtU57eE1g+ma8A==, + } + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.17: + resolution: + { + integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, + } + + minimatch@5.1.6: + resolution: + { + integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, + } + engines: { node: ">=10" } + + minipass@7.1.2: + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, + } + engines: { node: ">=16 || 14 >=14.17" } + + minizlib@3.0.2: + resolution: + { + integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==, + } + engines: { node: ">= 18" } + + mkdirp@3.0.1: + resolution: + { + integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==, + } + engines: { node: ">=10" } + hasBin: true + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + node-releases@2.0.19: + resolution: + { + integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, + } + + openapi-fetch@0.15.0: + resolution: + { + integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==, + } + + openapi-typescript-helpers@0.0.15: + resolution: + { + integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==, + } + + openapi-typescript@7.10.1: + resolution: + { + integrity: sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==, + } + hasBin: true + peerDependencies: + typescript: ^5.x + + parse-json@8.3.0: + resolution: + { + integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==, + } + engines: { node: ">=18" } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, + } + engines: { node: ">=12" } + + pluralize@8.0.0: + resolution: + { + integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==, + } + engines: { node: ">=4" } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prettier-plugin-tailwindcss@0.6.14: + resolution: + { + integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==, + } + engines: { node: ">=14.21.3" } + peerDependencies: + "@ianvs/prettier-plugin-sort-imports": "*" + "@prettier/plugin-hermes": "*" + "@prettier/plugin-oxc": "*" + "@prettier/plugin-pug": "*" + "@shopify/prettier-plugin-liquid": "*" + "@trivago/prettier-plugin-sort-imports": "*" + "@zackad/prettier-plugin-twig": "*" + prettier: ^3.0 + prettier-plugin-astro: "*" + prettier-plugin-css-order: "*" + prettier-plugin-import-sort: "*" + prettier-plugin-jsdoc: "*" + prettier-plugin-marko: "*" + prettier-plugin-multiline-arrays: "*" + prettier-plugin-organize-attributes: "*" + prettier-plugin-organize-imports: "*" + prettier-plugin-sort-imports: "*" + prettier-plugin-style-order: "*" + prettier-plugin-svelte: "*" + peerDependenciesMeta: + "@ianvs/prettier-plugin-sort-imports": + optional: true + "@prettier/plugin-hermes": + optional: true + "@prettier/plugin-oxc": + optional: true + "@prettier/plugin-pug": + optional: true + "@shopify/prettier-plugin-liquid": + optional: true + "@trivago/prettier-plugin-sort-imports": + optional: true + "@zackad/prettier-plugin-twig": + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.6.2: + resolution: + { + integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==, + } + engines: { node: ">=14" } + hasBin: true + + react-dom@19.1.1: + resolution: + { + integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==, + } + peerDependencies: + react: ^19.1.1 + + react-number-format@5.4.4: + resolution: + { + integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==, + } + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-refresh@0.17.0: + resolution: + { + integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==, + } + engines: { node: ">=0.10.0" } + + react-remove-scroll-bar@2.3.8: + resolution: + { + integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + + react-remove-scroll@2.7.1: + resolution: + { + integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + react-style-singleton@2.2.3: + resolution: + { + integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + react@19.1.1: + resolution: + { + integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==, + } + engines: { node: ">=0.10.0" } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, + } + engines: { node: ">=0.10.0" } + + rollup@4.45.3: + resolution: + { + integrity: sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + scheduler@0.26.0: + resolution: + { + integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==, + } + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, + } + hasBin: true + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } + + supports-color@10.2.2: + resolution: + { + integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==, + } + engines: { node: ">=18" } + + tailwind-merge@3.3.1: + resolution: + { + integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==, + } + + tailwindcss@4.1.11: + resolution: + { + integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==, + } + + tapable@2.2.2: + resolution: + { + integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==, + } + engines: { node: ">=6" } + + tar@7.4.3: + resolution: + { + integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==, + } + engines: { node: ">=18" } + + tinyglobby@0.2.14: + resolution: + { + integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==, + } + engines: { node: ">=12.0.0" } + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + tw-animate-css@1.3.6: + resolution: + { + integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==, + } + + type-fest@4.41.0: + resolution: + { + integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==, + } + engines: { node: ">=16" } + + typescript@5.8.3: + resolution: + { + integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, + } + engines: { node: ">=14.17" } + hasBin: true + + undici-types@7.8.0: + resolution: + { + integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==, + } + + update-browserslist-db@1.1.3: + resolution: + { + integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + + use-callback-ref@1.3.3: + resolution: + { + integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + use-sidecar@1.1.3: + resolution: + { + integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + vite@7.0.6: + resolution: + { + integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, + } + + yallist@5.0.0: + resolution: + { + integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==, + } + engines: { node: ">=18" } + + yaml-ast-parser@0.0.43: + resolution: + { + integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==, + } + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: ">=12" } + +snapshots: + "@ampproject/remapping@2.3.0": + dependencies: + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 + + "@babel/code-frame@7.27.1": + dependencies: + "@babel/helper-validator-identifier": 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + "@babel/compat-data@7.28.0": {} + + "@babel/core@7.28.0": + dependencies: + "@ampproject/remapping": 2.3.0 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.0 + "@babel/helper-compilation-targets": 7.27.2 + "@babel/helper-module-transforms": 7.27.3(@babel/core@7.28.0) + "@babel/helpers": 7.28.2 + "@babel/parser": 7.28.0 + "@babel/template": 7.27.2 + "@babel/traverse": 7.28.0 + "@babel/types": 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1(supports-color@10.2.2) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/generator@7.28.0": + dependencies: + "@babel/parser": 7.28.0 + "@babel/types": 7.28.2 + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 + jsesc: 3.1.0 + + "@babel/helper-compilation-targets@7.27.2": + dependencies: + "@babel/compat-data": 7.28.0 + "@babel/helper-validator-option": 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + "@babel/helper-globals@7.28.0": {} + + "@babel/helper-module-imports@7.27.1": + dependencies: + "@babel/traverse": 7.28.0 + "@babel/types": 7.28.2 + transitivePeerDependencies: + - supports-color + + "@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + "@babel/helper-module-imports": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + "@babel/traverse": 7.28.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-plugin-utils@7.27.1": {} + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.27.1": {} + + "@babel/helper-validator-option@7.27.1": {} + + "@babel/helpers@7.28.2": + dependencies: + "@babel/template": 7.27.2 + "@babel/types": 7.28.2 + + "@babel/parser@7.28.0": + dependencies: + "@babel/types": 7.28.2 + + "@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/template@7.27.2": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/parser": 7.28.0 + "@babel/types": 7.28.2 + + "@babel/traverse@7.28.0": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.0 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.28.0 + "@babel/template": 7.27.2 + "@babel/types": 7.28.2 + debug: 4.4.1(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + "@babel/types@7.28.2": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + + "@biomejs/biome@2.1.3": + optionalDependencies: + "@biomejs/cli-darwin-arm64": 2.1.3 + "@biomejs/cli-darwin-x64": 2.1.3 + "@biomejs/cli-linux-arm64": 2.1.3 + "@biomejs/cli-linux-arm64-musl": 2.1.3 + "@biomejs/cli-linux-x64": 2.1.3 + "@biomejs/cli-linux-x64-musl": 2.1.3 + "@biomejs/cli-win32-arm64": 2.1.3 + "@biomejs/cli-win32-x64": 2.1.3 + + "@biomejs/cli-darwin-arm64@2.1.3": + optional: true + + "@biomejs/cli-darwin-x64@2.1.3": + optional: true + + "@biomejs/cli-linux-arm64-musl@2.1.3": + optional: true + + "@biomejs/cli-linux-arm64@2.1.3": + optional: true + + "@biomejs/cli-linux-x64-musl@2.1.3": + optional: true + + "@biomejs/cli-linux-x64@2.1.3": + optional: true + + "@biomejs/cli-win32-arm64@2.1.3": + optional: true + + "@biomejs/cli-win32-x64@2.1.3": + optional: true + + "@esbuild/aix-ppc64@0.25.8": + optional: true + + "@esbuild/android-arm64@0.25.8": + optional: true + + "@esbuild/android-arm@0.25.8": + optional: true + + "@esbuild/android-x64@0.25.8": + optional: true + + "@esbuild/darwin-arm64@0.25.8": + optional: true + + "@esbuild/darwin-x64@0.25.8": + optional: true + + "@esbuild/freebsd-arm64@0.25.8": + optional: true + + "@esbuild/freebsd-x64@0.25.8": + optional: true + + "@esbuild/linux-arm64@0.25.8": + optional: true + + "@esbuild/linux-arm@0.25.8": + optional: true + + "@esbuild/linux-ia32@0.25.8": + optional: true + + "@esbuild/linux-loong64@0.25.8": + optional: true + + "@esbuild/linux-mips64el@0.25.8": + optional: true + + "@esbuild/linux-ppc64@0.25.8": + optional: true + + "@esbuild/linux-riscv64@0.25.8": + optional: true + + "@esbuild/linux-s390x@0.25.8": + optional: true + + "@esbuild/linux-x64@0.25.8": + optional: true + + "@esbuild/netbsd-arm64@0.25.8": + optional: true + + "@esbuild/netbsd-x64@0.25.8": + optional: true + + "@esbuild/openbsd-arm64@0.25.8": + optional: true + + "@esbuild/openbsd-x64@0.25.8": + optional: true + + "@esbuild/openharmony-arm64@0.25.8": + optional: true + + "@esbuild/sunos-x64@0.25.8": + optional: true + + "@esbuild/win32-arm64@0.25.8": + optional: true + + "@esbuild/win32-ia32@0.25.8": + optional: true + + "@esbuild/win32-x64@0.25.8": + optional: true + + "@floating-ui/core@1.7.3": + dependencies: + "@floating-ui/utils": 0.2.10 + + "@floating-ui/dom@1.7.3": + dependencies: + "@floating-ui/core": 1.7.3 + "@floating-ui/utils": 0.2.10 + + "@floating-ui/react-dom@2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@floating-ui/dom": 1.7.3 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + "@floating-ui/utils@0.2.10": {} + + "@isaacs/fs-minipass@4.0.1": + dependencies: + minipass: 7.1.2 + + "@jridgewell/gen-mapping@0.3.12": + dependencies: + "@jridgewell/sourcemap-codec": 1.5.4 + "@jridgewell/trace-mapping": 0.3.29 + + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/sourcemap-codec@1.5.4": {} + + "@jridgewell/trace-mapping@0.3.29": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.4 + + "@radix-ui/number@1.1.1": {} + + "@radix-ui/primitive@1.1.2": {} + + "@radix-ui/primitive@1.1.3": {} + + "@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-dialog": 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-context@1.1.2(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-dismissable-layer": 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-focus-guards": 1.1.3(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-direction@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.2 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-id@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-label@2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@floating-ui/react-dom": 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-rect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/rect": 1.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@floating-ui/react-dom": 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-rect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/rect": 1.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-select@2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/number": 1.1.1 + "@radix-ui/primitive": 1.1.2 + "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-direction": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-popper": 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-slot@1.2.3(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-dismissable-layer": 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-popper": 1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/react-use-effect-event": 0.0.2(@types/react@19.1.9)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-previous@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-rect@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/rect": 1.1.1 + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-use-size@1.1.1(@types/react@19.1.9)(react@19.1.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.9 + + "@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + "@types/react-dom": 19.1.7(@types/react@19.1.9) + + "@radix-ui/rect@1.1.1": {} + + "@redocly/ajv@8.17.1": + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + "@redocly/config@0.22.2": {} + + "@redocly/openapi-core@1.34.5(supports-color@10.2.2)": + dependencies: + "@redocly/ajv": 8.17.1 + "@redocly/config": 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + + "@rolldown/pluginutils@1.0.0-beta.27": {} + + "@rollup/rollup-android-arm-eabi@4.45.3": + optional: true + + "@rollup/rollup-android-arm64@4.45.3": + optional: true + + "@rollup/rollup-darwin-arm64@4.45.3": + optional: true + + "@rollup/rollup-darwin-x64@4.45.3": + optional: true + + "@rollup/rollup-freebsd-arm64@4.45.3": + optional: true + + "@rollup/rollup-freebsd-x64@4.45.3": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.45.3": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.45.3": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.45.3": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.45.3": + optional: true + + "@rollup/rollup-linux-loongarch64-gnu@4.45.3": + optional: true + + "@rollup/rollup-linux-ppc64-gnu@4.45.3": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.45.3": + optional: true + + "@rollup/rollup-linux-riscv64-musl@4.45.3": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.45.3": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.45.3": + optional: true + + "@rollup/rollup-linux-x64-musl@4.45.3": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.45.3": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.45.3": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.45.3": + optional: true + + "@tailwindcss/node@4.1.11": + dependencies: + "@ampproject/remapping": 2.3.0 + enhanced-resolve: 5.18.2 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + "@tailwindcss/oxide-android-arm64@4.1.11": + optional: true + + "@tailwindcss/oxide-darwin-arm64@4.1.11": + optional: true + + "@tailwindcss/oxide-darwin-x64@4.1.11": + optional: true + + "@tailwindcss/oxide-freebsd-x64@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-arm64-musl@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-x64-gnu@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-x64-musl@4.1.11": + optional: true + + "@tailwindcss/oxide-wasm32-wasi@4.1.11": + optional: true + + "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": + optional: true + + "@tailwindcss/oxide-win32-x64-msvc@4.1.11": + optional: true + + "@tailwindcss/oxide@4.1.11": + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + "@tailwindcss/oxide-android-arm64": 4.1.11 + "@tailwindcss/oxide-darwin-arm64": 4.1.11 + "@tailwindcss/oxide-darwin-x64": 4.1.11 + "@tailwindcss/oxide-freebsd-x64": 4.1.11 + "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.11 + "@tailwindcss/oxide-linux-arm64-gnu": 4.1.11 + "@tailwindcss/oxide-linux-arm64-musl": 4.1.11 + "@tailwindcss/oxide-linux-x64-gnu": 4.1.11 + "@tailwindcss/oxide-linux-x64-musl": 4.1.11 + "@tailwindcss/oxide-wasm32-wasi": 4.1.11 + "@tailwindcss/oxide-win32-arm64-msvc": 4.1.11 + "@tailwindcss/oxide-win32-x64-msvc": 4.1.11 + + "@tailwindcss/vite@4.1.11(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1))": + dependencies: + "@tailwindcss/node": 4.1.11 + "@tailwindcss/oxide": 4.1.11 + tailwindcss: 4.1.11 + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1) + + "@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2)": + dependencies: + "@babel/generator": 7.28.0 + "@babel/parser": 7.28.0 + "@babel/traverse": 7.28.0 + "@babel/types": 7.28.2 + javascript-natural-sort: 0.7.1 + lodash: 4.17.21 + prettier: 3.6.2 + transitivePeerDependencies: + - supports-color + + "@types/babel__core@7.20.5": + dependencies: + "@babel/parser": 7.28.0 + "@babel/types": 7.28.2 + "@types/babel__generator": 7.27.0 + "@types/babel__template": 7.4.4 + "@types/babel__traverse": 7.28.0 + + "@types/babel__generator@7.27.0": + dependencies: + "@babel/types": 7.28.2 + + "@types/babel__template@7.4.4": + dependencies: + "@babel/parser": 7.28.0 + "@babel/types": 7.28.2 + + "@types/babel__traverse@7.28.0": + dependencies: + "@babel/types": 7.28.2 + + "@types/estree@1.0.8": {} + + "@types/lodash@4.17.20": {} + + "@types/node@24.1.0": + dependencies: + undici-types: 7.8.0 + + "@types/react-dom@19.1.7(@types/react@19.1.9)": + dependencies: + "@types/react": 19.1.9 + + "@types/react@19.1.9": + dependencies: + csstype: 3.1.3 + + "@vitejs/plugin-react@4.7.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1))": + dependencies: + "@babel/core": 7.28.0 + "@babel/plugin-transform-react-jsx-self": 7.27.1(@babel/core@7.28.0) + "@babel/plugin-transform-react-jsx-source": 7.27.1(@babel/core@7.28.0) + "@rolldown/pluginutils": 1.0.0-beta.27 + "@types/babel__core": 7.20.5 + react-refresh: 0.17.0 + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1) + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + ansi-colors@4.1.3: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + balanced-match@1.0.2: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001731 + electron-to-chromium: 1.5.194 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + caniuse-lite@1.0.30001731: {} + + change-case@5.4.4: {} + + chownr@3.0.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + colorette@1.4.0: {} + + convert-source-map@2.0.0: {} + + csstype@3.1.3: {} + + debug@4.4.1(supports-color@10.2.2): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + detect-libc@2.0.4: {} + + detect-node-es@1.1.0: {} + + electron-to-chromium@1.5.194: {} + + enhanced-resolve@5.18.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + esbuild@0.25.8: + optionalDependencies: + "@esbuild/aix-ppc64": 0.25.8 + "@esbuild/android-arm": 0.25.8 + "@esbuild/android-arm64": 0.25.8 + "@esbuild/android-x64": 0.25.8 + "@esbuild/darwin-arm64": 0.25.8 + "@esbuild/darwin-x64": 0.25.8 + "@esbuild/freebsd-arm64": 0.25.8 + "@esbuild/freebsd-x64": 0.25.8 + "@esbuild/linux-arm": 0.25.8 + "@esbuild/linux-arm64": 0.25.8 + "@esbuild/linux-ia32": 0.25.8 + "@esbuild/linux-loong64": 0.25.8 + "@esbuild/linux-mips64el": 0.25.8 + "@esbuild/linux-ppc64": 0.25.8 + "@esbuild/linux-riscv64": 0.25.8 + "@esbuild/linux-s390x": 0.25.8 + "@esbuild/linux-x64": 0.25.8 + "@esbuild/netbsd-arm64": 0.25.8 + "@esbuild/netbsd-x64": 0.25.8 + "@esbuild/openbsd-arm64": 0.25.8 + "@esbuild/openbsd-x64": 0.25.8 + "@esbuild/openharmony-arm64": 0.25.8 + "@esbuild/sunos-x64": 0.25.8 + "@esbuild/win32-arm64": 0.25.8 + "@esbuild/win32-ia32": 0.25.8 + "@esbuild/win32-x64": 0.25.8 + + escalade@3.2.0: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + globals@16.3.0: {} + + graceful-fs@4.2.11: {} + + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + immer@10.1.1: {} + + index-to-position@1.2.0: {} + + javascript-natural-sort@0.7.1: {} + + jiti@2.5.1: {} + + js-levenshtein@1.1.6: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + lodash@4.17.21: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.536.0(react@19.1.1): + dependencies: + react: 19.1.1 + + magic-string@0.30.17: + dependencies: + "@jridgewell/sourcemap-codec": 1.5.4 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-releases@2.0.19: {} + + openapi-fetch@0.15.0: + dependencies: + openapi-typescript-helpers: 0.0.15 + + openapi-typescript-helpers@0.0.15: {} + + openapi-typescript@7.10.1(typescript@5.8.3): + dependencies: + "@redocly/openapi-core": 1.34.5(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.8.3 + yargs-parser: 21.1.1 + + parse-json@8.3.0: + dependencies: + "@babel/code-frame": 7.27.1 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pluralize@8.0.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-plugin-tailwindcss@0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2))(prettier@3.6.2): + dependencies: + prettier: 3.6.2 + optionalDependencies: + "@trivago/prettier-plugin-sort-imports": 5.2.2(prettier@3.6.2) + + prettier@3.6.2: {} + + react-dom@19.1.1(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.26.0 + + react-number-format@5.4.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1) + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.1.9 + + react-remove-scroll@2.7.1(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.9)(react@19.1.1) + react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.9)(react@19.1.1) + use-sidecar: 1.1.3(@types/react@19.1.9)(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.9 + + react-style-singleton@2.2.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + get-nonce: 1.0.1 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.1.9 + + react@19.1.1: {} + + require-from-string@2.0.2: {} + + rollup@4.45.3: + dependencies: + "@types/estree": 1.0.8 + optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.45.3 + "@rollup/rollup-android-arm64": 4.45.3 + "@rollup/rollup-darwin-arm64": 4.45.3 + "@rollup/rollup-darwin-x64": 4.45.3 + "@rollup/rollup-freebsd-arm64": 4.45.3 + "@rollup/rollup-freebsd-x64": 4.45.3 + "@rollup/rollup-linux-arm-gnueabihf": 4.45.3 + "@rollup/rollup-linux-arm-musleabihf": 4.45.3 + "@rollup/rollup-linux-arm64-gnu": 4.45.3 + "@rollup/rollup-linux-arm64-musl": 4.45.3 + "@rollup/rollup-linux-loongarch64-gnu": 4.45.3 + "@rollup/rollup-linux-ppc64-gnu": 4.45.3 + "@rollup/rollup-linux-riscv64-gnu": 4.45.3 + "@rollup/rollup-linux-riscv64-musl": 4.45.3 + "@rollup/rollup-linux-s390x-gnu": 4.45.3 + "@rollup/rollup-linux-x64-gnu": 4.45.3 + "@rollup/rollup-linux-x64-musl": 4.45.3 + "@rollup/rollup-win32-arm64-msvc": 4.45.3 + "@rollup/rollup-win32-ia32-msvc": 4.45.3 + "@rollup/rollup-win32-x64-msvc": 4.45.3 + fsevents: 2.3.3 + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + source-map-js@1.2.1: {} + + supports-color@10.2.2: {} + + tailwind-merge@3.3.1: {} + + tailwindcss@4.1.11: {} + + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + "@isaacs/fs-minipass": 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + tslib@2.8.1: {} + + tw-animate-css@1.3.6: {} + + type-fest@4.41.0: {} + + typescript@5.8.3: {} + + undici-types@7.8.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.1.9 + + use-sidecar@1.1.3(@types/react@19.1.9)(react@19.1.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + "@types/react": 19.1.9 + + vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1): + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.3 + tinyglobby: 0.2.14 + optionalDependencies: + "@types/node": 24.1.0 + fsevents: 2.3.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yaml-ast-parser@0.0.43: {} + + yargs-parser@21.1.1: {} diff --git a/frontend/src/app.css b/frontend/src/app.css new file mode 100644 index 0000000..f4c1e9b --- /dev/null +++ b/frontend/src/app.css @@ -0,0 +1,120 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx new file mode 100644 index 0000000..062a792 --- /dev/null +++ b/frontend/src/app.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { GeneralSettings } from "./components/configGeneral"; +import { GlobalSettings } from "./components/configGlobal"; +import { NotificationSettings } from "./components/configNotification"; +import { TicketsConfig } from "./components/configTickets"; +import { ConfigProvider } from "./providers/config"; +import { ThemeProvider } from "./providers/theme"; + +function App() { + return ( + + +
+

Twitchets Configuration

+ + + + +
+
+
+ ); +} + +export default App; diff --git a/frontend/src/components/buttonReset.tsx b/frontend/src/components/buttonReset.tsx new file mode 100644 index 0000000..d2b9844 --- /dev/null +++ b/frontend/src/components/buttonReset.tsx @@ -0,0 +1,41 @@ +import { Button } from "./ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "./ui/tooltip"; +import { Globe, RotateCcw } from "lucide-react"; + +interface ResetButtonProps { + onClick: () => void; + resetType: "default" | "global"; +} + +export function ResetButton({ onClick, resetType }: ResetButtonProps) { + const Icon = resetType === "default" ? RotateCcw : Globe; + + const tooltip = + resetType === "default" ? "Reset to default" : "Reset to global setting"; + + return ( + + + + + + +

{tooltip}

+
+
+
+ ); +} diff --git a/frontend/src/components/buttonsSaveDiscard.tsx b/frontend/src/components/buttonsSaveDiscard.tsx new file mode 100644 index 0000000..707b3a8 --- /dev/null +++ b/frontend/src/components/buttonsSaveDiscard.tsx @@ -0,0 +1,33 @@ +import { Button } from "./ui/button"; + +interface SaveDiscardButtonsProps { + onSave: () => void; + onDiscard: () => void; +} + +export function SaveDiscardButtons({ + onSave, + onDiscard, +}: SaveDiscardButtonsProps) { + const handleSave = (e: React.MouseEvent) => { + e.stopPropagation(); + onSave(); + }; + + const handleDiscard = (e: React.MouseEvent) => { + e.stopPropagation(); + onDiscard(); + }; + + return ( +
+ + + +
+ ); +} diff --git a/frontend/src/components/cardCollapsible.tsx b/frontend/src/components/cardCollapsible.tsx new file mode 100644 index 0000000..a67d456 --- /dev/null +++ b/frontend/src/components/cardCollapsible.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@radix-ui/react-collapsible"; +import { ChevronsDownUp, ChevronsUpDown } from "lucide-react"; +import { type ReactNode, useState } from "react"; + +interface CollapsibleCardProps { + title: string; + description?: string; + isOpen?: boolean; + setIsOpen?: (isOpen: boolean) => void; + action?: ReactNode; + children?: ReactNode; +} + +export function CollapsibleCard({ + title, + description, + isOpen: isOpenProp, + setIsOpen: setIsOpenProp, + action, + children, +}: CollapsibleCardProps) { + const [isOpenInternal, setIsOpenInteral] = useState(false); + + const isOpen = isOpenProp ?? isOpenInternal; + const setIsOpen = setIsOpenProp ?? setIsOpenInteral; + + const toggleCollapse = () => { + setIsOpen(!isOpen); + }; + + return ( + + + + +
+ {title} + {description && {description}} +
+
+ {action} + {isOpen ? ( + + ) : ( + + )} +
+
+
+ + {children} + +
+
+ ); +} diff --git a/frontend/src/components/configCommon.tsx b/frontend/src/components/configCommon.tsx new file mode 100644 index 0000000..feaf5b3 --- /dev/null +++ b/frontend/src/components/configCommon.tsx @@ -0,0 +1,86 @@ +import { ConfigField } from "./configField"; +import { Regions } from "./configRegions"; +import type { CommonConfig } from "@/types/config"; + +interface CommonFieldsProps { + commonConfig: CommonConfig; + globalCommonConfig?: CommonConfig; // Unset if common config IS global config + updateCommonConfig: (config: CommonConfig) => void; +} + +export function CommonFields({ + commonConfig: config, + globalCommonConfig: globalConfig, + updateCommonConfig: updateConfig, +}: CommonFieldsProps) { + return ( +
+ { + updateConfig({ ...config, regions: value }); + }} + /> + +
+ { + updateConfig({ ...config, eventSimilarity: value }); + }} + /> + + { + updateConfig({ ...config, numTickets: value }); + }} + /> + + { + updateConfig({ ...config, maxTicketPrice: value }); + }} + /> + + { + updateConfig({ ...config, discount: value }); + }} + /> +
+
+ ); +} diff --git a/frontend/src/components/configField.tsx b/frontend/src/components/configField.tsx new file mode 100644 index 0000000..4a8602f --- /dev/null +++ b/frontend/src/components/configField.tsx @@ -0,0 +1,129 @@ +import { LinkedStatusTooltip } from "./statusLinked"; +import { ResetButton } from "@/components/buttonReset"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { NumericFormat } from "react-number-format"; + +interface ConfigFieldProps { + label: string; + description: string; + placeholder?: string; + type: "text" | "number" | "integer" | "fraction" | "percentage" | "price"; + value?: T; + globalFallbackValue?: T; + withGlobalFallback?: boolean; + showReset?: boolean; + updateValue: (newValue?: T) => void; +} + +export function ConfigField({ + label, + description, + placeholder, + type, + value, + withGlobalFallback = false, + globalFallbackValue = undefined, + showReset = true, + updateValue, +}: ConfigFieldProps) { + // Determine the field value to display + let fieldValue = value; + let isLinkedToGlobal = false; + if (fieldValue === undefined && withGlobalFallback) { + // If no value is set, and we want to use global fallback + fieldValue = globalFallbackValue; + isLinkedToGlobal = true; + } else if (typeof fieldValue === "number" && fieldValue < 0) { + // If value is negative number, set undefined to show placeholder + fieldValue = undefined; + } + + // Determine the reset value based on type + let resetValue: T; + if (type === "text") { + resetValue = "" as T; + } else { + resetValue = -1 as T; + } + + const renderInput = () => { + if (type === "text") { + return ( + updateValue(event.target.value as T)} + /> + ); + } + + // TODO better to use use-mask-input - but this currently reverses numbers??? + return ( + { + // Only update if the value is not undefined + // This prevents reverting from -1 (which is displayed as undefined) back to undefined + if (values.floatValue !== undefined) { + updateValue(values.floatValue as T); + } + }} + isAllowed={(values) => { + const val = values.floatValue; + if (val === undefined) return true; + if (type === "fraction") return val <= 1; + if (type === "percentage") return val <= 100; + return true; + }} + suffix={type === "percentage" ? "%" : ""} + prefix={type === "price" ? "£" : ""} + /> + ); + }; + + return ( +
+
+
+ + + {withGlobalFallback && ( + + )} +
+ +
+ {withGlobalFallback && ( + { + // The global button sets the value to "undefined". + // This causes the global value to be inherited. + updateValue(undefined); + }} + /> + )} + + {showReset && ( + { + updateValue(resetValue); + }} + /> + )} +
+
+ +

{description}

+ + {renderInput()} +
+ ); +} diff --git a/frontend/src/components/configGeneral.tsx b/frontend/src/components/configGeneral.tsx new file mode 100644 index 0000000..306ab0e --- /dev/null +++ b/frontend/src/components/configGeneral.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useConfig } from "../providers/config"; +import type { Country } from "../types/config"; +import { SaveDiscardButtons } from "./buttonsSaveDiscard"; +import { CollapsibleCard } from "./cardCollapsible"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import { Label } from "./ui/label"; +import { + Select, + SelectContent, + SelectTrigger, + SelectValue, + SelectItem, +} from "./ui/select"; +import { isEqual } from "lodash"; +import { Eye, EyeOff } from "lucide-react"; +import { useState, useEffect } from "react"; + +export function GeneralSettings() { + const { config, updateConfig } = useConfig(); + + const [draft, setDraft] = useState({ + apiKey: config.apiKey, + country: config.country, + }); + const [showApiKey, setShowApiKey] = useState(false); + + // If the canonical config changes, reset the draft + useEffect(() => { + setDraft({ apiKey: config.apiKey, country: config.country }); + }, [config.apiKey, config.country]); + + const hasChanges = !isEqual(draft, { + apiKey: config.apiKey, + country: config.country, + }); + + const toggleShowApiKey = () => { + setShowApiKey(!showApiKey); + }; + + return ( + { + updateConfig((config) => { + config.apiKey = draft.apiKey; + config.country = draft.country; + }); + }} + onDiscard={() => { + setDraft({ + apiKey: config.apiKey, + country: config.country, + }); + }} + /> + ) + } + > +
+
+ +

+ Twickets API key (required) +

+
+ { + setDraft((prev) => ({ ...prev, apiKey: event.target.value })); + }} + /> + +
+
+ +
+ +

+ Currently only GB is supported +

+ +
+
+
+ ); +} diff --git a/frontend/src/components/configGlobal.tsx b/frontend/src/components/configGlobal.tsx new file mode 100644 index 0000000..b7d1f72 --- /dev/null +++ b/frontend/src/components/configGlobal.tsx @@ -0,0 +1,46 @@ +import { useConfig } from "../providers/config"; +import { SaveDiscardButtons } from "./buttonsSaveDiscard"; +import { CollapsibleCard } from "./cardCollapsible"; +import { CommonFields } from "./configCommon"; +import { isEqual } from "lodash"; +import { useState, useEffect } from "react"; + +export function GlobalSettings() { + const { config, updateConfig } = useConfig(); + + const [draft, setDraft] = useState(config.global); + + useEffect(() => { + setDraft(config.global); + }, [config.global]); + + const hasChanges = !isEqual(draft, config.global); + + return ( + { + updateConfig((config) => { + config.global = draft; + }); + }} + onDiscard={() => { + setDraft(config.global); + }} + /> + ) + } + > + { + setDraft(commonConfig); + }} + /> + + ); +} diff --git a/frontend/src/components/configNotification.tsx b/frontend/src/components/configNotification.tsx new file mode 100644 index 0000000..3523494 --- /dev/null +++ b/frontend/src/components/configNotification.tsx @@ -0,0 +1,190 @@ +"use client"; + +import { useConfig } from "../providers/config"; +import { SaveDiscardButtons } from "./buttonsSaveDiscard"; +import { CollapsibleCard } from "./cardCollapsible"; +import { Button } from "./ui/button"; +import { Checkbox } from "./ui/checkbox"; +import { Input } from "./ui/input"; +import { Label } from "./ui/label"; +import { Separator } from "./ui/separator"; +import type { NtfyConfig } from "@/types/config"; +import { isEqual, omit } from "lodash"; +import { Eye, EyeOff } from "lucide-react"; +import { useState, useEffect } from "react"; + +const defaultNtfyUrl = "https://ntfy.sh"; + +function newNtfyConfig(): NtfyConfig { + return { + url: defaultNtfyUrl, + topic: "", + username: "", + password: "", + }; +} + +export function NotificationSettings() { + const { config, updateConfig } = useConfig(); + + const [draft, setDraft] = useState(config.notification); + const [showNtfyPassword, setShowNtfyPassword] = useState(false); + + useEffect(() => { + setDraft(config.notification); + }, [config.notification]); + + const hasChanges = !isEqual(draft, config.notification); + + const toggleShowNtfyPassword = () => { + setShowNtfyPassword(!showNtfyPassword); + }; + + return ( + { + updateConfig((config) => { + config.notification = draft; + }); + }} + onDiscard={() => { + setDraft(config.notification); + }} + /> + ) + } + > +
+
+
+

Ntfy

+ { + setDraft((prev) => { + if (checked) { + return { + ...prev, + ntfy: newNtfyConfig(), + }; + } else { + return omit(prev, "ntfy"); + } + }); + }} + /> + +
+ + {draft.ntfy && ( + <> +
+
+ + { + setDraft((prev) => ({ + ...prev, + ntfy: { + ...(prev.ntfy || newNtfyConfig()), + url: + event.target.value === "" + ? defaultNtfyUrl + : event.target.value, + }, + })); + }} + /> +
+ +
+ + { + setDraft((prev) => ({ + ...prev, + ntfy: { + ...(prev.ntfy || newNtfyConfig()), + topic: event.target.value, + }, + })); + }} + /> +
+
+ +
+
+ + { + setDraft((prev) => ({ + ...prev, + ntfy: { + ...(prev.ntfy || newNtfyConfig()), + username: event.target.value, + }, + })); + }} + /> +
+ +
+ +
+ { + setDraft((prev) => ({ + ...prev, + ntfy: { + ...(prev.ntfy || newNtfyConfig()), + password: event.target.value, + }, + })); + }} + /> + +
+
+
+ + )} +
+ +

+ Telegram and Gotify notification settings not yet supported in user + interface +

+
+
+ ); +} diff --git a/frontend/src/components/configRegions.tsx b/frontend/src/components/configRegions.tsx new file mode 100644 index 0000000..cdd32bc --- /dev/null +++ b/frontend/src/components/configRegions.tsx @@ -0,0 +1,98 @@ +import { LinkedStatusTooltip } from "./statusLinked"; +import { Checkbox } from "./ui/checkbox"; +import { ResetButton } from "@/components/buttonReset"; +import { Label } from "@/components/ui/label"; +import { REGIONS } from "@/constants/regions"; +import type { Region } from "@/types/config"; + +interface RegionsProps { + label?: string; + description?: string; + value?: Region[]; + withGlobalFallback?: boolean; + globalFallbackValue?: Region[]; + updateValue: (newValue?: Region[]) => void; +} + +export function Regions({ + label = "Regions", + description = "If no regions selected, all regions will be used", + value, + withGlobalFallback = false, + globalFallbackValue, + updateValue, +}: RegionsProps) { + // Determine the field value to display + let fieldValue = value; + let isLinkedToGlobal = false; + if (fieldValue === undefined && withGlobalFallback) { + // If no value is set, and we want to use global fallback + fieldValue = globalFallbackValue; + isLinkedToGlobal = true; + } + + const currentRegions = fieldValue || []; + const resetValue: Region[] = []; + + const handleOnCheckedChange = (regionCode: Region, checked: boolean) => { + const regionSet = new Set(currentRegions); + + if (checked) { + regionSet.add(regionCode); + } else { + regionSet.delete(regionCode); + } + + updateValue(Array.from(regionSet).sort()); + }; + + return ( +
+
+
+ + + {withGlobalFallback && ( + + )} +
+ +
+ {withGlobalFallback && ( + { + // The global button sets the value to "undefined". + // This causes the global value to be inherited. + updateValue(undefined); + }} + /> + )} + + { + updateValue(resetValue); + }} + /> +
+
+ +

{description}

+ +
+ {REGIONS.map((region) => ( +
+ { + handleOnCheckedChange(region.code as Region, checked); + }} + /> + +
+ ))} +
+
+ ); +} diff --git a/frontend/src/components/configTicket.tsx b/frontend/src/components/configTicket.tsx new file mode 100644 index 0000000..2c3dd7c --- /dev/null +++ b/frontend/src/components/configTicket.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { SaveDiscardButtons } from "./buttonsSaveDiscard"; +import { CollapsibleCard } from "./cardCollapsible"; +import { CommonFields } from "./configCommon"; +import { ConfigField } from "./configField"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import type { CommonConfig, TicketConfig } from "@/types/config"; +import { isEqual } from "lodash"; +import { Trash, } from "lucide-react"; +import { useEffect, useState } from "react"; + +interface TicketProps { + ticketConfig: TicketConfig; + globalConfig: CommonConfig; + isOpen?: boolean; + onUpdate: (updatedTicket: TicketConfig) => void; + onRemove: () => void; +} + +export function Ticket({ + ticketConfig, + globalConfig, + onUpdate, + onRemove, +}: TicketProps) { + const [draft, setDraft] = useState(ticketConfig); + + useEffect(() => { + setDraft(ticketConfig); + }, [ticketConfig]); + + const hasChanges = !isEqual(ticketConfig, draft); + + return ( + + {hasChanges && ( + { + onUpdate(draft); + }} + onDiscard={() => { + setDraft(ticketConfig); + }} + /> + )} + + + + + + + + Delete Ticket + + Are you sure you want to delete "{ticketConfig.event}"? This + action cannot be undone. + + + + Cancel + + Delete + + + + + + } + > +
+ { + if (typeof value === "string") { + setDraft((prev) => ({ ...prev, event: value })); + } + }} + /> + + { + setDraft((prev) => ({ ...prev, ...commonConfig })); + }} + /> +
+
+ ); +} diff --git a/frontend/src/components/configTickets.tsx b/frontend/src/components/configTickets.tsx new file mode 100644 index 0000000..8ad566b --- /dev/null +++ b/frontend/src/components/configTickets.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useConfig } from "../providers/config"; +import { Ticket } from "./configTicket"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "./ui/card"; +import { Input } from "./ui/input"; +import { Button } from "@/components/ui/button"; +import type { TicketConfig } from "@/types/config"; +import { Plus } from "lucide-react"; +import { useMemo, useState } from "react"; + +export function TicketsConfig() { + const { config, updateConfig } = useConfig(); + + const [_, setIsTicketsConfigOpen] = useState(false); + const [filterText, setFilterText] = useState(""); + + const newEventName = "New Event"; + + // Get tickets (ensuring they are ordered) + const tickets = useMemo(() => { + return sortTickets(config.tickets); + }, [config.tickets]); + + const handleAddTicket = () => { + const newTickets = [...tickets, { event: newEventName }]; + updateConfig((config) => { + config.tickets = sortTickets(newTickets); + }); + setIsTicketsConfigOpen(true); + }; + + const handleUpdateTicket = (updatedTicket: TicketConfig, index: number) => { + const newTickets = [...tickets]; + newTickets[index] = updatedTicket; + + updateConfig((config) => { + config.tickets = sortTickets(newTickets); + }); + }; + + const handleRemoveTicket = (index: number) => { + updateConfig((config) => { + const newTickets = [...config.tickets]; + newTickets.splice(index, 1); + config.tickets = newTickets; + }); + }; + + return ( + + +
+ Ticket Configuration + + Individual ticket configuration (overrides global configuration) + +
+ +
+ +
+ setFilterText(event.target.value)} + /> + {config.tickets.length === 0 ? ( +

+ No tickets configured. Click "Add Ticket" to get started. +

+ ) : ( + tickets + .filter((ticket) => + ticket.event.toLowerCase().includes(filterText.toLowerCase()), + ) + .map((ticket, ticketIndex) => { + return ( + + handleUpdateTicket(updatedTicket, ticketIndex) + } + onRemove={() => handleRemoveTicket(ticketIndex)} + /> + ); + }) + )} +
+
+
+ ); +} + +function sortTickets(tickets: TicketConfig[]): TicketConfig[] { + return [...tickets].sort((a, b) => { + // "New Event" should always appear first + if (a.event === "New Event") return -1; + if (b.event === "New Event") return 1; + + // Otherwise, sort alphabetically + return a.event.localeCompare(b.event); + }); +} diff --git a/frontend/src/components/statusLinked.tsx b/frontend/src/components/statusLinked.tsx new file mode 100644 index 0000000..a1d0214 --- /dev/null +++ b/frontend/src/components/statusLinked.tsx @@ -0,0 +1,31 @@ +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "./ui/tooltip"; +import { Link, Unlink } from "lucide-react"; + +interface LinkedStatusTooltipProps { + isLinked: boolean; +} + +export function LinkedStatusTooltip({ isLinked }: LinkedStatusTooltipProps) { + const Icon = isLinked ? Link : Unlink; + const text = isLinked + ? "Linked to global value" + : "Unlinked from global value"; + + return ( + + + + + + +

{text}

+
+
+
+ ); +} diff --git a/frontend/src/components/ui/alert-dialog.tsx b/frontend/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..0863e40 --- /dev/null +++ b/frontend/src/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx new file mode 100644 index 0000000..1421354 --- /dev/null +++ b/frontend/src/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..21409a0 --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,60 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx new file mode 100644 index 0000000..681ad98 --- /dev/null +++ b/frontend/src/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..0e2a6cd --- /dev/null +++ b/frontend/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/frontend/src/components/ui/collapsible.tsx b/frontend/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..ae9fad0 --- /dev/null +++ b/frontend/src/components/ui/collapsible.tsx @@ -0,0 +1,33 @@ +"use client" + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +function Collapsible({ + ...props +}: React.ComponentProps) { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx new file mode 100644 index 0000000..8916905 --- /dev/null +++ b/frontend/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx new file mode 100644 index 0000000..ef7133a --- /dev/null +++ b/frontend/src/components/ui/label.tsx @@ -0,0 +1,22 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx new file mode 100644 index 0000000..25e5439 --- /dev/null +++ b/frontend/src/components/ui/select.tsx @@ -0,0 +1,187 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + ...props +}: React.ComponentProps) { + return +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "popper", + align = "center", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/frontend/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx new file mode 100644 index 0000000..bb3ad74 --- /dev/null +++ b/frontend/src/components/ui/separator.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..715bf76 --- /dev/null +++ b/frontend/src/components/ui/tooltip.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/frontend/src/constants/regions.ts b/frontend/src/constants/regions.ts new file mode 100644 index 0000000..1184c04 --- /dev/null +++ b/frontend/src/constants/regions.ts @@ -0,0 +1,19 @@ +export interface Region { + code: string; + name: string; +} + +export const REGIONS: Region[] = [ + { code: "GBLO", name: "London" }, + { code: "GBSO", name: "South" }, + { code: "GBSW", name: "South West" }, + { code: "GBSE", name: "South East" }, + { code: "GBMI", name: "Midlands" }, + { code: "GBEA", name: "East Anglia" }, + { code: "GBNO", name: "North" }, + { code: "GBNE", name: "North East" }, + { code: "GBNW", name: "North West" }, + { code: "GBSC", name: "Scotland" }, + { code: "GBWA", name: "Wales" }, + { code: "GBNI", name: "Northern Ireland" }, +]; diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..31575de --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,27 @@ +import type { Config } from "../types/config"; +import type { paths } from "../types/openapi"; +import createClient from "openapi-fetch"; + +const client = createClient({ + baseUrl: import.meta.env.VITE_API_BASE_URL || `${window.location.origin}/api`, +}); + +export async function getConfig(): Promise { + const { data, error } = await client.GET("/config", {}); + if (error) { + throw new Error(`Failed to fetch config: ${error}`); + } + if (!data) { + throw new Error("No config data received"); + } + return data; +} + +export async function putConfig(config: Config): Promise { + const { error } = await client.PUT("/config", { + body: config, + }); + if (error) { + throw new Error(`Failed to update config: ${error}`); + } +} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..409087b --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,14 @@ +import App from "./app"; +import "./app.css"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +const node = document.getElementById("root"); +if (!node) throw new Error("Root element not found"); + +const root = createRoot(node); +root.render( + + + , +); diff --git a/frontend/src/providers/config.tsx b/frontend/src/providers/config.tsx new file mode 100644 index 0000000..9e7bb8a --- /dev/null +++ b/frontend/src/providers/config.tsx @@ -0,0 +1,131 @@ +import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert"; +import { getConfig, putConfig } from "../lib/api"; +import type { Config } from "../types/config"; +import { produce } from "immer"; +import { TriangleAlertIcon, Save } from "lucide-react"; +import { + createContext, + type ReactNode, + useContext, + useEffect, + useState, +} from "react"; + +function newConfig(): Config { + return { + apiKey: "", + country: "GB", + notification: {}, + global: {}, + tickets: [], + }; +} + +type ConfigUpdater = (config: Config) => void; + +type ConfigProviderState = { + config: Config; + updateConfig: (updater: ConfigUpdater) => void; + saved: boolean; + error: string | null; +}; + +const ConfigProviderContext = createContext({ + config: newConfig(), + updateConfig: () => null, + saved: false, + error: null, +}); + +type ConfigProviderProps = { + children: ReactNode; +}; + +export function ConfigProvider({ children, ...props }: ConfigProviderProps) { + const [config, setConfig] = useState(newConfig()); + const [saved, setSaved] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const loadConfig = async () => { + try { + const serverConfig = await getConfig(); + setConfig(serverConfig); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load config"); + } + }; + + loadConfig(); + }, []); + + useEffect(() => { + if (!saved) return; + const timer = setTimeout(() => setSaved(false), 5000); + return () => clearTimeout(timer); + }, [saved]); + + useEffect(() => { + if (!error) return; + const timer = setTimeout(() => setError(null), 5000); + return () => clearTimeout(timer); + }, [error]); + + const updateConfig = async (updater: ConfigUpdater) => { + const newConfig = produce(config, updater); + setConfig(newConfig); + + try { + await putConfig(newConfig); + setError(null); + setSaved(true); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to update config"); + // Revert on error + setConfig(config); + } + }; + + const value = { + config, + updateConfig, + saved, + error, + }; + + return ( + + {children} + {saved && ( +
+ + + Saved + + Changes have been saved + + +
+ )} + {error && ( +
+ + + Error + + {error} + + +
+ )} +
+ ); +} + +export const useConfig = () => { + const context = useContext(ConfigProviderContext); + if (context === undefined) + throw new Error("useConfig must be used within a ConfigProvider"); + + return context; +}; diff --git a/frontend/src/providers/theme.tsx b/frontend/src/providers/theme.tsx new file mode 100644 index 0000000..424b3b5 --- /dev/null +++ b/frontend/src/providers/theme.tsx @@ -0,0 +1,71 @@ +import { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "dark" | "light" | "system"; + +type ThemeProviderState = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const ThemeProviderContext = createContext({ + theme: "system", + setTheme: () => null, +}); + +type ThemeProviderProps = { + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove("light", "dark"); + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + + root.classList.add(systemTheme); + return; + } + + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext); + + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider"); + + return context; +}; diff --git a/frontend/src/types/config.d.ts b/frontend/src/types/config.d.ts new file mode 100644 index 0000000..0bad5dc --- /dev/null +++ b/frontend/src/types/config.d.ts @@ -0,0 +1,8 @@ +import type { components } from "./openapi"; + +export type CommonConfig = components["schemas"]["GlobalTicketListingConfig"]; +export type Config = components["schemas"]["Config"]; +export type Country = components["schemas"]["Country"]; +export type NtfyConfig = components["schemas"]["NtfyConfig"]; +export type Region = components["schemas"]["Region"]; +export type TicketConfig = components["schemas"]["TicketListingConfig"]; diff --git a/frontend/src/types/openapi.d.ts b/frontend/src/types/openapi.d.ts new file mode 100644 index 0000000..d729077 --- /dev/null +++ b/frontend/src/types/openapi.d.ts @@ -0,0 +1,254 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get current configuration + * @description Retrieve the current Twitchets configuration + */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Config"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + /** + * Update configuration + * @description Update the Twitchets configuration + */ + put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Config"]; + }; + }; + responses: { + /** @description Configuration updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Invalid configuration */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** + * @description Country code. + * Currently only GB is supported. + * @enum {string} + */ + Country: "GB"; + NtfyConfig: { + /** @description You can use the public instance at https://ntfy.sh */ + url: string; + /** @description If using https://ntfy.sh, make sure this is unique to you! */ + topic: string; + /** @description Optional: for authenticated instances */ + username: string; + /** @description Optional: for authenticated instances */ + password: string; + }; + GotifyConfig: { + /** @description Your Gotify server URL */ + url: string; + /** @description Application token from Gotify */ + token: string; + }; + TelegramConfig: { + /** @description Get from @BotFather on Telegram */ + token: string; + /** @description Your chat ID or group chat ID */ + chatId: number; + }; + /** @description Notification service configuration */ + NotificationConfig: { + ntfy?: components["schemas"]["NtfyConfig"]; + gotify?: components["schemas"]["GotifyConfig"]; + telegram?: components["schemas"]["TelegramConfig"]; + }; + /** + * @description Region code. + * Possible values are: + * - GBLO: London + * - GBSO: South + * - GBSW: South West + * - GBSE: South East + * - GBMI: Midlands + * - GBEA: East Anglia + * - GBNO: North + * - GBNE: North East + * - GBNW: North West + * - GBSC: Scotland + * - GBWA: Wales + * - GBNI: Northern Ireland + * @enum {string} + */ + Region: "GBLO" | "GBSO" | "GBSW" | "GBSE" | "GBMI" | "GBEA" | "GBNO" | "GBNE" | "GBNW" | "GBSC" | "GBWA" | "GBNI"; + /** @enum {string} */ + NotificationType: "ntfy" | "gotify" | "telegram"; + /** + * @description GlobalTicketListingConfig represents configuration settings that apply to all ticket listings + * unless explicitly overridden by a specific ticket configuration. + * Any setting not specified will use the default. + */ + GlobalTicketListingConfig: { + /** + * Format: double + * @description Event name similarity matching (0.0 - 1.0). + * Default: 0.9 (allows for minor naming differences) + */ + eventSimilarity?: number; + /** + * @description Geographic regions to search for tickets. + * Default: All regions if not specified. + * Full list: https://github.com/ahobsonsayers/twigots/blob/main/location.go#L79-L90 + */ + regions?: components["schemas"]["Region"][]; + /** + * @description Minimum number of tickets required in listing + * Default: Any number of tickets. + */ + numTickets?: number; + /** + * Format: double + * @description Minimum discount (including fee) on the original price as a percentage + * Default: Any discount (including no discount). + */ + discount?: number; + /** + * Format: double + * @description Maximum price per ticket (including fee) in pounds (£) + * Default: Any price. + */ + maxTicketPrice?: number; + /** + * @description Notification services to use + * Default: All configured services. + */ + notification?: components["schemas"]["NotificationType"][]; + }; + Regions: components["schemas"]["Region"][]; + Notifications: components["schemas"]["NotificationType"][]; + /** + * @description TicketListingConfig represents configuration for specific ticket listings + * Configuration overrides global configuration + * To reset a global configuration to its default, use: + * - "" (empty string) for string values + * - [] (empty array) for list values + * - -1 for numeric values + */ + TicketListingConfig: { + /** @description Event name */ + event: string; + /** + * Format: double + * @description Event name similarity matching (0.0 - 1.0). + * Overrides global setting. + */ + eventSimilarity?: number; + /** + * @description Geographic regions to search for tickets + * Overrides global setting. To reset to default (all regions), use an empty array []. + */ + regions?: components["schemas"]["Regions"]; + /** + * @description Number of tickets required in listing + * Overrides global setting. To reset to default (any number), use -1. + */ + numTickets?: number; + /** + * Format: double + * @description Minimum discount on the original price as a percentage + * Overrides global setting. To reset to default (any discount), use -1. + */ + discount?: number; + /** + * Format: double + * @description Maximum price per ticket (including fee) in pounds (£) + * Overrides global setting. To reset to default (any price), use -1. + */ + maxTicketPrice?: number; + /** + * @description Notification services to use + * Overrides global setting. To reset to default (all configured services), use an empty array []. + */ + notification?: components["schemas"]["Notifications"]; + }; + Config: { + /** @description REQUIRED: See README.md for details on how to obtain */ + apiKey: string; + country: components["schemas"]["Country"]; + notification: components["schemas"]["NotificationConfig"]; + global: components["schemas"]["GlobalTicketListingConfig"]; + tickets: components["schemas"]["TicketListingConfig"][]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..db6e072 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +export interface ImportMetaEnv { + readonly VITE_API_BASE_URL?: string; +} + +export interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..935f37d --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + + "compilerOptions": { + "module": "ESNext", + + /* Bundler mode */ + "moduleResolution": "bundler", + "noEmit": true, + "jsx": "preserve", + "allowImportingTsExtensions": true, + + /* Linting */ + "strict": true, + "erasableSyntaxOnly": true, + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..6256a2e --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import path from "node:path"; +import { defineConfig } from "vite"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +}); diff --git a/main.go b/main.go index 5181936..b92930c 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/ahobsonsayers/twigots" "github.com/ahobsonsayers/twitchets/config" + "github.com/ahobsonsayers/twitchets/frontend" "github.com/ahobsonsayers/twitchets/notification" "github.com/ahobsonsayers/twitchets/scanner" "github.com/ahobsonsayers/twitchets/server" @@ -80,7 +81,7 @@ func main() { }() // Run server - err = server.Start(9000, userConfigPath) + err = server.Start(9000, frontend.DistFS, userConfigPath) if err != nil { log.Fatalf("error running server: %v", err) } diff --git a/server/serve.go b/server/serve.go index a9a960f..4b88e40 100644 --- a/server/serve.go +++ b/server/serve.go @@ -3,6 +3,7 @@ package server import ( "context" "fmt" + "io/fs" "log" "log/slog" "net/http" @@ -16,7 +17,7 @@ import ( oapimiddleware "github.com/oapi-codegen/nethttp-middleware" ) -func Start(port int, configPath string) error { +func Start(port int, frontendFS fs.FS, configPath string) error { address := fmt.Sprintf("0.0.0.0:%d", port) // Create middlewares @@ -34,6 +35,10 @@ func Start(port int, configPath string) error { router := chi.NewRouter() router.Use(loggerMiddleware, corsMiddleware) + // Register routes for frontend + fileServer := http.FileServer(http.FS(frontendFS)) + router.Handle("/*", http.StripPrefix("/", fileServer)) + // Create api router and mount apiRouter := chi.NewRouter() apiRouter.Use(openapiValidationMiddleware)