From bb9187e9a0ef0c819186b225cc294d46bba534a3 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 11 Jun 2025 10:41:32 +0200 Subject: [PATCH] add new features and format --- spotizerr-ui/.prettierignore | 9 + spotizerr-ui/.prettierrc.json | 7 + spotizerr-ui/README.md | 16 +- spotizerr-ui/eslint.config.js | 37 +- spotizerr-ui/package.json | 10 +- spotizerr-ui/pnpm-lock.yaml | 2573 ++++++++++------- spotizerr-ui/postcss.config.mjs | 2 +- spotizerr-ui/src/components/Queue.tsx | 193 +- .../src/components/config/AccountsTab.tsx | 147 +- .../src/components/config/DownloadsTab.tsx | 139 +- .../src/components/config/FormattingTab.tsx | 164 +- .../src/components/config/GeneralTab.tsx | 196 +- .../src/components/config/ServerTab.tsx | 247 +- .../src/components/config/WatchTab.tsx | 122 +- spotizerr-ui/src/contexts/QueueProvider.tsx | 345 ++- .../src/contexts/SettingsProvider.tsx | 74 +- spotizerr-ui/src/contexts/queue-context.ts | 47 +- spotizerr-ui/src/contexts/settings-context.ts | 12 +- spotizerr-ui/src/lib/api-client.ts | 34 +- spotizerr-ui/src/main.tsx | 12 +- spotizerr-ui/src/router.tsx | 38 +- spotizerr-ui/src/routes/album.tsx | 84 +- spotizerr-ui/src/routes/artist.tsx | 316 +- spotizerr-ui/src/routes/config.tsx | 82 +- spotizerr-ui/src/routes/history.tsx | 377 ++- spotizerr-ui/src/routes/home.tsx | 195 +- spotizerr-ui/src/routes/playlist.tsx | 174 +- spotizerr-ui/src/routes/root.tsx | 54 +- spotizerr-ui/src/routes/track.tsx | 96 +- spotizerr-ui/src/routes/watchlist.tsx | 110 +- spotizerr-ui/src/types/settings.ts | 8 +- spotizerr-ui/src/types/spotify.ts | 1 + spotizerr-ui/tsconfig.json | 5 +- spotizerr-ui/vite.config.ts | 23 +- 34 files changed, 3405 insertions(+), 2544 deletions(-) create mode 100644 spotizerr-ui/.prettierignore create mode 100644 spotizerr-ui/.prettierrc.json diff --git a/spotizerr-ui/.prettierignore b/spotizerr-ui/.prettierignore new file mode 100644 index 0000000..d867097 --- /dev/null +++ b/spotizerr-ui/.prettierignore @@ -0,0 +1,9 @@ +node_modules +dist +.DS_Store +coverage +.pnpm-store +.vite +.env +.env.* +!.env.example diff --git a/spotizerr-ui/.prettierrc.json b/spotizerr-ui/.prettierrc.json new file mode 100644 index 0000000..388f854 --- /dev/null +++ b/spotizerr-ui/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 120, + "tabWidth": 2 +} diff --git a/spotizerr-ui/README.md b/spotizerr-ui/README.md index da98444..a2980c2 100644 --- a/spotizerr-ui/README.md +++ b/spotizerr-ui/README.md @@ -24,31 +24,31 @@ export default tseslint.config({ languageOptions: { // other options... parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, }, -}) +}); ``` You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +import reactX from "eslint-plugin-react-x"; +import reactDom from "eslint-plugin-react-dom"; export default tseslint.config({ plugins: { // Add the react-x and react-dom plugins - 'react-x': reactX, - 'react-dom': reactDom, + "react-x": reactX, + "react-dom": reactDom, }, rules: { // other rules... // Enable its recommended typescript rules - ...reactX.configs['recommended-typescript'].rules, + ...reactX.configs["recommended-typescript"].rules, ...reactDom.configs.recommended.rules, }, -}) +}); ``` diff --git a/spotizerr-ui/eslint.config.js b/spotizerr-ui/eslint.config.js index 092408a..d2ab424 100644 --- a/spotizerr-ui/eslint.config.js +++ b/spotizerr-ui/eslint.config.js @@ -1,28 +1,33 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import prettier from "eslint-plugin-prettier"; +import { readFileSync } from "node:fs"; -export default tseslint.config( - { ignores: ['dist'] }, +// Read Prettier configuration from .prettierrc.json +const prettierOptions = JSON.parse(readFileSync("./.prettierrc.json", "utf8")); + +export default [ + { ignores: ["dist"] }, + js.configs.recommended, + ...tseslint.configs.recommended, { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + prettier: prettier, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + "prettier/prettier": ["error", prettierOptions], }, }, -) +]; diff --git a/spotizerr-ui/package.json b/spotizerr-ui/package.json index 6294105..a4f034c 100644 --- a/spotizerr-ui/package.json +++ b/spotizerr-ui/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "lint": "eslint .", + "lint": "eslint . --fix", + "format": "prettier --write .", "preview": "vite preview" }, "dependencies": { @@ -16,13 +17,15 @@ "@tanstack/react-router": "^1.120.18", "@tanstack/react-table": "^8.21.3", "@tanstack/router-devtools": "^1.120.18", + "@types/uuid": "^10.0.0", "axios": "^1.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.57.0", "sonner": "^2.0.5", "tailwindcss": "^4.1.8", - "use-debounce": "^10.0.5" + "use-debounce": "^10.0.5", + "uuid": "^11.1.0" }, "devDependencies": { "@eslint/js": "^9.25.0", @@ -31,9 +34,12 @@ "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", "eslint": "^9.25.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "prettier": "^3.5.3", "typescript": "~5.8.3", "typescript-eslint": "^8.30.1", "vite": "^6.3.5" diff --git a/spotizerr-ui/pnpm-lock.yaml b/spotizerr-ui/pnpm-lock.yaml index a6e2dc7..541de6e 100644 --- a/spotizerr-ui/pnpm-lock.yaml +++ b/spotizerr-ui/pnpm-lock.yaml @@ -1,31 +1,33 @@ -lockfileVersion: '9.0' +lockfileVersion: "9.0" settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: - .: dependencies: - '@tailwindcss/postcss': + "@tailwindcss/postcss": specifier: ^4.1.8 version: 4.1.8 - '@tailwindcss/vite': + "@tailwindcss/vite": specifier: ^4.1.8 version: 4.1.8(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)) - '@tanstack/react-query': + "@tanstack/react-query": specifier: ^5.80.6 version: 5.80.6(react@19.1.0) - '@tanstack/react-router': + "@tanstack/react-router": specifier: ^1.120.18 version: 1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/react-table': + "@tanstack/react-table": specifier: ^8.21.3 version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-devtools': + "@tanstack/router-devtools": specifier: ^1.120.18 version: 1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3) + "@types/uuid": + specifier: ^10.0.0 + version: 10.0.0 axios: specifier: ^1.9.0 version: 1.9.0 @@ -47,25 +49,34 @@ importers: use-debounce: specifier: ^10.0.5 version: 10.0.5(react@19.1.0) + uuid: + specifier: ^11.1.0 + version: 11.1.0 devDependencies: - '@eslint/js': + "@eslint/js": specifier: ^9.25.0 version: 9.28.0 - '@types/node': + "@types/node": specifier: ^22.15.30 version: 22.15.30 - '@types/react': + "@types/react": specifier: ^19.1.2 version: 19.1.6 - '@types/react-dom': + "@types/react-dom": specifier: ^19.1.2 version: 19.1.6(@types/react@19.1.6) - '@vitejs/plugin-react': + "@vitejs/plugin-react": specifier: ^4.4.1 version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)) eslint: specifier: ^9.25.0 version: 9.28.0(jiti@2.4.2) + eslint-config-prettier: + specifier: ^10.1.5 + version: 10.1.5(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-prettier: + specifier: ^5.4.1 + version: 5.4.1(eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3) eslint-plugin-react-hooks: specifier: ^5.2.0 version: 5.2.0(eslint@9.28.0(jiti@2.4.2)) @@ -75,6 +86,9 @@ importers: globals: specifier: ^16.0.0 version: 16.2.0 + prettier: + specifier: ^3.5.3 + version: 3.5.3 typescript: specifier: ~5.8.3 version: 5.8.3 @@ -86,926 +100,1154 @@ importers: version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1) packages: + "@alloc/quick-lru@5.2.0": + resolution: + { integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== } + engines: { node: ">=10" } - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} + "@ampproject/remapping@2.3.0": + resolution: + { integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== } + engines: { node: ">=6.0.0" } - '@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/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} + "@babel/compat-data@7.27.5": + resolution: + { integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg== } + engines: { node: ">=6.9.0" } - '@babel/compat-data@7.27.5': - resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==} - engines: {node: '>=6.9.0'} + "@babel/core@7.27.4": + resolution: + { integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g== } + engines: { node: ">=6.9.0" } - '@babel/core@7.27.4': - resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} - engines: {node: '>=6.9.0'} + "@babel/generator@7.27.5": + resolution: + { integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw== } + engines: { node: ">=6.9.0" } - '@babel/generator@7.27.5': - resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} - engines: {node: '>=6.9.0'} + "@babel/helper-compilation-targets@7.27.2": + resolution: + { integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== } + engines: { node: ">=6.9.0" } - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - 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-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'} + "@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/core": ^7.0.0 - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.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-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-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/helper-validator-option@7.27.1": + resolution: + { integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== } + engines: { node: ">=6.9.0" } - '@babel/helpers@7.27.6': - resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} - engines: {node: '>=6.9.0'} + "@babel/helpers@7.27.6": + resolution: + { integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== } + engines: { node: ">=6.9.0" } - '@babel/parser@7.27.5': - resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} - engines: {node: '>=6.0.0'} + "@babel/parser@7.27.5": + resolution: + { integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg== } + 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'} + "@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/core": ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.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/core": ^7.0.0-0 - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} + "@babel/template@7.27.2": + resolution: + { integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== } + engines: { node: ">=6.9.0" } - '@babel/traverse@7.27.4': - resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} - engines: {node: '>=6.9.0'} + "@babel/traverse@7.27.4": + resolution: + { integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA== } + engines: { node: ">=6.9.0" } - '@babel/types@7.27.6': - resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} - engines: {node: '>=6.9.0'} + "@babel/types@7.27.6": + resolution: + { integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q== } + engines: { node: ">=6.9.0" } - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} + "@esbuild/aix-ppc64@0.25.5": + resolution: + { integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== } + engines: { node: ">=18" } cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} + "@esbuild/android-arm64@0.25.5": + resolution: + { integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== } + engines: { node: ">=18" } cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} + "@esbuild/android-arm@0.25.5": + resolution: + { integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== } + engines: { node: ">=18" } cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} + "@esbuild/android-x64@0.25.5": + resolution: + { integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== } + engines: { node: ">=18" } cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} + "@esbuild/darwin-arm64@0.25.5": + resolution: + { integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== } + engines: { node: ">=18" } cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} + "@esbuild/darwin-x64@0.25.5": + resolution: + { integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== } + engines: { node: ">=18" } cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} + "@esbuild/freebsd-arm64@0.25.5": + resolution: + { integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== } + engines: { node: ">=18" } cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} + "@esbuild/freebsd-x64@0.25.5": + resolution: + { integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== } + engines: { node: ">=18" } cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} + "@esbuild/linux-arm64@0.25.5": + resolution: + { integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== } + engines: { node: ">=18" } cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} + "@esbuild/linux-arm@0.25.5": + resolution: + { integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== } + engines: { node: ">=18" } cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} + "@esbuild/linux-ia32@0.25.5": + resolution: + { integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== } + engines: { node: ">=18" } cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} + "@esbuild/linux-loong64@0.25.5": + resolution: + { integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== } + engines: { node: ">=18" } cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} + "@esbuild/linux-mips64el@0.25.5": + resolution: + { integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== } + engines: { node: ">=18" } cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} + "@esbuild/linux-ppc64@0.25.5": + resolution: + { integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== } + engines: { node: ">=18" } cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} + "@esbuild/linux-riscv64@0.25.5": + resolution: + { integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== } + engines: { node: ">=18" } cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} + "@esbuild/linux-s390x@0.25.5": + resolution: + { integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== } + engines: { node: ">=18" } cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} + "@esbuild/linux-x64@0.25.5": + resolution: + { integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw== } + engines: { node: ">=18" } cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} + "@esbuild/netbsd-arm64@0.25.5": + resolution: + { integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== } + engines: { node: ">=18" } cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} + "@esbuild/netbsd-x64@0.25.5": + resolution: + { integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ== } + engines: { node: ">=18" } cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} + "@esbuild/openbsd-arm64@0.25.5": + resolution: + { integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== } + engines: { node: ">=18" } cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} + "@esbuild/openbsd-x64@0.25.5": + resolution: + { integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== } + engines: { node: ">=18" } cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} + "@esbuild/sunos-x64@0.25.5": + resolution: + { integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== } + engines: { node: ">=18" } cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} + "@esbuild/win32-arm64@0.25.5": + resolution: + { integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== } + engines: { node: ">=18" } cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} + "@esbuild/win32-ia32@0.25.5": + resolution: + { integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== } + engines: { node: ">=18" } cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - engines: {node: '>=18'} + "@esbuild/win32-x64@0.25.5": + resolution: + { integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g== } + engines: { node: ">=18" } cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + "@eslint-community/eslint-utils@4.7.0": + resolution: + { integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + "@eslint-community/regexpp@4.12.1": + resolution: + { integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - '@eslint/config-array@0.20.0': - resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/config-array@0.20.0": + resolution: + { integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@eslint/config-helpers@0.2.2': - resolution: {integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/config-helpers@0.2.2": + resolution: + { integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@eslint/core@0.14.0': - resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/core@0.14.0": + resolution: + { integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/eslintrc@3.3.1": + resolution: + { integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@eslint/js@9.28.0': - resolution: {integrity: sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/js@9.28.0": + resolution: + { integrity: sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/object-schema@2.1.6": + resolution: + { integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@eslint/plugin-kit@0.3.1': - resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@eslint/plugin-kit@0.3.1": + resolution: + { integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} + "@humanfs/core@0.19.1": + resolution: + { integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== } + engines: { node: ">=18.18.0" } - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} + "@humanfs/node@0.16.6": + resolution: + { integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== } + engines: { node: ">=18.18.0" } - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} + "@humanwhocodes/module-importer@1.0.1": + resolution: + { integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== } + engines: { node: ">=12.22" } - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} + "@humanwhocodes/retry@0.3.1": + resolution: + { integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== } + engines: { node: ">=18.18" } - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} + "@humanwhocodes/retry@0.4.3": + resolution: + { integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== } + engines: { node: ">=18.18" } - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} + "@isaacs/fs-minipass@4.0.1": + resolution: + { integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== } + engines: { node: ">=18.0.0" } - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + "@jridgewell/gen-mapping@0.3.8": + resolution: + { integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== } + engines: { node: ">=6.0.0" } - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} + "@jridgewell/resolve-uri@3.1.2": + resolution: + { integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== } + engines: { node: ">=6.0.0" } - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} + "@jridgewell/set-array@1.2.1": + resolution: + { integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== } + engines: { node: ">=6.0.0" } - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + "@jridgewell/sourcemap-codec@1.5.0": + resolution: + { integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== } - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + "@jridgewell/trace-mapping@0.3.25": + resolution: + { integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== } - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + "@nodelib/fs.scandir@2.1.5": + resolution: + { integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== } + engines: { node: ">= 8" } - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + "@nodelib/fs.stat@2.0.5": + resolution: + { integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== } + engines: { node: ">= 8" } - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + "@nodelib/fs.walk@1.2.8": + resolution: + { integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== } + engines: { node: ">= 8" } - '@rolldown/pluginutils@1.0.0-beta.9': - resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} + "@pkgr/core@0.2.7": + resolution: + { integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg== } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - '@rollup/rollup-android-arm-eabi@4.42.0': - resolution: {integrity: sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==} + "@rolldown/pluginutils@1.0.0-beta.9": + resolution: + { integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w== } + + "@rollup/rollup-android-arm-eabi@4.42.0": + resolution: + { integrity: sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ== } cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.42.0': - resolution: {integrity: sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==} + "@rollup/rollup-android-arm64@4.42.0": + resolution: + { integrity: sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw== } cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.42.0': - resolution: {integrity: sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==} + "@rollup/rollup-darwin-arm64@4.42.0": + resolution: + { integrity: sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA== } cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.42.0': - resolution: {integrity: sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==} + "@rollup/rollup-darwin-x64@4.42.0": + resolution: + { integrity: sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA== } cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.42.0': - resolution: {integrity: sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==} + "@rollup/rollup-freebsd-arm64@4.42.0": + resolution: + { integrity: sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ== } cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.42.0': - resolution: {integrity: sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==} + "@rollup/rollup-freebsd-x64@4.42.0": + resolution: + { integrity: sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ== } cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.42.0': - resolution: {integrity: sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==} + "@rollup/rollup-linux-arm-gnueabihf@4.42.0": + resolution: + { integrity: sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A== } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.42.0': - resolution: {integrity: sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==} + "@rollup/rollup-linux-arm-musleabihf@4.42.0": + resolution: + { integrity: sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ== } cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.42.0': - resolution: {integrity: sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==} + "@rollup/rollup-linux-arm64-gnu@4.42.0": + resolution: + { integrity: sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q== } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.42.0': - resolution: {integrity: sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==} + "@rollup/rollup-linux-arm64-musl@4.42.0": + resolution: + { integrity: sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g== } cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.42.0': - resolution: {integrity: sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==} + "@rollup/rollup-linux-loongarch64-gnu@4.42.0": + resolution: + { integrity: sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg== } cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.42.0': - resolution: {integrity: sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==} + "@rollup/rollup-linux-powerpc64le-gnu@4.42.0": + resolution: + { integrity: sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA== } cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.42.0': - resolution: {integrity: sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==} + "@rollup/rollup-linux-riscv64-gnu@4.42.0": + resolution: + { integrity: sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA== } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.42.0': - resolution: {integrity: sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==} + "@rollup/rollup-linux-riscv64-musl@4.42.0": + resolution: + { integrity: sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw== } cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.42.0': - resolution: {integrity: sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==} + "@rollup/rollup-linux-s390x-gnu@4.42.0": + resolution: + { integrity: sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw== } cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.42.0': - resolution: {integrity: sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==} + "@rollup/rollup-linux-x64-gnu@4.42.0": + resolution: + { integrity: sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw== } cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.42.0': - resolution: {integrity: sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==} + "@rollup/rollup-linux-x64-musl@4.42.0": + resolution: + { integrity: sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w== } cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.42.0': - resolution: {integrity: sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==} + "@rollup/rollup-win32-arm64-msvc@4.42.0": + resolution: + { integrity: sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A== } cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.42.0': - resolution: {integrity: sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==} + "@rollup/rollup-win32-ia32-msvc@4.42.0": + resolution: + { integrity: sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g== } cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.42.0': - resolution: {integrity: sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==} + "@rollup/rollup-win32-x64-msvc@4.42.0": + resolution: + { integrity: sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA== } cpu: [x64] os: [win32] - '@tailwindcss/node@4.1.8': - resolution: {integrity: sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==} + "@tailwindcss/node@4.1.8": + resolution: + { integrity: sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q== } - '@tailwindcss/oxide-android-arm64@4.1.8': - resolution: {integrity: sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-android-arm64@4.1.8": + resolution: + { integrity: sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg== } + engines: { node: ">= 10" } cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.8': - resolution: {integrity: sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-darwin-arm64@4.1.8": + resolution: + { integrity: sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A== } + engines: { node: ">= 10" } cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.8': - resolution: {integrity: sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-darwin-x64@4.1.8": + resolution: + { integrity: sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw== } + engines: { node: ">= 10" } cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.8': - resolution: {integrity: sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-freebsd-x64@4.1.8": + resolution: + { integrity: sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg== } + engines: { node: ">= 10" } cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8': - resolution: {integrity: sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8": + resolution: + { integrity: sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ== } + engines: { node: ">= 10" } cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.8': - resolution: {integrity: sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-arm64-gnu@4.1.8": + resolution: + { integrity: sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q== } + engines: { node: ">= 10" } cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.8': - resolution: {integrity: sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-arm64-musl@4.1.8": + resolution: + { integrity: sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ== } + engines: { node: ">= 10" } cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.8': - resolution: {integrity: sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-x64-gnu@4.1.8": + resolution: + { integrity: sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g== } + engines: { node: ">= 10" } cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.8': - resolution: {integrity: sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-linux-x64-musl@4.1.8": + resolution: + { integrity: sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg== } + engines: { node: ">= 10" } cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.8': - resolution: {integrity: sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==} - engines: {node: '>=14.0.0'} + "@tailwindcss/oxide-wasm32-wasi@4.1.8": + resolution: + { integrity: sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg== } + engines: { node: ">=14.0.0" } cpu: [wasm32] bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' + - "@napi-rs/wasm-runtime" + - "@emnapi/core" + - "@emnapi/runtime" + - "@tybys/wasm-util" + - "@emnapi/wasi-threads" - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.8': - resolution: {integrity: sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-win32-arm64-msvc@4.1.8": + resolution: + { integrity: sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA== } + engines: { node: ">= 10" } cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.8': - resolution: {integrity: sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==} - engines: {node: '>= 10'} + "@tailwindcss/oxide-win32-x64-msvc@4.1.8": + resolution: + { integrity: sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ== } + engines: { node: ">= 10" } cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.8': - resolution: {integrity: sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==} - engines: {node: '>= 10'} + "@tailwindcss/oxide@4.1.8": + resolution: + { integrity: sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A== } + engines: { node: ">= 10" } - '@tailwindcss/postcss@4.1.8': - resolution: {integrity: sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw==} + "@tailwindcss/postcss@4.1.8": + resolution: + { integrity: sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw== } - '@tailwindcss/vite@4.1.8': - resolution: {integrity: sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A==} + "@tailwindcss/vite@4.1.8": + resolution: + { integrity: sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A== } peerDependencies: vite: ^5.2.0 || ^6 - '@tanstack/history@1.120.17': - resolution: {integrity: sha512-k07LFI4Qo074IIaWzT/XjD0KlkGx2w1V3fnNtclKx0oAl8z4O9kCh6za+FPEIRe98xLgNFEiddDbJeAYGSlPtw==} - engines: {node: '>=12'} + "@tanstack/history@1.120.17": + resolution: + { integrity: sha512-k07LFI4Qo074IIaWzT/XjD0KlkGx2w1V3fnNtclKx0oAl8z4O9kCh6za+FPEIRe98xLgNFEiddDbJeAYGSlPtw== } + engines: { node: ">=12" } - '@tanstack/query-core@5.80.6': - resolution: {integrity: sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ==} + "@tanstack/query-core@5.80.6": + resolution: + { integrity: sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ== } - '@tanstack/react-query@5.80.6': - resolution: {integrity: sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw==} + "@tanstack/react-query@5.80.6": + resolution: + { integrity: sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw== } peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.120.18': - resolution: {integrity: sha512-iYz1jp2AJG0a+FiyxKqpg44RM9KiVzChD57RsZTQylbwjPBF+dpP1GHhSwKPS99JgA10VhsB2moo3wc7wAsmvg==} - engines: {node: '>=12'} + "@tanstack/react-router-devtools@1.120.18": + resolution: + { integrity: sha512-iYz1jp2AJG0a+FiyxKqpg44RM9KiVzChD57RsZTQylbwjPBF+dpP1GHhSwKPS99JgA10VhsB2moo3wc7wAsmvg== } + engines: { node: ">=12" } peerDependencies: - '@tanstack/react-router': ^1.120.18 - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' + "@tanstack/react-router": ^1.120.18 + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" - '@tanstack/react-router@1.120.18': - resolution: {integrity: sha512-VXEP4L7We0XGKv1zRmttTbjPZfDXma6y9IV5Jo2yTrz4V4mhL4cPb7Do+shgW08dW16nJ0Yiy7VlxFd5P8LLeA==} - engines: {node: '>=12'} + "@tanstack/react-router@1.120.18": + resolution: + { integrity: sha512-VXEP4L7We0XGKv1zRmttTbjPZfDXma6y9IV5Jo2yTrz4V4mhL4cPb7Do+shgW08dW16nJ0Yiy7VlxFd5P8LLeA== } + engines: { node: ">=12" } peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" - '@tanstack/react-store@0.7.1': - resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==} + "@tanstack/react-store@0.7.1": + resolution: + { integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA== } peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-table@8.21.3': - resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} - engines: {node: '>=12'} + "@tanstack/react-table@8.21.3": + resolution: + { integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== } + engines: { node: ">=12" } peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' + react: ">=16.8" + react-dom: ">=16.8" - '@tanstack/router-core@1.120.17': - resolution: {integrity: sha512-AqNr/rJKua/q/BFcUJTFH1YNUWcLF7dzsRVlN+EdRyvGT4Vpz0wip2wroO5N/9A3463NIuLSacjiZI9UKO/4XQ==} - engines: {node: '>=12'} + "@tanstack/router-core@1.120.17": + resolution: + { integrity: sha512-AqNr/rJKua/q/BFcUJTFH1YNUWcLF7dzsRVlN+EdRyvGT4Vpz0wip2wroO5N/9A3463NIuLSacjiZI9UKO/4XQ== } + engines: { node: ">=12" } - '@tanstack/router-devtools-core@1.120.17': - resolution: {integrity: sha512-Ci2OV/hecsKy/ZXqK3gLS/h1Qg7OEl2Gy9c76iSYWIzroQ9nhKgsQ+qj+LxucImztWujNINXf23wkq5fVlR0EQ==} - engines: {node: '>=12'} + "@tanstack/router-devtools-core@1.120.17": + resolution: + { integrity: sha512-Ci2OV/hecsKy/ZXqK3gLS/h1Qg7OEl2Gy9c76iSYWIzroQ9nhKgsQ+qj+LxucImztWujNINXf23wkq5fVlR0EQ== } + engines: { node: ">=12" } peerDependencies: - '@tanstack/router-core': ^1.120.17 + "@tanstack/router-core": ^1.120.17 csstype: ^3.0.10 - solid-js: '>=1.9.5' + solid-js: ">=1.9.5" tiny-invariant: ^1.3.3 peerDependenciesMeta: csstype: optional: true - '@tanstack/router-devtools@1.120.18': - resolution: {integrity: sha512-9P74erkZQprhMIJftzein35WS0Fi2J+jOvNaaylX59k+2wjahYdSImIkICAEGmj2K5C51QsPFUrikYVTT591LA==} - engines: {node: '>=12'} + "@tanstack/router-devtools@1.120.18": + resolution: + { integrity: sha512-9P74erkZQprhMIJftzein35WS0Fi2J+jOvNaaylX59k+2wjahYdSImIkICAEGmj2K5C51QsPFUrikYVTT591LA== } + engines: { node: ">=12" } peerDependencies: - '@tanstack/react-router': ^1.120.18 + "@tanstack/react-router": ^1.120.18 csstype: ^3.0.10 - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' + react: ">=18.0.0 || >=19.0.0" + react-dom: ">=18.0.0 || >=19.0.0" peerDependenciesMeta: csstype: optional: true - '@tanstack/store@0.7.1': - resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} + "@tanstack/store@0.7.1": + resolution: + { integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg== } - '@tanstack/table-core@8.21.3': - resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} - engines: {node: '>=12'} + "@tanstack/table-core@8.21.3": + resolution: + { integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== } + engines: { node: ">=12" } - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + "@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__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__template@7.4.4": + resolution: + { integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== } - '@types/babel__traverse@7.20.7': - resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + "@types/babel__traverse@7.20.7": + resolution: + { integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== } - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + "@types/estree@1.0.7": + resolution: + { integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== } - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + "@types/estree@1.0.8": + resolution: + { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + "@types/json-schema@7.0.15": + resolution: + { integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } - '@types/node@22.15.30': - resolution: {integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==} + "@types/node@22.15.30": + resolution: + { integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA== } - '@types/react-dom@19.1.6': - resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} + "@types/react-dom@19.1.6": + resolution: + { integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== } peerDependencies: - '@types/react': ^19.0.0 + "@types/react": ^19.0.0 - '@types/react@19.1.6': - resolution: {integrity: sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==} + "@types/react@19.1.6": + resolution: + { integrity: sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q== } - '@typescript-eslint/eslint-plugin@8.33.1': - resolution: {integrity: sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@types/uuid@10.0.0": + resolution: + { integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== } + + "@typescript-eslint/eslint-plugin@8.33.1": + resolution: + { integrity: sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - '@typescript-eslint/parser': ^8.33.1 + "@typescript-eslint/parser": ^8.33.1 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/parser@8.33.1': - resolution: {integrity: sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/parser@8.33.1": + resolution: + { integrity: sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/project-service@8.33.1': - resolution: {integrity: sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/project-service@8.33.1": + resolution: + { integrity: sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/scope-manager@8.33.1': - resolution: {integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/scope-manager@8.33.1": + resolution: + { integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@typescript-eslint/tsconfig-utils@8.33.1': - resolution: {integrity: sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/tsconfig-utils@8.33.1": + resolution: + { integrity: sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/type-utils@8.33.1': - resolution: {integrity: sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/type-utils@8.33.1": + resolution: + { integrity: sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/types@8.33.1': - resolution: {integrity: sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/types@8.33.1": + resolution: + { integrity: sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@typescript-eslint/typescript-estree@8.33.1': - resolution: {integrity: sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/typescript-estree@8.33.1": + resolution: + { integrity: sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/utils@8.33.1': - resolution: {integrity: sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/utils@8.33.1": + resolution: + { integrity: sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" - '@typescript-eslint/visitor-keys@8.33.1': - resolution: {integrity: sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + "@typescript-eslint/visitor-keys@8.33.1": + resolution: + { integrity: sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - '@vitejs/plugin-react@4.5.1': - resolution: {integrity: sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==} - engines: {node: ^14.18.0 || >=16.0.0} + "@vitejs/plugin-react@4.5.1": + resolution: + { integrity: sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A== } + engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + resolution: + { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} + resolution: + { integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== } + engines: { node: ">=0.4.0" } hasBin: true ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + resolution: + { integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== } ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } + engines: { node: ">=8" } argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + resolution: + { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + resolution: + { integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== } axios@1.9.0: - resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} + resolution: + { integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== } balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + resolution: + { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + resolution: + { integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== } brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + resolution: + { integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== } braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== } + engines: { node: ">=8" } browserslist@4.25.0: - resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + resolution: + { integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA== } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== } + engines: { node: ">= 0.4" } callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } + engines: { node: ">=6" } caniuse-lite@1.0.30001721: - resolution: {integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==} + resolution: + { integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ== } chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } + engines: { node: ">=10" } chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} + resolution: + { integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== } + engines: { node: ">=18" } clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== } + engines: { node: ">=6" } color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + resolution: + { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } + engines: { node: ">=7.0.0" } color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + resolution: + { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} + resolution: + { integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== } + engines: { node: ">= 0.8" } concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: + { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + resolution: + { integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== } cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + resolution: + { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } + engines: { node: ">= 8" } csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + resolution: + { integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== } debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} + resolution: + { integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== } + engines: { node: ">=6.0" } peerDependencies: - supports-color: '*' + supports-color: "*" peerDependenciesMeta: supports-color: optional: true deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + resolution: + { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} + resolution: + { integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== } + engines: { node: ">=0.4.0" } detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== } + engines: { node: ">=8" } dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== } + engines: { node: ">= 0.4" } electron-to-chromium@1.5.165: - resolution: {integrity: sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==} + resolution: + { integrity: sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw== } enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} - engines: {node: '>=10.13.0'} + resolution: + { integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== } + engines: { node: ">=10.13.0" } es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== } + engines: { node: ">= 0.4" } es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== } + engines: { node: ">= 0.4" } es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== } + engines: { node: ">= 0.4" } es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== } + engines: { node: ">= 0.4" } esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} + resolution: + { integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ== } + engines: { node: ">=18" } hasBin: true escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== } + engines: { node: ">=6" } escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } + engines: { node: ">=10" } + + eslint-config-prettier@10.1.5: + resolution: + { integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw== } + hasBin: true + peerDependencies: + eslint: ">=7.0.0" + + eslint-plugin-prettier@5.4.1: + resolution: + { integrity: sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg== } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== } + engines: { node: ">=10" } peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react-refresh@0.4.20: - resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==} + resolution: + { integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA== } peerDependencies: - eslint: '>=8.40' + eslint: ">=8.40" eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + resolution: + { integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } eslint@9.28.0: - resolution: {integrity: sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { integrity: sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } hasBin: true peerDependencies: - jiti: '*' + jiti: "*" peerDependenciesMeta: jiti: optional: true espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} + resolution: + { integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== } + engines: { node: ">=0.10" } esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + resolution: + { integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== } + engines: { node: ">=4.0" } estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + resolution: + { integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== } + engines: { node: ">=4.0" } esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== } + engines: { node: ">=0.10.0" } fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + resolution: + { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + + fast-diff@1.3.0: + resolution: + { integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== } fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + resolution: + { integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== } + engines: { node: ">=8.6.0" } fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + resolution: + { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + resolution: + { integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + resolution: + { integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== } fdir@6.4.5: - resolution: {integrity: sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==} + resolution: + { integrity: sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw== } peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1013,534 +1255,678 @@ packages: optional: true file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + resolution: + { integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== } + engines: { node: ">=16.0.0" } fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== } + engines: { node: ">=8" } find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } + engines: { node: ">=10" } flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + resolution: + { integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== } + engines: { node: ">=16" } flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + resolution: + { integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== } follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} + resolution: + { integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== } + engines: { node: ">=4.0" } peerDependencies: - debug: '*' + debug: "*" peerDependenciesMeta: debug: optional: true form-data@4.0.3: - resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} - engines: {node: '>= 6'} + resolution: + { integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== } + engines: { node: ">= 6" } fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + resolution: + { integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + resolution: + { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + resolution: + { integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== } + engines: { node: ">=6.9.0" } get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== } + engines: { node: ">= 0.4" } get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== } + engines: { node: ">= 0.4" } glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + resolution: + { integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== } + engines: { node: ">= 6" } glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + resolution: + { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } + engines: { node: ">=10.13.0" } globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} + resolution: + { integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== } + engines: { node: ">=4" } globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + resolution: + { integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== } + engines: { node: ">=18" } globals@16.2.0: - resolution: {integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==} - engines: {node: '>=18'} + resolution: + { integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg== } + engines: { node: ">=18" } goober@2.1.16: - resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} + resolution: + { integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g== } peerDependencies: csstype: ^3.0.10 gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== } + engines: { node: ">= 0.4" } graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + resolution: + { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== } graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + resolution: + { integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== } has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } + engines: { node: ">=8" } has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== } + engines: { node: ">= 0.4" } has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== } + engines: { node: ">= 0.4" } hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } + engines: { node: ">= 0.4" } ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + resolution: + { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } + engines: { node: ">= 4" } ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} + resolution: + { integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== } + engines: { node: ">= 4" } import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } + engines: { node: ">=6" } imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + resolution: + { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } + engines: { node: ">=0.8.19" } is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } + engines: { node: ">=0.10.0" } is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } + engines: { node: ">=0.10.0" } is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + resolution: + { integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== } + engines: { node: ">=0.12.0" } isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + resolution: + { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + resolution: + { integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== } hasBin: true js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + resolution: + { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + resolution: + { integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== } hasBin: true jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== } + engines: { node: ">=6" } hasBin: true json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + resolution: + { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + resolution: + { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + resolution: + { integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== } + engines: { node: ">=6" } hasBin: true keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + resolution: + { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + resolution: + { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== } + engines: { node: ">= 0.8.0" } lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} - engines: {node: '>= 12.0.0'} + 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'} + 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'} + 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'} + 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'} + 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'} + 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'} + 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'} + 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'} + 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'} + 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'} + resolution: + { integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== } + engines: { node: ">= 12.0.0" } locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } + engines: { node: ">=10" } lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + resolution: + { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + resolution: + { integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== } magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + resolution: + { integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== } math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} + resolution: + { integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== } + engines: { node: ">= 0.4" } merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + resolution: + { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } + engines: { node: ">= 8" } micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + resolution: + { integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== } + engines: { node: ">=8.6" } mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + resolution: + { integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== } + engines: { node: ">= 0.6" } mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + resolution: + { integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== } + engines: { node: ">= 0.6" } minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + resolution: + { integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== } minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + { integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== } + engines: { node: ">=16 || 14 >=14.17" } minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} + resolution: + { integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== } + engines: { node: ">=16 || 14 >=14.17" } minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} - engines: {node: '>= 18'} + resolution: + { integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== } + engines: { node: ">= 18" } mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== } + engines: { node: ">=10" } hasBin: true ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + 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} + resolution: + { integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + resolution: + { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + resolution: + { integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== } optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + resolution: + { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } + engines: { node: ">= 0.8.0" } p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } + engines: { node: ">=10" } p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } + engines: { node: ">=10" } parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } + engines: { node: ">=6" } path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== } + engines: { node: ">=8" } path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== } + engines: { node: ">=8" } picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + resolution: + { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + resolution: + { integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== } + engines: { node: ">=8.6" } picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} + resolution: + { integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== } + engines: { node: ">=12" } postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} - engines: {node: ^10 || ^12 || >=14} + resolution: + { integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w== } + engines: { node: ^10 || ^12 || >=14 } prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + resolution: + { integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== } + engines: { node: ">= 0.8.0" } + + prettier-linter-helpers@1.0.0: + resolution: + { integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== } + engines: { node: ">=6.0.0" } + + prettier@3.5.3: + resolution: + { integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== } + engines: { node: ">=14" } + hasBin: true proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + resolution: + { integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== } punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + resolution: + { integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== } + engines: { node: ">=6" } queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + resolution: + { integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== } react-dom@19.1.0: - resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + resolution: + { integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== } peerDependencies: react: ^19.1.0 react-hook-form@7.57.0: - resolution: {integrity: sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==} - engines: {node: '>=18.0.0'} + resolution: + { integrity: sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg== } + engines: { node: ">=18.0.0" } peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== } + engines: { node: ">=0.10.0" } react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== } + engines: { node: ">=0.10.0" } resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + resolution: + { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } + engines: { node: ">=4" } reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + resolution: + { integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } rollup@4.42.0: - resolution: {integrity: sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + resolution: + { integrity: sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw== } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + resolution: + { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + resolution: + { integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== } semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + resolution: + { integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== } hasBin: true semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== } + engines: { node: ">=10" } hasBin: true seroval-plugins@1.3.2: - resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ== } + engines: { node: ">=10" } peerDependencies: seroval: ^1.0 seroval@1.3.2: - resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ== } + engines: { node: ">=10" } shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } + engines: { node: ">=8" } shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } + engines: { node: ">=8" } solid-js@1.9.7: - resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==} + resolution: + { integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw== } sonner@2.0.5: - resolution: {integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==} + resolution: + { integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ== } peerDependencies: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== } + engines: { node: ">=0.10.0" } strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } + engines: { node: ">=8" } supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + resolution: + { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } + engines: { node: ">=8" } + + synckit@0.11.8: + resolution: + { integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== } + engines: { node: ^14.18.0 || >=16.0.0 } tailwindcss@4.1.8: - resolution: {integrity: sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==} + resolution: + { integrity: sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og== } tapable@2.2.2: - resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} - engines: {node: '>=6'} + 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'} + resolution: + { integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== } + engines: { node: ">=18" } tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + resolution: + { integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== } tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + resolution: + { integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== } tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} + resolution: + { integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== } + engines: { node: ">=12.0.0" } to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + resolution: + { integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== } + engines: { node: ">=8.0" } ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} + resolution: + { integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== } + engines: { node: ">=18.12" } peerDependencies: - typescript: '>=4.8.4' + typescript: ">=4.8.4" type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + resolution: + { integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== } + engines: { node: ">= 0.8.0" } typescript-eslint@8.33.1: - resolution: {integrity: sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + resolution: + { integrity: sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: ">=4.8.4 <5.9.0" typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} + resolution: + { integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== } + engines: { node: ">=14.17" } hasBin: true undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + resolution: + { integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== } update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + resolution: + { integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== } hasBin: true peerDependencies: - browserslist: '>= 4.21.0' + browserslist: ">= 4.21.0" uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + resolution: + { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } use-debounce@10.0.5: - resolution: {integrity: sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ==} - engines: {node: '>= 16.0.0'} + resolution: + { integrity: sha512-Q76E3lnIV+4YT9AHcrHEHYmAd9LKwUAbPXDm7FlqVGDHiSOhX3RDjT8dm0AxbJup6WgOb1YEcKyCr11kBJR5KQ== } + engines: { node: ">= 16.0.0" } peerDependencies: - react: '*' + react: "*" use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + resolution: + { integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== } peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@11.1.0: + resolution: + { integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== } + hasBin: true + vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + resolution: + { integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ== } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: - '@types/node': + "@types/node": optional: true jiti: optional: true @@ -1564,54 +1950,58 @@ packages: optional: true which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + resolution: + { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } + engines: { node: ">= 8" } hasBin: true word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + resolution: + { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } + engines: { node: ">=0.10.0" } yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + resolution: + { integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== } yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} + resolution: + { integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== } + engines: { node: ">=18" } yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + resolution: + { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } + engines: { node: ">=10" } snapshots: + "@alloc/quick-lru@5.2.0": {} - '@alloc/quick-lru@5.2.0': {} - - '@ampproject/remapping@2.3.0': + "@ampproject/remapping@2.3.0": dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + "@jridgewell/gen-mapping": 0.3.8 + "@jridgewell/trace-mapping": 0.3.25 - '@babel/code-frame@7.27.1': + "@babel/code-frame@7.27.1": dependencies: - '@babel/helper-validator-identifier': 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.27.5': {} + "@babel/compat-data@7.27.5": {} - '@babel/core@7.27.4': + "@babel/core@7.27.4": dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) - '@babel/helpers': 7.27.6 - '@babel/parser': 7.27.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.27.4 - '@babel/types': 7.27.6 + "@ampproject/remapping": 2.3.0 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.27.5 + "@babel/helper-compilation-targets": 7.27.2 + "@babel/helper-module-transforms": 7.27.3(@babel/core@7.27.4) + "@babel/helpers": 7.27.6 + "@babel/parser": 7.27.5 + "@babel/template": 7.27.2 + "@babel/traverse": 7.27.4 + "@babel/types": 7.27.6 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -1620,185 +2010,185 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.27.5': + "@babel/generator@7.27.5": dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + "@babel/parser": 7.27.5 + "@babel/types": 7.27.6 + "@jridgewell/gen-mapping": 0.3.8 + "@jridgewell/trace-mapping": 0.3.25 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + "@babel/helper-compilation-targets@7.27.2": dependencies: - '@babel/compat-data': 7.27.5 - '@babel/helper-validator-option': 7.27.1 + "@babel/compat-data": 7.27.5 + "@babel/helper-validator-option": 7.27.1 browserslist: 4.25.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.27.1': + "@babel/helper-module-imports@7.27.1": dependencies: - '@babel/traverse': 7.27.4 - '@babel/types': 7.27.6 + "@babel/traverse": 7.27.4 + "@babel/types": 7.27.6 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': + "@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)": dependencies: - '@babel/core': 7.27.4 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.4 + "@babel/core": 7.27.4 + "@babel/helper-module-imports": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + "@babel/traverse": 7.27.4 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.27.1': {} + "@babel/helper-plugin-utils@7.27.1": {} - '@babel/helper-string-parser@7.27.1': {} + "@babel/helper-string-parser@7.27.1": {} - '@babel/helper-validator-identifier@7.27.1': {} + "@babel/helper-validator-identifier@7.27.1": {} - '@babel/helper-validator-option@7.27.1': {} + "@babel/helper-validator-option@7.27.1": {} - '@babel/helpers@7.27.6': + "@babel/helpers@7.27.6": dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.27.6 + "@babel/template": 7.27.2 + "@babel/types": 7.27.6 - '@babel/parser@7.27.5': + "@babel/parser@7.27.5": dependencies: - '@babel/types': 7.27.6 + "@babel/types": 7.27.6 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)': + "@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)": dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 + "@babel/core": 7.27.4 + "@babel/helper-plugin-utils": 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.4)': + "@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.4)": dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 + "@babel/core": 7.27.4 + "@babel/helper-plugin-utils": 7.27.1 - '@babel/template@7.27.2': + "@babel/template@7.27.2": dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 + "@babel/code-frame": 7.27.1 + "@babel/parser": 7.27.5 + "@babel/types": 7.27.6 - '@babel/traverse@7.27.4': + "@babel/traverse@7.27.4": dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.5 - '@babel/template': 7.27.2 - '@babel/types': 7.27.6 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.27.5 + "@babel/parser": 7.27.5 + "@babel/template": 7.27.2 + "@babel/types": 7.27.6 debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.27.6': + "@babel/types@7.27.6": dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 - '@esbuild/aix-ppc64@0.25.5': + "@esbuild/aix-ppc64@0.25.5": optional: true - '@esbuild/android-arm64@0.25.5': + "@esbuild/android-arm64@0.25.5": optional: true - '@esbuild/android-arm@0.25.5': + "@esbuild/android-arm@0.25.5": optional: true - '@esbuild/android-x64@0.25.5': + "@esbuild/android-x64@0.25.5": optional: true - '@esbuild/darwin-arm64@0.25.5': + "@esbuild/darwin-arm64@0.25.5": optional: true - '@esbuild/darwin-x64@0.25.5': + "@esbuild/darwin-x64@0.25.5": optional: true - '@esbuild/freebsd-arm64@0.25.5': + "@esbuild/freebsd-arm64@0.25.5": optional: true - '@esbuild/freebsd-x64@0.25.5': + "@esbuild/freebsd-x64@0.25.5": optional: true - '@esbuild/linux-arm64@0.25.5': + "@esbuild/linux-arm64@0.25.5": optional: true - '@esbuild/linux-arm@0.25.5': + "@esbuild/linux-arm@0.25.5": optional: true - '@esbuild/linux-ia32@0.25.5': + "@esbuild/linux-ia32@0.25.5": optional: true - '@esbuild/linux-loong64@0.25.5': + "@esbuild/linux-loong64@0.25.5": optional: true - '@esbuild/linux-mips64el@0.25.5': + "@esbuild/linux-mips64el@0.25.5": optional: true - '@esbuild/linux-ppc64@0.25.5': + "@esbuild/linux-ppc64@0.25.5": optional: true - '@esbuild/linux-riscv64@0.25.5': + "@esbuild/linux-riscv64@0.25.5": optional: true - '@esbuild/linux-s390x@0.25.5': + "@esbuild/linux-s390x@0.25.5": optional: true - '@esbuild/linux-x64@0.25.5': + "@esbuild/linux-x64@0.25.5": optional: true - '@esbuild/netbsd-arm64@0.25.5': + "@esbuild/netbsd-arm64@0.25.5": optional: true - '@esbuild/netbsd-x64@0.25.5': + "@esbuild/netbsd-x64@0.25.5": optional: true - '@esbuild/openbsd-arm64@0.25.5': + "@esbuild/openbsd-arm64@0.25.5": optional: true - '@esbuild/openbsd-x64@0.25.5': + "@esbuild/openbsd-x64@0.25.5": optional: true - '@esbuild/sunos-x64@0.25.5': + "@esbuild/sunos-x64@0.25.5": optional: true - '@esbuild/win32-arm64@0.25.5': + "@esbuild/win32-arm64@0.25.5": optional: true - '@esbuild/win32-ia32@0.25.5': + "@esbuild/win32-ia32@0.25.5": optional: true - '@esbuild/win32-x64@0.25.5': + "@esbuild/win32-x64@0.25.5": optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.28.0(jiti@2.4.2))': + "@eslint-community/eslint-utils@4.7.0(eslint@9.28.0(jiti@2.4.2))": dependencies: eslint: 9.28.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + "@eslint-community/regexpp@4.12.1": {} - '@eslint/config-array@0.20.0': + "@eslint/config-array@0.20.0": dependencies: - '@eslint/object-schema': 2.1.6 + "@eslint/object-schema": 2.1.6 debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.2.2': {} + "@eslint/config-helpers@0.2.2": {} - '@eslint/core@0.14.0': + "@eslint/core@0.14.0": dependencies: - '@types/json-schema': 7.0.15 + "@types/json-schema": 7.0.15 - '@eslint/eslintrc@3.3.1': + "@eslint/eslintrc@3.3.1": dependencies: ajv: 6.12.6 debug: 4.4.1 @@ -1812,126 +2202,128 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.28.0': {} + "@eslint/js@9.28.0": {} - '@eslint/object-schema@2.1.6': {} + "@eslint/object-schema@2.1.6": {} - '@eslint/plugin-kit@0.3.1': + "@eslint/plugin-kit@0.3.1": dependencies: - '@eslint/core': 0.14.0 + "@eslint/core": 0.14.0 levn: 0.4.1 - '@humanfs/core@0.19.1': {} + "@humanfs/core@0.19.1": {} - '@humanfs/node@0.16.6': + "@humanfs/node@0.16.6": dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.3.1 - '@humanwhocodes/module-importer@1.0.1': {} + "@humanwhocodes/module-importer@1.0.1": {} - '@humanwhocodes/retry@0.3.1': {} + "@humanwhocodes/retry@0.3.1": {} - '@humanwhocodes/retry@0.4.3': {} + "@humanwhocodes/retry@0.4.3": {} - '@isaacs/fs-minipass@4.0.1': + "@isaacs/fs-minipass@4.0.1": dependencies: minipass: 7.1.2 - '@jridgewell/gen-mapping@0.3.8': + "@jridgewell/gen-mapping@0.3.8": dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + "@jridgewell/set-array": 1.2.1 + "@jridgewell/sourcemap-codec": 1.5.0 + "@jridgewell/trace-mapping": 0.3.25 - '@jridgewell/resolve-uri@3.1.2': {} + "@jridgewell/resolve-uri@3.1.2": {} - '@jridgewell/set-array@1.2.1': {} + "@jridgewell/set-array@1.2.1": {} - '@jridgewell/sourcemap-codec@1.5.0': {} + "@jridgewell/sourcemap-codec@1.5.0": {} - '@jridgewell/trace-mapping@0.3.25': + "@jridgewell/trace-mapping@0.3.25": dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.0 - '@nodelib/fs.scandir@2.1.5': + "@nodelib/fs.scandir@2.1.5": dependencies: - '@nodelib/fs.stat': 2.0.5 + "@nodelib/fs.stat": 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@2.0.5': {} + "@nodelib/fs.stat@2.0.5": {} - '@nodelib/fs.walk@1.2.8': + "@nodelib/fs.walk@1.2.8": dependencies: - '@nodelib/fs.scandir': 2.1.5 + "@nodelib/fs.scandir": 2.1.5 fastq: 1.19.1 - '@rolldown/pluginutils@1.0.0-beta.9': {} + "@pkgr/core@0.2.7": {} - '@rollup/rollup-android-arm-eabi@4.42.0': + "@rolldown/pluginutils@1.0.0-beta.9": {} + + "@rollup/rollup-android-arm-eabi@4.42.0": optional: true - '@rollup/rollup-android-arm64@4.42.0': + "@rollup/rollup-android-arm64@4.42.0": optional: true - '@rollup/rollup-darwin-arm64@4.42.0': + "@rollup/rollup-darwin-arm64@4.42.0": optional: true - '@rollup/rollup-darwin-x64@4.42.0': + "@rollup/rollup-darwin-x64@4.42.0": optional: true - '@rollup/rollup-freebsd-arm64@4.42.0': + "@rollup/rollup-freebsd-arm64@4.42.0": optional: true - '@rollup/rollup-freebsd-x64@4.42.0': + "@rollup/rollup-freebsd-x64@4.42.0": optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.42.0': + "@rollup/rollup-linux-arm-gnueabihf@4.42.0": optional: true - '@rollup/rollup-linux-arm-musleabihf@4.42.0': + "@rollup/rollup-linux-arm-musleabihf@4.42.0": optional: true - '@rollup/rollup-linux-arm64-gnu@4.42.0': + "@rollup/rollup-linux-arm64-gnu@4.42.0": optional: true - '@rollup/rollup-linux-arm64-musl@4.42.0': + "@rollup/rollup-linux-arm64-musl@4.42.0": optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.42.0': + "@rollup/rollup-linux-loongarch64-gnu@4.42.0": optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.42.0': + "@rollup/rollup-linux-powerpc64le-gnu@4.42.0": optional: true - '@rollup/rollup-linux-riscv64-gnu@4.42.0': + "@rollup/rollup-linux-riscv64-gnu@4.42.0": optional: true - '@rollup/rollup-linux-riscv64-musl@4.42.0': + "@rollup/rollup-linux-riscv64-musl@4.42.0": optional: true - '@rollup/rollup-linux-s390x-gnu@4.42.0': + "@rollup/rollup-linux-s390x-gnu@4.42.0": optional: true - '@rollup/rollup-linux-x64-gnu@4.42.0': + "@rollup/rollup-linux-x64-gnu@4.42.0": optional: true - '@rollup/rollup-linux-x64-musl@4.42.0': + "@rollup/rollup-linux-x64-musl@4.42.0": optional: true - '@rollup/rollup-win32-arm64-msvc@4.42.0': + "@rollup/rollup-win32-arm64-msvc@4.42.0": optional: true - '@rollup/rollup-win32-ia32-msvc@4.42.0': + "@rollup/rollup-win32-ia32-msvc@4.42.0": optional: true - '@rollup/rollup-win32-x64-msvc@4.42.0': + "@rollup/rollup-win32-x64-msvc@4.42.0": optional: true - '@tailwindcss/node@4.1.8': + "@tailwindcss/node@4.1.8": dependencies: - '@ampproject/remapping': 2.3.0 + "@ampproject/remapping": 2.3.0 enhanced-resolve: 5.18.1 jiti: 2.4.2 lightningcss: 1.30.1 @@ -1939,129 +2331,129 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.1.8 - '@tailwindcss/oxide-android-arm64@4.1.8': + "@tailwindcss/oxide-android-arm64@4.1.8": optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.8': + "@tailwindcss/oxide-darwin-arm64@4.1.8": optional: true - '@tailwindcss/oxide-darwin-x64@4.1.8': + "@tailwindcss/oxide-darwin-x64@4.1.8": optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.8': + "@tailwindcss/oxide-freebsd-x64@4.1.8": optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8': + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8": optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.8': + "@tailwindcss/oxide-linux-arm64-gnu@4.1.8": optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.8': + "@tailwindcss/oxide-linux-arm64-musl@4.1.8": optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.8': + "@tailwindcss/oxide-linux-x64-gnu@4.1.8": optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.8': + "@tailwindcss/oxide-linux-x64-musl@4.1.8": optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.8': + "@tailwindcss/oxide-wasm32-wasi@4.1.8": optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.8': + "@tailwindcss/oxide-win32-arm64-msvc@4.1.8": optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.8': + "@tailwindcss/oxide-win32-x64-msvc@4.1.8": optional: true - '@tailwindcss/oxide@4.1.8': + "@tailwindcss/oxide@4.1.8": dependencies: detect-libc: 2.0.4 tar: 7.4.3 optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.8 - '@tailwindcss/oxide-darwin-arm64': 4.1.8 - '@tailwindcss/oxide-darwin-x64': 4.1.8 - '@tailwindcss/oxide-freebsd-x64': 4.1.8 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.8 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.8 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.8 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.8 - '@tailwindcss/oxide-linux-x64-musl': 4.1.8 - '@tailwindcss/oxide-wasm32-wasi': 4.1.8 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.8 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.8 + "@tailwindcss/oxide-android-arm64": 4.1.8 + "@tailwindcss/oxide-darwin-arm64": 4.1.8 + "@tailwindcss/oxide-darwin-x64": 4.1.8 + "@tailwindcss/oxide-freebsd-x64": 4.1.8 + "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.8 + "@tailwindcss/oxide-linux-arm64-gnu": 4.1.8 + "@tailwindcss/oxide-linux-arm64-musl": 4.1.8 + "@tailwindcss/oxide-linux-x64-gnu": 4.1.8 + "@tailwindcss/oxide-linux-x64-musl": 4.1.8 + "@tailwindcss/oxide-wasm32-wasi": 4.1.8 + "@tailwindcss/oxide-win32-arm64-msvc": 4.1.8 + "@tailwindcss/oxide-win32-x64-msvc": 4.1.8 - '@tailwindcss/postcss@4.1.8': + "@tailwindcss/postcss@4.1.8": dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.8 - '@tailwindcss/oxide': 4.1.8 + "@alloc/quick-lru": 5.2.0 + "@tailwindcss/node": 4.1.8 + "@tailwindcss/oxide": 4.1.8 postcss: 8.5.4 tailwindcss: 4.1.8 - '@tailwindcss/vite@4.1.8(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1))': + "@tailwindcss/vite@4.1.8(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1))": dependencies: - '@tailwindcss/node': 4.1.8 - '@tailwindcss/oxide': 4.1.8 + "@tailwindcss/node": 4.1.8 + "@tailwindcss/oxide": 4.1.8 tailwindcss: 4.1.8 vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1) - '@tanstack/history@1.120.17': {} + "@tanstack/history@1.120.17": {} - '@tanstack/query-core@5.80.6': {} + "@tanstack/query-core@5.80.6": {} - '@tanstack/react-query@5.80.6(react@19.1.0)': + "@tanstack/react-query@5.80.6(react@19.1.0)": dependencies: - '@tanstack/query-core': 5.80.6 + "@tanstack/query-core": 5.80.6 react: 19.1.0 - '@tanstack/react-router-devtools@1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)': + "@tanstack/react-router-devtools@1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)": dependencies: - '@tanstack/react-router': 1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-devtools-core': 1.120.17(@tanstack/router-core@1.120.17)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3) + "@tanstack/react-router": 1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tanstack/router-devtools-core": 1.120.17(@tanstack/router-core@1.120.17)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) solid-js: 1.9.7 transitivePeerDependencies: - - '@tanstack/router-core' + - "@tanstack/router-core" - csstype - tiny-invariant - '@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + "@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": dependencies: - '@tanstack/history': 1.120.17 - '@tanstack/react-store': 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-core': 1.120.17 + "@tanstack/history": 1.120.17 + "@tanstack/react-store": 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tanstack/router-core": 1.120.17 jsesc: 3.1.0 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + "@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": dependencies: - '@tanstack/store': 0.7.1 + "@tanstack/store": 0.7.1 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.5.0(react@19.1.0) - '@tanstack/react-table@8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + "@tanstack/react-table@8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": dependencies: - '@tanstack/table-core': 8.21.3 + "@tanstack/table-core": 8.21.3 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - '@tanstack/router-core@1.120.17': + "@tanstack/router-core@1.120.17": dependencies: - '@tanstack/history': 1.120.17 - '@tanstack/store': 0.7.1 + "@tanstack/history": 1.120.17 + "@tanstack/store": 0.7.1 tiny-invariant: 1.3.3 - '@tanstack/router-devtools-core@1.120.17(@tanstack/router-core@1.120.17)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3)': + "@tanstack/router-devtools-core@1.120.17(@tanstack/router-core@1.120.17)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3)": dependencies: - '@tanstack/router-core': 1.120.17 + "@tanstack/router-core": 1.120.17 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) solid-js: 1.9.7 @@ -2069,10 +2461,10 @@ snapshots: optionalDependencies: csstype: 3.1.3 - '@tanstack/router-devtools@1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)': + "@tanstack/router-devtools@1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)": dependencies: - '@tanstack/react-router': 1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/react-router-devtools': 1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3) + "@tanstack/react-router": 1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tanstack/react-router-devtools": 1.120.18(@tanstack/react-router@1.120.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.17)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3) clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) react: 19.1.0 @@ -2080,60 +2472,62 @@ snapshots: optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: - - '@tanstack/router-core' + - "@tanstack/router-core" - tiny-invariant - '@tanstack/store@0.7.1': {} + "@tanstack/store@0.7.1": {} - '@tanstack/table-core@8.21.3': {} + "@tanstack/table-core@8.21.3": {} - '@types/babel__core@7.20.5': + "@types/babel__core@7.20.5": dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.7 + "@babel/parser": 7.27.5 + "@babel/types": 7.27.6 + "@types/babel__generator": 7.27.0 + "@types/babel__template": 7.4.4 + "@types/babel__traverse": 7.20.7 - '@types/babel__generator@7.27.0': + "@types/babel__generator@7.27.0": dependencies: - '@babel/types': 7.27.6 + "@babel/types": 7.27.6 - '@types/babel__template@7.4.4': + "@types/babel__template@7.4.4": dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 + "@babel/parser": 7.27.5 + "@babel/types": 7.27.6 - '@types/babel__traverse@7.20.7': + "@types/babel__traverse@7.20.7": dependencies: - '@babel/types': 7.27.6 + "@babel/types": 7.27.6 - '@types/estree@1.0.7': {} + "@types/estree@1.0.7": {} - '@types/estree@1.0.8': {} + "@types/estree@1.0.8": {} - '@types/json-schema@7.0.15': {} + "@types/json-schema@7.0.15": {} - '@types/node@22.15.30': + "@types/node@22.15.30": dependencies: undici-types: 6.21.0 - '@types/react-dom@19.1.6(@types/react@19.1.6)': + "@types/react-dom@19.1.6(@types/react@19.1.6)": dependencies: - '@types/react': 19.1.6 + "@types/react": 19.1.6 - '@types/react@19.1.6': + "@types/react@19.1.6": dependencies: csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + "@types/uuid@10.0.0": {} + + "@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)": dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.33.1 + "@eslint-community/regexpp": 4.12.1 + "@typescript-eslint/parser": 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/scope-manager": 8.33.1 + "@typescript-eslint/type-utils": 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/utils": 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.33.1 eslint: 9.28.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 @@ -2143,40 +2537,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + "@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)": dependencies: - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.33.1 + "@typescript-eslint/scope-manager": 8.33.1 + "@typescript-eslint/types": 8.33.1 + "@typescript-eslint/typescript-estree": 8.33.1(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.33.1 debug: 4.4.1 eslint: 9.28.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.33.1(typescript@5.8.3)': + "@typescript-eslint/project-service@8.33.1(typescript@5.8.3)": dependencies: - '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 + "@typescript-eslint/tsconfig-utils": 8.33.1(typescript@5.8.3) + "@typescript-eslint/types": 8.33.1 debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.33.1': + "@typescript-eslint/scope-manager@8.33.1": dependencies: - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/visitor-keys': 8.33.1 + "@typescript-eslint/types": 8.33.1 + "@typescript-eslint/visitor-keys": 8.33.1 - '@typescript-eslint/tsconfig-utils@8.33.1(typescript@5.8.3)': + "@typescript-eslint/tsconfig-utils@8.33.1(typescript@5.8.3)": dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + "@typescript-eslint/type-utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)": dependencies: - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/typescript-estree": 8.33.1(typescript@5.8.3) + "@typescript-eslint/utils": 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 eslint: 9.28.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) @@ -2184,14 +2578,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.33.1': {} + "@typescript-eslint/types@8.33.1": {} - '@typescript-eslint/typescript-estree@8.33.1(typescript@5.8.3)': + "@typescript-eslint/typescript-estree@8.33.1(typescript@5.8.3)": dependencies: - '@typescript-eslint/project-service': 8.33.1(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/visitor-keys': 8.33.1 + "@typescript-eslint/project-service": 8.33.1(typescript@5.8.3) + "@typescript-eslint/tsconfig-utils": 8.33.1(typescript@5.8.3) + "@typescript-eslint/types": 8.33.1 + "@typescript-eslint/visitor-keys": 8.33.1 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -2202,29 +2596,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + "@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)": dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) + "@eslint-community/eslint-utils": 4.7.0(eslint@9.28.0(jiti@2.4.2)) + "@typescript-eslint/scope-manager": 8.33.1 + "@typescript-eslint/types": 8.33.1 + "@typescript-eslint/typescript-estree": 8.33.1(typescript@5.8.3) eslint: 9.28.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.33.1': + "@typescript-eslint/visitor-keys@8.33.1": dependencies: - '@typescript-eslint/types': 8.33.1 + "@typescript-eslint/types": 8.33.1 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react@4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1))': + "@vitejs/plugin-react@4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1))": dependencies: - '@babel/core': 7.27.4 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.4) - '@rolldown/pluginutils': 1.0.0-beta.9 - '@types/babel__core': 7.20.5 + "@babel/core": 7.27.4 + "@babel/plugin-transform-react-jsx-self": 7.27.1(@babel/core@7.27.4) + "@babel/plugin-transform-react-jsx-source": 7.27.1(@babel/core@7.27.4) + "@rolldown/pluginutils": 1.0.0-beta.9 + "@types/babel__core": 7.20.5 react-refresh: 0.17.0 vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: @@ -2361,36 +2755,49 @@ snapshots: esbuild@0.25.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 + "@esbuild/aix-ppc64": 0.25.5 + "@esbuild/android-arm": 0.25.5 + "@esbuild/android-arm64": 0.25.5 + "@esbuild/android-x64": 0.25.5 + "@esbuild/darwin-arm64": 0.25.5 + "@esbuild/darwin-x64": 0.25.5 + "@esbuild/freebsd-arm64": 0.25.5 + "@esbuild/freebsd-x64": 0.25.5 + "@esbuild/linux-arm": 0.25.5 + "@esbuild/linux-arm64": 0.25.5 + "@esbuild/linux-ia32": 0.25.5 + "@esbuild/linux-loong64": 0.25.5 + "@esbuild/linux-mips64el": 0.25.5 + "@esbuild/linux-ppc64": 0.25.5 + "@esbuild/linux-riscv64": 0.25.5 + "@esbuild/linux-s390x": 0.25.5 + "@esbuild/linux-x64": 0.25.5 + "@esbuild/netbsd-arm64": 0.25.5 + "@esbuild/netbsd-x64": 0.25.5 + "@esbuild/openbsd-arm64": 0.25.5 + "@esbuild/openbsd-x64": 0.25.5 + "@esbuild/sunos-x64": 0.25.5 + "@esbuild/win32-arm64": 0.25.5 + "@esbuild/win32-ia32": 0.25.5 + "@esbuild/win32-x64": 0.25.5 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} + eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)): + dependencies: + eslint: 9.28.0(jiti@2.4.2) + + eslint-plugin-prettier@5.4.1(eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3): + dependencies: + eslint: 9.28.0(jiti@2.4.2) + prettier: 3.5.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.8 + optionalDependencies: + eslint-config-prettier: 10.1.5(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-react-hooks@5.2.0(eslint@9.28.0(jiti@2.4.2)): dependencies: eslint: 9.28.0(jiti@2.4.2) @@ -2410,19 +2817,19 @@ snapshots: eslint@9.28.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.20.0 - '@eslint/config-helpers': 0.2.2 - '@eslint/core': 0.14.0 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.28.0 - '@eslint/plugin-kit': 0.3.1 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 + "@eslint-community/eslint-utils": 4.7.0(eslint@9.28.0(jiti@2.4.2)) + "@eslint-community/regexpp": 4.12.1 + "@eslint/config-array": 0.20.0 + "@eslint/config-helpers": 0.2.2 + "@eslint/core": 0.14.0 + "@eslint/eslintrc": 3.3.1 + "@eslint/js": 9.28.0 + "@eslint/plugin-kit": 0.3.1 + "@humanfs/node": 0.16.6 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -2470,10 +2877,12 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.3: dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 @@ -2686,7 +3095,7 @@ snapshots: magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + "@jridgewell/sourcemap-codec": 1.5.0 math-intrinsics@1.1.0: {} @@ -2766,6 +3175,12 @@ snapshots: prelude-ls@1.2.1: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.5.3: {} + proxy-from-env@1.1.0: {} punycode@2.3.1: {} @@ -2791,28 +3206,28 @@ snapshots: rollup@4.42.0: dependencies: - '@types/estree': 1.0.7 + "@types/estree": 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.42.0 - '@rollup/rollup-android-arm64': 4.42.0 - '@rollup/rollup-darwin-arm64': 4.42.0 - '@rollup/rollup-darwin-x64': 4.42.0 - '@rollup/rollup-freebsd-arm64': 4.42.0 - '@rollup/rollup-freebsd-x64': 4.42.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.42.0 - '@rollup/rollup-linux-arm-musleabihf': 4.42.0 - '@rollup/rollup-linux-arm64-gnu': 4.42.0 - '@rollup/rollup-linux-arm64-musl': 4.42.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.42.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.42.0 - '@rollup/rollup-linux-riscv64-gnu': 4.42.0 - '@rollup/rollup-linux-riscv64-musl': 4.42.0 - '@rollup/rollup-linux-s390x-gnu': 4.42.0 - '@rollup/rollup-linux-x64-gnu': 4.42.0 - '@rollup/rollup-linux-x64-musl': 4.42.0 - '@rollup/rollup-win32-arm64-msvc': 4.42.0 - '@rollup/rollup-win32-ia32-msvc': 4.42.0 - '@rollup/rollup-win32-x64-msvc': 4.42.0 + "@rollup/rollup-android-arm-eabi": 4.42.0 + "@rollup/rollup-android-arm64": 4.42.0 + "@rollup/rollup-darwin-arm64": 4.42.0 + "@rollup/rollup-darwin-x64": 4.42.0 + "@rollup/rollup-freebsd-arm64": 4.42.0 + "@rollup/rollup-freebsd-x64": 4.42.0 + "@rollup/rollup-linux-arm-gnueabihf": 4.42.0 + "@rollup/rollup-linux-arm-musleabihf": 4.42.0 + "@rollup/rollup-linux-arm64-gnu": 4.42.0 + "@rollup/rollup-linux-arm64-musl": 4.42.0 + "@rollup/rollup-linux-loongarch64-gnu": 4.42.0 + "@rollup/rollup-linux-powerpc64le-gnu": 4.42.0 + "@rollup/rollup-linux-riscv64-gnu": 4.42.0 + "@rollup/rollup-linux-riscv64-musl": 4.42.0 + "@rollup/rollup-linux-s390x-gnu": 4.42.0 + "@rollup/rollup-linux-x64-gnu": 4.42.0 + "@rollup/rollup-linux-x64-musl": 4.42.0 + "@rollup/rollup-win32-arm64-msvc": 4.42.0 + "@rollup/rollup-win32-ia32-msvc": 4.42.0 + "@rollup/rollup-win32-x64-msvc": 4.42.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -2856,13 +3271,17 @@ snapshots: dependencies: has-flag: 4.0.0 + synckit@0.11.8: + dependencies: + "@pkgr/core": 0.2.7 + tailwindcss@4.1.8: {} tapable@2.2.2: {} tar@7.4.3: dependencies: - '@isaacs/fs-minipass': 4.0.1 + "@isaacs/fs-minipass": 4.0.1 chownr: 3.0.0 minipass: 7.1.2 minizlib: 3.0.2 @@ -2892,9 +3311,9 @@ snapshots: typescript-eslint@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/eslint-plugin": 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/parser": 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/utils": 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.28.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: @@ -2922,6 +3341,8 @@ snapshots: dependencies: react: 19.1.0 + uuid@11.1.0: {} + vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1): dependencies: esbuild: 0.25.5 @@ -2931,7 +3352,7 @@ snapshots: rollup: 4.42.0 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.15.30 + "@types/node": 22.15.30 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 diff --git a/spotizerr-ui/postcss.config.mjs b/spotizerr-ui/postcss.config.mjs index 5e6830c..104f87b 100644 --- a/spotizerr-ui/postcss.config.mjs +++ b/spotizerr-ui/postcss.config.mjs @@ -1,4 +1,4 @@ -import tailwindcss from '@tailwindcss/postcss'; +import tailwindcss from "@tailwindcss/postcss"; export default { plugins: [tailwindcss], diff --git a/spotizerr-ui/src/components/Queue.tsx b/spotizerr-ui/src/components/Queue.tsx index dd9bba9..aea11d0 100644 --- a/spotizerr-ui/src/components/Queue.tsx +++ b/spotizerr-ui/src/components/Queue.tsx @@ -1,73 +1,162 @@ -import { useQueue, type QueueItem } from '../contexts/queue-context'; +import { useQueue, type QueueItem } from "../contexts/queue-context"; export function Queue() { - const { items, isVisible, removeItem, clearQueue, toggleVisibility } = useQueue(); + const { items, isVisible, removeItem, retryItem, clearQueue, toggleVisibility, clearCompleted } = useQueue(); if (!isVisible) return null; - const renderStatus = (item: QueueItem) => { - switch (item.status) { - case 'downloading': - return ( -
-
-
- ); - case 'completed': - return Completed; - case 'error': - return {item.error || 'Failed'}; - default: - return {item.status}; + const handleClearQueue = () => { + if (confirm("Are you sure you want to cancel all downloads and clear the queue?")) { + clearQueue(); } }; - const renderItemDetails = (item: QueueItem) => { - if (item.status !== 'downloading' || !item.progress) return null; - return ( -
- {item.progress.toFixed(0)}% - {item.speed} - {item.size} - {item.eta} + const renderProgress = (item: QueueItem) => { + if (item.status === "downloading" || item.status === "processing") { + const isMultiTrack = item.totalTracks && item.totalTracks > 1; + const overallProgress = + isMultiTrack && item.totalTracks + ? ((item.currentTrackNumber || 0) / item.totalTracks) * 100 + : item.progress || 0; + + return ( +
+
+ {isMultiTrack && ( +
+
+
+ )}
- ) - } + ); + } + return null; + }; + + const renderStatusDetails = (item: QueueItem) => { + const statusClass = { + initializing: "text-gray-400", + pending: "text-gray-400", + downloading: "text-blue-400", + processing: "text-purple-400", + completed: "text-green-500 font-semibold", + error: "text-red-500 font-semibold", + skipped: "text-yellow-500", + cancelled: "text-gray-500", + queued: "text-gray-400", + }[item.status]; + + const isMultiTrack = item.totalTracks && item.totalTracks > 1; + + return ( +
+ {item.status.toUpperCase()} + {item.status === "downloading" && ( + <> + {item.progress?.toFixed(0)}% + {item.speed} + {item.eta} + + )} + {isMultiTrack && ( + + {item.currentTrackNumber}/{item.totalTracks} + + )} +
+ ); + }; + + const renderSummary = (item: QueueItem) => { + if (item.status !== "completed" || !item.summary) return null; + + return ( +
+ + Success: {item.summary.successful} + {" "} + |{" "} + + Skipped: {item.summary.skipped} + {" "} + |{" "} + + Failed: {item.summary.failed} + +
+ ); + }; return ( -
-
-

Download Queue

-
- - -
-
-
+ ); } diff --git a/spotizerr-ui/src/components/config/AccountsTab.tsx b/spotizerr-ui/src/components/config/AccountsTab.tsx index f1f2119..682a2a0 100644 --- a/spotizerr-ui/src/components/config/AccountsTab.tsx +++ b/spotizerr-ui/src/components/config/AccountsTab.tsx @@ -1,11 +1,11 @@ -import { useState } from 'react'; -import { useForm, type SubmitHandler } from 'react-hook-form'; -import apiClient from '../../lib/api-client'; -import { toast } from 'sonner'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useState } from "react"; +import { useForm, type SubmitHandler } from "react-hook-form"; +import apiClient from "../../lib/api-client"; +import { toast } from "sonner"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; // --- Type Definitions --- -type Service = 'spotify' | 'deezer'; +type Service = "spotify" | "deezer"; interface Credential { name: string; @@ -16,25 +16,26 @@ interface AccountFormData { accountName: string; accountRegion?: string; authBlob?: string; // Spotify specific - arl?: string; // Deezer specific + arl?: string; // Deezer specific } // --- API Functions --- const fetchCredentials = async (service: Service): Promise => { const { data } = await apiClient.get(`/credentials/${service}`); - return data.map(name => ({ name })); + return data.map((name) => ({ name })); }; -const addCredential = async ({ service, data }: { service: Service, data: AccountFormData }) => { - const payload = service === 'spotify' - ? { blob_content: data.authBlob, region: data.accountRegion } - : { arl: data.arl, region: data.accountRegion }; +const addCredential = async ({ service, data }: { service: Service; data: AccountFormData }) => { + const payload = + service === "spotify" + ? { blob_content: data.authBlob, region: data.accountRegion } + : { arl: data.arl, region: data.accountRegion }; const { data: response } = await apiClient.post(`/credentials/${service}/${data.accountName}`, payload); return response; }; -const deleteCredential = async ({ service, name }: { service: Service, name:string }) => { +const deleteCredential = async ({ service, name }: { service: Service; name: string }) => { const { data: response } = await apiClient.delete(`/credentials/${service}/${name}`); return response; }; @@ -42,21 +43,26 @@ const deleteCredential = async ({ service, name }: { service: Service, name:stri // --- Component --- export function AccountsTab() { const queryClient = useQueryClient(); - const [activeService, setActiveService] = useState('spotify'); + const [activeService, setActiveService] = useState("spotify"); const [isAdding, setIsAdding] = useState(false); const { data: credentials, isLoading } = useQuery({ - queryKey: ['credentials', activeService], + queryKey: ["credentials", activeService], queryFn: () => fetchCredentials(activeService), }); - const { register, handleSubmit, reset, formState: { errors } } = useForm(); + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm(); const addMutation = useMutation({ mutationFn: addCredential, onSuccess: () => { - toast.success('Account added successfully!'); - queryClient.invalidateQueries({ queryKey: ['credentials', activeService] }); + toast.success("Account added successfully!"); + queryClient.invalidateQueries({ queryKey: ["credentials", activeService] }); setIsAdding(false); reset(); }, @@ -69,7 +75,7 @@ export function AccountsTab() { mutationFn: deleteCredential, onSuccess: (_, variables) => { toast.success(`Account "${variables.name}" deleted.`); - queryClient.invalidateQueries({ queryKey: ['credentials', activeService] }); + queryClient.invalidateQueries({ queryKey: ["credentials", activeService] }); }, onError: (error) => { toast.error(`Failed to delete account: ${error.message}`); @@ -82,35 +88,61 @@ export function AccountsTab() { const renderAddForm = () => (
-

Add New {activeService === 'spotify' ? 'Spotify' : 'Deezer'} Account

-
- - - {errors.accountName &&

{errors.accountName.message}

} +

Add New {activeService === "spotify" ? "Spotify" : "Deezer"} Account

+
+ + + {errors.accountName &&

{errors.accountName.message}

}
- {activeService === 'spotify' && ( + {activeService === "spotify" && (
- - {errors.authBlob &&

{errors.authBlob.message}

} + + {errors.authBlob &&

{errors.authBlob.message}

}
)} - {activeService === 'deezer' && ( + {activeService === "deezer" && (
- - {errors.arl &&

{errors.arl.message}

} + + {errors.arl &&

{errors.arl.message}

}
)} -
- - +
+ +
- -
@@ -120,29 +152,46 @@ export function AccountsTab() { return (
- - + +
{isLoading ? (

Loading accounts...

) : (
- {credentials?.map(cred => ( -
- {cred.name} - -
- ))} + {credentials?.map((cred) => ( +
+ {cred.name} + +
+ ))}
)} {!isAdding && ( - + )} {isAdding && renderAddForm()}
diff --git a/spotizerr-ui/src/components/config/DownloadsTab.tsx b/spotizerr-ui/src/components/config/DownloadsTab.tsx index 1ccd213..f191993 100644 --- a/spotizerr-ui/src/components/config/DownloadsTab.tsx +++ b/spotizerr-ui/src/components/config/DownloadsTab.tsx @@ -1,14 +1,14 @@ -import { useForm, type SubmitHandler } from 'react-hook-form'; -import apiClient from '../../lib/api-client'; -import { toast } from 'sonner'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useForm, type SubmitHandler } from "react-hook-form"; +import apiClient from "../../lib/api-client"; +import { toast } from "sonner"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; // --- Type Definitions --- interface DownloadSettings { maxConcurrentDownloads: number; realTime: boolean; fallback: boolean; - convertTo: 'MP3' | 'AAC' | 'OGG' | 'OPUS' | 'FLAC' | 'WAV' | 'ALAC' | ''; + convertTo: "MP3" | "AAC" | "OGG" | "OPUS" | "FLAC" | "WAV" | "ALAC" | ""; bitrate: string; maxRetries: number; retryDelaySeconds: number; @@ -26,18 +26,18 @@ interface DownloadsTabProps { } const CONVERSION_FORMATS: Record = { - MP3: ['32k', '64k', '96k', '128k', '192k', '256k', '320k'], - AAC: ['32k', '64k', '96k', '128k', '192k', '256k'], - OGG: ['64k', '96k', '128k', '192k', '256k', '320k'], - OPUS: ['32k', '64k', '96k', '128k', '192k', '256k'], - FLAC: [], - WAV: [], - ALAC: [] + MP3: ["32k", "64k", "96k", "128k", "192k", "256k", "320k"], + AAC: ["32k", "64k", "96k", "128k", "192k", "256k"], + OGG: ["64k", "96k", "128k", "192k", "256k", "320k"], + OPUS: ["32k", "64k", "96k", "128k", "192k", "256k"], + FLAC: [], + WAV: [], + ALAC: [], }; // --- API Functions --- const saveDownloadConfig = async (data: Partial) => { - const { data: response } = await apiClient.post('/config', data); + const { data: response } = await apiClient.post("/config", data); return response; }; @@ -48,8 +48,8 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) { const mutation = useMutation({ mutationFn: saveDownloadConfig, onSuccess: () => { - toast.success('Download settings saved successfully!'); - queryClient.invalidateQueries({ queryKey: ['config'] }); + toast.success("Download settings saved successfully!"); + queryClient.invalidateQueries({ queryKey: ["config"] }); }, onError: (error) => { toast.error(`Failed to save settings: ${error.message}`); @@ -60,15 +60,15 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) { values: config, }); - const selectedFormat = watch('convertTo'); + const selectedFormat = watch("convertTo"); const onSubmit: SubmitHandler = (data) => { mutation.mutate({ - ...data, - maxConcurrentDownloads: Number(data.maxConcurrentDownloads), - maxRetries: Number(data.maxRetries), - retryDelaySeconds: Number(data.retryDelaySeconds), - retryDelayIncrease: Number(data.retryDelayIncrease), + ...data, + maxConcurrentDownloads: Number(data.maxConcurrentDownloads), + maxRetries: Number(data.maxRetries), + retryDelaySeconds: Number(data.retryDelaySeconds), + retryDelayIncrease: Number(data.retryDelayIncrease), }); }; @@ -82,16 +82,22 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {

Download Behavior

- - + +
- - + +
- - + +
@@ -99,22 +105,35 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {

Conversion

- - + +
- - + +
@@ -122,21 +141,43 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {

Retries

- - + +
- - + +
- - + +
- ); diff --git a/spotizerr-ui/src/components/config/FormattingTab.tsx b/spotizerr-ui/src/components/config/FormattingTab.tsx index 22a58d8..0228110 100644 --- a/spotizerr-ui/src/components/config/FormattingTab.tsx +++ b/spotizerr-ui/src/components/config/FormattingTab.tsx @@ -1,8 +1,8 @@ -import { useRef } from 'react'; -import { useForm, type SubmitHandler } from 'react-hook-form'; -import apiClient from '../../lib/api-client'; -import { toast } from 'sonner'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useRef } from "react"; +import { useForm, type SubmitHandler } from "react-hook-form"; +import apiClient from "../../lib/api-client"; +import { toast } from "sonner"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; // --- Type Definitions --- interface FormattingSettings { @@ -23,47 +23,46 @@ interface FormattingTabProps { // --- API Functions --- const saveFormattingConfig = async (data: Partial) => { - const { data: response } = await apiClient.post('/config', data); + const { data: response } = await apiClient.post("/config", data); return response; }; // --- Placeholders --- const placeholders = { - "Common": { - "%music%": "Track title", - "%artist%": "Track artist", - "%album%": "Album name", - "%ar_album%": "Album artist", - "%tracknum%": "Track number", - "%year%": "Year of release", - }, - "Additional": { - "%discnum%": "Disc number", - "%date%": "Release date", - "%genre%": "Music genre", - "%isrc%": "ISRC", - "%explicit%": "Explicit flag", - "%duration%": "Track duration (s)", - }, + Common: { + "%music%": "Track title", + "%artist%": "Track artist", + "%album%": "Album name", + "%ar_album%": "Album artist", + "%tracknum%": "Track number", + "%year%": "Year of release", + }, + Additional: { + "%discnum%": "Disc number", + "%date%": "Release date", + "%genre%": "Music genre", + "%isrc%": "ISRC", + "%explicit%": "Explicit flag", + "%duration%": "Track duration (s)", + }, }; const PlaceholderSelector = ({ onSelect }: { onSelect: (value: string) => void }) => ( - onSelect(e.target.value)} + className="block w-full p-2 border rounded-md bg-gray-50 dark:bg-gray-800 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm mt-1" + > + + {Object.entries(placeholders).map(([group, options]) => ( + + {Object.entries(options).map(([value, label]) => ( + ))} - + + ))} + ); - // --- Component --- export function FormattingTab({ config, isLoading }: FormattingTabProps) { const queryClient = useQueryClient(); @@ -73,8 +72,8 @@ export function FormattingTab({ config, isLoading }: FormattingTabProps) { const mutation = useMutation({ mutationFn: saveFormattingConfig, onSuccess: () => { - toast.success('Formatting settings saved!'); - queryClient.invalidateQueries({ queryKey: ['config'] }); + toast.success("Formatting settings saved!"); + queryClient.invalidateQueries({ queryKey: ["config"] }); }, onError: (error) => { toast.error(`Failed to save settings: ${error.message}`); @@ -86,17 +85,19 @@ export function FormattingTab({ config, isLoading }: FormattingTabProps) { }); // Correctly register the refs for react-hook-form while also holding a local ref. - const { ref: dirFormatRef, ...dirFormatRest } = register('customDirFormat'); - const { ref: trackFormatRef, ...trackFormatRest } = register('customTrackFormat'); - - const handlePlaceholderSelect = (field: 'customDirFormat' | 'customTrackFormat', inputRef: React.RefObject) => (value: string) => { - if (!value || !inputRef.current) return; - const { selectionStart, selectionEnd } = inputRef.current; - const currentValue = inputRef.current.value; - const newValue = currentValue.substring(0, selectionStart ?? 0) + value + currentValue.substring(selectionEnd ?? 0); - setValue(field, newValue); - }; + const { ref: dirFormatRef, ...dirFormatRest } = register("customDirFormat"); + const { ref: trackFormatRef, ...trackFormatRest } = register("customTrackFormat"); + const handlePlaceholderSelect = + (field: "customDirFormat" | "customTrackFormat", inputRef: React.RefObject) => + (value: string) => { + if (!value || !inputRef.current) return; + const { selectionStart, selectionEnd } = inputRef.current; + const currentValue = inputRef.current.value; + const newValue = + currentValue.substring(0, selectionStart ?? 0) + value + currentValue.substring(selectionEnd ?? 0); + setValue(field, newValue); + }; const onSubmit: SubmitHandler = (data) => { mutation.mutate(data); @@ -111,45 +112,54 @@ export function FormattingTab({ config, isLoading }: FormattingTabProps) {

File Naming

- - { - dirFormatRef(e); - dirInputRef.current = e; - }} - className="block w-full p-2 border rounded-md bg-gray-50 dark:bg-gray-800 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - + + { + dirFormatRef(e); + dirInputRef.current = e; + }} + className="block w-full p-2 border rounded-md bg-gray-50 dark:bg-gray-800 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
- - { - trackFormatRef(e); - trackInputRef.current = e; - }} - className="block w-full p-2 border rounded-md bg-gray-50 dark:bg-gray-800 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - + + { + trackFormatRef(e); + trackInputRef.current = e; + }} + className="block w-full p-2 border rounded-md bg-gray-50 dark:bg-gray-800 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
- - + +
- - + +
- ); diff --git a/spotizerr-ui/src/components/config/GeneralTab.tsx b/spotizerr-ui/src/components/config/GeneralTab.tsx index 57b2164..c651a3f 100644 --- a/spotizerr-ui/src/components/config/GeneralTab.tsx +++ b/spotizerr-ui/src/components/config/GeneralTab.tsx @@ -1,8 +1,8 @@ -import { useForm } from 'react-hook-form'; -import apiClient from '../../lib/api-client'; -import { toast } from 'sonner'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { useSettings } from '../../contexts/settings-context'; +import { useForm } from "react-hook-form"; +import apiClient from "../../lib/api-client"; +import { toast } from "sonner"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useSettings } from "../../contexts/settings-context"; // --- Type Definitions --- interface Credential { @@ -10,11 +10,11 @@ interface Credential { } interface GeneralSettings { - service: 'spotify' | 'deezer'; + service: "spotify" | "deezer"; spotify: string; - spotifyQuality: 'NORMAL' | 'HIGH' | 'VERY_HIGH'; + spotifyQuality: "NORMAL" | "HIGH" | "VERY_HIGH"; deezer: string; - deezerQuality: 'MP3_128' | 'MP3_320' | 'FLAC'; + deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; } interface GeneralTabProps { @@ -23,20 +23,26 @@ interface GeneralTabProps { } // --- API Functions --- -const fetchCredentials = async (service: 'spotify' | 'deezer'): Promise => { +const fetchCredentials = async (service: "spotify" | "deezer"): Promise => { const { data } = await apiClient.get(`/credentials/${service}`); - return data.map(name => ({ name })); + return data.map((name) => ({ name })); }; -const saveGeneralConfig = (data: Partial) => apiClient.post('/config', data); +const saveGeneralConfig = (data: Partial) => apiClient.post("/config", data); // --- Component --- export function GeneralTab({ config, isLoading: isConfigLoading }: GeneralTabProps) { const queryClient = useQueryClient(); const { settings: globalSettings, isLoading: settingsLoading } = useSettings(); - const { data: spotifyAccounts, isLoading: spotifyLoading } = useQuery({ queryKey: ['credentials', 'spotify'], queryFn: () => fetchCredentials('spotify') }); - const { data: deezerAccounts, isLoading: deezerLoading } = useQuery({ queryKey: ['credentials', 'deezer'], queryFn: () => fetchCredentials('deezer') }); + const { data: spotifyAccounts, isLoading: spotifyLoading } = useQuery({ + queryKey: ["credentials", "spotify"], + queryFn: () => fetchCredentials("spotify"), + }); + const { data: deezerAccounts, isLoading: deezerLoading } = useQuery({ + queryKey: ["credentials", "deezer"], + queryFn: () => fetchCredentials("deezer"), + }); const { register, handleSubmit } = useForm({ values: config, @@ -45,8 +51,8 @@ export function GeneralTab({ config, isLoading: isConfigLoading }: GeneralTabPro const mutation = useMutation({ mutationFn: saveGeneralConfig, onSuccess: () => { - toast.success('General settings saved!'); - queryClient.invalidateQueries({ queryKey: ['config'] }); + toast.success("General settings saved!"); + queryClient.invalidateQueries({ queryKey: ["config"] }); }, onError: (e: Error) => toast.error(`Failed to save: ${e.message}`), }); @@ -58,91 +64,99 @@ export function GeneralTab({ config, isLoading: isConfigLoading }: GeneralTabPro return (
-
-

Service Defaults

-
- - -
+
+

Service Defaults

+
+ +
+
-
-

Spotify Settings

-
- - -
-
- - -
+
+

Spotify Settings

+
+ +
+
+ + +
+
-
-

Deezer Settings

-
- - -
-
- - -
+
+

Deezer Settings

+
+ +
+
+ + +
+
-
-

Content Filters

-
- -
- - {globalSettings?.explicitFilter ? 'Enabled' : 'Disabled'} - - ENV -
-
-

- The explicit content filter is controlled by an environment variable and cannot be changed here. -

+
+

Content Filters

+
+ +
+ + {globalSettings?.explicitFilter ? "Enabled" : "Disabled"} + + ENV +
+

+ The explicit content filter is controlled by an environment variable and cannot be changed here. +

+
); diff --git a/spotizerr-ui/src/components/config/ServerTab.tsx b/spotizerr-ui/src/components/config/ServerTab.tsx index 22143d0..a856886 100644 --- a/spotizerr-ui/src/components/config/ServerTab.tsx +++ b/spotizerr-ui/src/components/config/ServerTab.tsx @@ -1,8 +1,8 @@ -import { useEffect } from 'react'; -import { useForm, Controller } from 'react-hook-form'; -import apiClient from '../../lib/api-client'; -import { toast } from 'sonner'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useEffect } from "react"; +import { useForm, Controller } from "react-hook-form"; +import apiClient from "../../lib/api-client"; +import { toast } from "sonner"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; // --- Type Definitions --- interface SpotifyApiSettings { @@ -18,16 +18,16 @@ interface WebhookSettings { // --- API Functions --- const fetchSpotifyApiConfig = async (): Promise => { - const { data } = await apiClient.get('/credentials/spotify_api_config'); + const { data } = await apiClient.get("/credentials/spotify_api_config"); return data; }; -const saveSpotifyApiConfig = (data: SpotifyApiSettings) => apiClient.put('/credentials/spotify_api_config', data); +const saveSpotifyApiConfig = (data: SpotifyApiSettings) => apiClient.put("/credentials/spotify_api_config", data); const fetchWebhookConfig = async (): Promise => { // Mock a response since backend endpoint doesn't exist // This will prevent the UI from crashing. return Promise.resolve({ - url: '', + url: "", events: [], available_events: ["download_start", "download_complete", "download_failed", "watch_added"], }); @@ -39,120 +39,153 @@ const saveWebhookConfig = (data: Partial) => { const testWebhook = (url: string) => { toast.info("Webhook testing is not available."); return Promise.resolve(url); -} +}; // --- Components --- function SpotifyApiForm() { - const queryClient = useQueryClient(); - const { data, isLoading } = useQuery({ queryKey: ['spotifyApiConfig'], queryFn: fetchSpotifyApiConfig }); - const { register, handleSubmit, reset } = useForm(); + const queryClient = useQueryClient(); + const { data, isLoading } = useQuery({ queryKey: ["spotifyApiConfig"], queryFn: fetchSpotifyApiConfig }); + const { register, handleSubmit, reset } = useForm(); - const mutation = useMutation({ - mutationFn: saveSpotifyApiConfig, - onSuccess: () => { - toast.success('Spotify API settings saved!'); - queryClient.invalidateQueries({ queryKey: ['spotifyApiConfig'] }); - }, - onError: (e) => toast.error(`Failed to save: ${e.message}`), - }); + const mutation = useMutation({ + mutationFn: saveSpotifyApiConfig, + onSuccess: () => { + toast.success("Spotify API settings saved!"); + queryClient.invalidateQueries({ queryKey: ["spotifyApiConfig"] }); + }, + onError: (e) => toast.error(`Failed to save: ${e.message}`), + }); - useEffect(() => { if (data) reset(data); }, [data, reset]); + useEffect(() => { + if (data) reset(data); + }, [data, reset]); - const onSubmit = (formData: SpotifyApiSettings) => mutation.mutate(formData); + const onSubmit = (formData: SpotifyApiSettings) => mutation.mutate(formData); - if (isLoading) return

Loading Spotify API settings...

; + if (isLoading) return

Loading Spotify API settings...

; - return ( -
-
- - -
-
- - -
- -
- ); + return ( +
+
+ + +
+
+ + +
+ +
+ ); } function WebhookForm() { - const queryClient = useQueryClient(); - const { data, isLoading } = useQuery({ queryKey: ['webhookConfig'], queryFn: fetchWebhookConfig }); - const { register, handleSubmit, control, reset, watch } = useForm(); - const currentUrl = watch('url'); + const queryClient = useQueryClient(); + const { data, isLoading } = useQuery({ queryKey: ["webhookConfig"], queryFn: fetchWebhookConfig }); + const { register, handleSubmit, control, reset, watch } = useForm(); + const currentUrl = watch("url"); - const mutation = useMutation({ - mutationFn: saveWebhookConfig, - onSuccess: () => { - // No toast needed since the function shows one - queryClient.invalidateQueries({ queryKey: ['webhookConfig'] }); - }, - onError: (e) => toast.error(`Failed to save: ${e.message}`), - }); + const mutation = useMutation({ + mutationFn: saveWebhookConfig, + onSuccess: () => { + // No toast needed since the function shows one + queryClient.invalidateQueries({ queryKey: ["webhookConfig"] }); + }, + onError: (e) => toast.error(`Failed to save: ${e.message}`), + }); - const testMutation = useMutation({ - mutationFn: testWebhook, - onSuccess: () => { - // No toast needed - }, - onError: (e) => toast.error(`Webhook test failed: ${e.message}`), - }); + const testMutation = useMutation({ + mutationFn: testWebhook, + onSuccess: () => { + // No toast needed + }, + onError: (e) => toast.error(`Webhook test failed: ${e.message}`), + }); - useEffect(() => { if (data) reset(data); }, [data, reset]); + useEffect(() => { + if (data) reset(data); + }, [data, reset]); - const onSubmit = (formData: WebhookSettings) => mutation.mutate(formData); + const onSubmit = (formData: WebhookSettings) => mutation.mutate(formData); - if (isLoading) return

Loading Webhook settings...

; + if (isLoading) return

Loading Webhook settings...

; - return ( -
-
- - -
-
- -
- {data?.available_events.map((event) => ( - ( - - )} - /> - ))} -
-
-
- - -
-
- ); + return ( +
+
+ + +
+
+ +
+ {data?.available_events.map((event) => ( + ( + + )} + /> + ))} +
+
+
+ + +
+
+ ); } export function ServerTab() { @@ -166,7 +199,9 @@ export function ServerTab() {

Webhooks

-

Get notifications for events like download completion. (Currently disabled)

+

+ Get notifications for events like download completion. (Currently disabled) +

diff --git a/spotizerr-ui/src/components/config/WatchTab.tsx b/spotizerr-ui/src/components/config/WatchTab.tsx index cdd7149..831a52c 100644 --- a/spotizerr-ui/src/components/config/WatchTab.tsx +++ b/spotizerr-ui/src/components/config/WatchTab.tsx @@ -1,13 +1,13 @@ -import { useEffect } from 'react'; -import { useForm, type SubmitHandler, Controller } from 'react-hook-form'; -import apiClient from '../../lib/api-client'; -import { toast } from 'sonner'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useEffect } from "react"; +import { useForm, type SubmitHandler, Controller } from "react-hook-form"; +import apiClient from "../../lib/api-client"; +import { toast } from "sonner"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; // --- Type Definitions --- const ALBUM_GROUPS = ["album", "single", "compilation", "appears_on"] as const; -type AlbumGroup = typeof ALBUM_GROUPS[number]; +type AlbumGroup = (typeof ALBUM_GROUPS)[number]; interface WatchSettings { enabled: boolean; @@ -17,12 +17,12 @@ interface WatchSettings { // --- API Functions --- const fetchWatchConfig = async (): Promise => { - const { data } = await apiClient.get('/config/watch'); + const { data } = await apiClient.get("/config/watch"); return data; }; const saveWatchConfig = async (data: Partial) => { - const { data: response } = await apiClient.post('/config/watch', data); + const { data: response } = await apiClient.post("/config/watch", data); return response; }; @@ -31,15 +31,15 @@ export function WatchTab() { const queryClient = useQueryClient(); const { data: config, isLoading } = useQuery({ - queryKey: ['watchConfig'], + queryKey: ["watchConfig"], queryFn: fetchWatchConfig, }); const mutation = useMutation({ mutationFn: saveWatchConfig, onSuccess: () => { - toast.success('Watch settings saved successfully!'); - queryClient.invalidateQueries({ queryKey: ['watchConfig'] }); + toast.success("Watch settings saved successfully!"); + queryClient.invalidateQueries({ queryKey: ["watchConfig"] }); }, onError: (error) => { toast.error(`Failed to save settings: ${error.message}`); @@ -56,8 +56,8 @@ export function WatchTab() { const onSubmit: SubmitHandler = (data) => { mutation.mutate({ - ...data, - watchPollIntervalSeconds: Number(data.watchPollIntervalSeconds), + ...data, + watchPollIntervalSeconds: Number(data.watchPollIntervalSeconds), }); }; @@ -67,54 +67,60 @@ export function WatchTab() { return (
-
-

Watchlist Behavior

-
- - -
-
- - -

- How often to check watched items for updates. -

-
+
+

Watchlist Behavior

+
+ +
- -
-

Artist Album Groups

-

Select which album groups to monitor for watched artists.

-
- {ALBUM_GROUPS.map((group) => ( - ( - - )} - /> - ))} -
+
+ + +

How often to check watched items for updates.

+
- ); diff --git a/spotizerr-ui/src/contexts/QueueProvider.tsx b/spotizerr-ui/src/contexts/QueueProvider.tsx index f5a6bf8..4fc9157 100644 --- a/spotizerr-ui/src/contexts/QueueProvider.tsx +++ b/spotizerr-ui/src/contexts/QueueProvider.tsx @@ -1,117 +1,282 @@ -import { useState, useCallback, type ReactNode, useEffect, useRef } from 'react'; -import apiClient from '../lib/api-client'; -import { QueueContext, type QueueItem } from './queue-context'; +import { useState, useCallback, type ReactNode, useEffect, useRef } from "react"; +import apiClient from "../lib/api-client"; +import { QueueContext, type QueueItem, type DownloadType, type QueueStatus } from "./queue-context"; +import { toast } from "sonner"; +import { v4 as uuidv4 } from "uuid"; // --- Helper Types --- -interface TaskStatus { - status: 'downloading' | 'completed' | 'error' | 'queued'; - progress?: number; - speed?: string; - size?: string; - eta?: string; - message?: string; +// This represents the raw status object from the backend polling endpoint +interface TaskStatusDTO { + status: QueueStatus; + message?: string; + can_retry?: boolean; + + // Progress indicators + progress?: number; + speed?: string; + size?: string; + eta?: string; + + // Multi-track progress + current_track?: number; + total_tracks?: number; + summary?: { + successful_tracks: number; + skipped_tracks: number; + failed_tracks: number; + failed_track_details: { name: string; reason: string }[]; + }; } +const isTerminalStatus = (status: QueueStatus) => ["completed", "error", "cancelled", "skipped"].includes(status); + export function QueueProvider({ children }: { children: ReactNode }) { - const [items, setItems] = useState([]); - const [isVisible, setIsVisible] = useState(false); - const pollingIntervals = useRef>({}); + const [items, setItems] = useState(() => { + try { + const storedItems = localStorage.getItem("queueItems"); + return storedItems ? JSON.parse(storedItems) : []; + } catch { + return []; + } + }); + const [isVisible, setIsVisible] = useState(false); + const pollingIntervals = useRef>({}); - // --- Core Action: Add Item --- - const addItem = useCallback(async (item: Omit) => { - const newItem: QueueItem = { ...item, status: 'queued' }; - setItems(prev => [...prev, newItem]); - toggleVisibility(); + // --- Persistence --- + useEffect(() => { + localStorage.setItem("queueItems", JSON.stringify(items)); + }, [items]); + const stopPolling = useCallback((internalId: string) => { + if (pollingIntervals.current[internalId]) { + clearInterval(pollingIntervals.current[internalId]); + delete pollingIntervals.current[internalId]; + } + }, []); + + // --- Polling Logic --- + const startPolling = useCallback( + (internalId: string, taskId: string) => { + if (pollingIntervals.current[internalId]) return; + + const intervalId = window.setInterval(async () => { try { - // This endpoint should initiate the download and return a task ID - const response = await apiClient.post<{ taskId: string }>(`/download/${item.type}`, { id: item.id }); - const { taskId } = response.data; + const response = await apiClient.get(`/download/status/${taskId}`); + const statusUpdate = response.data; - // Update item with taskId and start polling - setItems(prev => prev.map(i => i.id === item.id ? { ...i, taskId, status: 'pending' } : i)); - startPolling(taskId); + setItems((prev) => + prev.map((item) => { + if (item.id === internalId) { + const updatedItem: QueueItem = { + ...item, + status: statusUpdate.status, + progress: statusUpdate.progress, + speed: statusUpdate.speed, + size: statusUpdate.size, + eta: statusUpdate.eta, + error: statusUpdate.status === "error" ? statusUpdate.message : undefined, + canRetry: statusUpdate.can_retry, + currentTrackNumber: statusUpdate.current_track, + totalTracks: statusUpdate.total_tracks, + summary: statusUpdate.summary + ? { + successful: statusUpdate.summary.successful_tracks, + skipped: statusUpdate.summary.skipped_tracks, + failed: statusUpdate.summary.failed_tracks, + failedTracks: statusUpdate.summary.failed_track_details, + } + : item.summary, + }; + + if (isTerminalStatus(statusUpdate.status)) { + stopPolling(internalId); + } + return updatedItem; + } + return item; + }), + ); } catch (error) { - console.error(`Failed to start download for ${item.name}:`, error); - setItems(prev => prev.map(i => i.id === item.id ? { ...i, status: 'error', error: 'Failed to start download' } : i)); + console.error(`Polling failed for task ${taskId}:`, error); + stopPolling(internalId); + setItems((prev) => + prev.map((i) => + i.id === internalId + ? { + ...i, + status: "error", + error: "Connection lost", + } + : i, + ), + ); } - }, []); + }, 2000); // Poll every 2 seconds - // --- Polling Logic --- - const startPolling = (taskId: string) => { - if (pollingIntervals.current[taskId]) return; // Already polling + pollingIntervals.current[internalId] = intervalId; + }, + [stopPolling], + ); - const intervalId = window.setInterval(async () => { - try { - const response = await apiClient.get(`/download/status/${taskId}`); - const statusUpdate = response.data; + // --- Core Action: Add Item --- + const addItem = useCallback( + async (item: { name: string; type: DownloadType; spotifyId: string }) => { + const internalId = uuidv4(); + const newItem: QueueItem = { + ...item, + id: internalId, + status: "queued", + }; + setItems((prev) => [...prev, newItem]); + if (!isVisible) setIsVisible(true); - setItems(prev => prev.map(item => { - if (item.taskId === taskId) { - const updatedItem = { - ...item, - status: statusUpdate.status, - progress: statusUpdate.progress, - speed: statusUpdate.speed, - size: statusUpdate.size, - eta: statusUpdate.eta, - error: statusUpdate.status === 'error' ? statusUpdate.message : undefined, - }; + try { + const response = await apiClient.post<{ task_id: string }>(`/download`, { + url: `https://open.spotify.com/${item.type}/${item.spotifyId}`, + }); + const { task_id } = response.data; + setItems((prev) => + prev.map((i) => (i.id === internalId ? { ...i, taskId: task_id, status: "initializing" } : i)), + ); + startPolling(internalId, task_id); + } catch (error) { + console.error(`Failed to start download for ${item.name}:`, error); + toast.error(`Failed to start download for ${item.name}`); + setItems((prev) => + prev.map((i) => + i.id === internalId + ? { + ...i, + status: "error", + error: "Failed to start download task.", + } + : i, + ), + ); + } + }, + [isVisible, startPolling], + ); - if (statusUpdate.status === 'completed' || statusUpdate.status === 'error') { - stopPolling(taskId); - } - return updatedItem; - } - return item; - })); - } catch (error) { - console.error(`Polling failed for task ${taskId}:`, error); - stopPolling(taskId); - setItems(prev => prev.map(i => i.taskId === taskId ? { ...i, status: 'error', error: 'Connection lost' } : i)); + const clearAllPolls = useCallback(() => { + Object.values(pollingIntervals.current).forEach(clearInterval); + }, []); + + // --- Load existing tasks on startup --- + useEffect(() => { + const syncActiveTasks = async () => { + try { + const response = await apiClient.get("/download/active"); + const activeTasks = response.data; + + // Basic reconciliation + setItems((prevItems) => { + const newItems = [...prevItems]; + activeTasks.forEach((task) => { + if (!newItems.some((item) => item.taskId === task.taskId)) { + newItems.push({ + ...task, + id: task.taskId || uuidv4(), + }); } - }, 2000); // Poll every 2 seconds + }); + return newItems; + }); - pollingIntervals.current[taskId] = intervalId; + activeTasks.forEach((item) => { + if (item.id && item.taskId && !isTerminalStatus(item.status)) { + startPolling(item.id, item.taskId); + } + }); + } catch (error) { + console.error("Failed to sync active tasks:", error); + } }; + syncActiveTasks(); - const stopPolling = (taskId: string) => { - clearInterval(pollingIntervals.current[taskId]); - delete pollingIntervals.current[taskId]; - }; + // restart polling for any non-terminal items from localStorage + items.forEach((item) => { + if (item.id && item.taskId && !isTerminalStatus(item.status)) { + startPolling(item.id, item.taskId); + } + }); - // Cleanup on unmount - useEffect(() => { - return () => { - Object.values(pollingIntervals.current).forEach(clearInterval); - }; - }, []); + return clearAllPolls; + // This effect should only run once on mount to initialize the queue. + // We are intentionally omitting 'items' as a dependency to prevent re-runs. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clearAllPolls, startPolling]); - // --- Other Actions --- - const removeItem = useCallback((id: string) => { - const itemToRemove = items.find(i => i.id === id); - if (itemToRemove && itemToRemove.taskId) { - stopPolling(itemToRemove.taskId); - // Optionally, call an API to cancel the backend task - // apiClient.post(`/download/cancel/${itemToRemove.taskId}`); + // --- Other Actions --- + const removeItem = useCallback( + async (id: string) => { + const itemToRemove = items.find((i) => i.id === id); + if (itemToRemove) { + stopPolling(itemToRemove.id); + if (itemToRemove.taskId) { + try { + await apiClient.post(`/download/cancel/${itemToRemove.taskId}`); + toast.success(`Cancelled download: ${itemToRemove.name}`); + } catch { + toast.error(`Failed to cancel download: ${itemToRemove.name}`); + } } - setItems(prev => prev.filter(item => item.id !== id)); - }, [items]); + } + setItems((prev) => prev.filter((item) => item.id !== id)); + }, + [items, stopPolling], + ); - const clearQueue = useCallback(() => { - Object.values(pollingIntervals.current).forEach(clearInterval); - pollingIntervals.current = {}; - setItems([]); - // Optionally, call an API to cancel all tasks - }, []); + const retryItem = useCallback( + async (id: string) => { + const itemToRetry = items.find((i) => i.id === id); + if (!itemToRetry || !itemToRetry.spotifyId) return; - const toggleVisibility = useCallback(() => setIsVisible(prev => !prev), []); + // Remove the old item + setItems((prev) => prev.filter((item) => item.id !== id)); - const value = { items, isVisible, addItem, removeItem, clearQueue, toggleVisibility }; + // Add it again + await addItem({ + name: itemToRetry.name, + type: itemToRetry.type, + spotifyId: itemToRetry.spotifyId, + }); + toast.info(`Retrying download: ${itemToRetry.name}`); + }, + [items, addItem], + ); - return ( - - {children} - - ); + const clearQueue = useCallback(async () => { + for (const item of items) { + if (item.taskId) { + stopPolling(item.id); + try { + await apiClient.post(`/download/cancel/${item.taskId}`); + } catch (err) { + console.error(`Failed to cancel task ${item.taskId}`, err); + } + } + } + setItems([]); + toast.info("Queue cleared."); + }, [items, stopPolling]); + + const clearCompleted = useCallback(() => { + setItems((prev) => prev.filter((item) => !isTerminalStatus(item.status))); + }, []); + + const toggleVisibility = useCallback(() => setIsVisible((prev) => !prev), []); + + const value = { + items, + isVisible, + addItem, + removeItem, + retryItem, + clearQueue, + toggleVisibility, + clearCompleted, + }; + + return {children}; } diff --git a/spotizerr-ui/src/contexts/SettingsProvider.tsx b/spotizerr-ui/src/contexts/SettingsProvider.tsx index 48ab045..fcf507b 100644 --- a/spotizerr-ui/src/contexts/SettingsProvider.tsx +++ b/spotizerr-ui/src/contexts/SettingsProvider.tsx @@ -1,19 +1,19 @@ -import { type ReactNode } from 'react'; -import apiClient from '../lib/api-client'; -import { SettingsContext, type AppSettings } from './settings-context'; -import { useQuery } from '@tanstack/react-query'; +import { type ReactNode } from "react"; +import apiClient from "../lib/api-client"; +import { SettingsContext, type AppSettings } from "./settings-context"; +import { useQuery } from "@tanstack/react-query"; // --- Case Conversion Utility --- // This is added here to simplify the fix and avoid module resolution issues. function snakeToCamel(str: string): string { - return str.replace(/(_\w)/g, m => m[1].toUpperCase()); + return str.replace(/(_\w)/g, (m) => m[1].toUpperCase()); } function convertKeysToCamelCase(obj: unknown): unknown { if (Array.isArray(obj)) { - return obj.map(v => convertKeysToCamelCase(v)); + return obj.map((v) => convertKeysToCamelCase(v)); } - if (typeof obj === 'object' && obj !== null) { + if (typeof obj === "object" && obj !== null) { return Object.keys(obj).reduce((acc: Record, key: string) => { const camelKey = snakeToCamel(key); acc[camelKey] = convertKeysToCamelCase((obj as Record)[key]); @@ -25,15 +25,15 @@ function convertKeysToCamelCase(obj: unknown): unknown { // Redefine AppSettings to match the flat structure of the API response export type FlatAppSettings = { - service: 'spotify' | 'deezer'; + service: "spotify" | "deezer"; spotify: string; - spotifyQuality: 'NORMAL' | 'HIGH' | 'VERY_HIGH'; + spotifyQuality: "NORMAL" | "HIGH" | "VERY_HIGH"; deezer: string; - deezerQuality: 'MP3_128' | 'MP3_320' | 'FLAC'; + deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; maxConcurrentDownloads: number; realTime: boolean; fallback: boolean; - convertTo: 'MP3' | 'AAC' | 'OGG' | 'OPUS' | 'FLAC' | 'WAV' | 'ALAC' | ''; + convertTo: "MP3" | "AAC" | "OGG" | "OPUS" | "FLAC" | "WAV" | "ALAC" | ""; bitrate: string; maxRetries: number; retryDelaySeconds: number; @@ -44,7 +44,7 @@ export type FlatAppSettings = { saveCover: boolean; explicitFilter: boolean; // Add other fields from the old AppSettings as needed by other parts of the app - watch: AppSettings['watch']; + watch: AppSettings["watch"]; // Add defaults for the new download properties threads: number; path: string; @@ -59,60 +59,60 @@ export type FlatAppSettings = { }; const defaultSettings: FlatAppSettings = { - service: 'spotify', - spotify: '', - spotifyQuality: 'NORMAL', - deezer: '', - deezerQuality: 'MP3_128', + service: "spotify", + spotify: "", + spotifyQuality: "NORMAL", + deezer: "", + deezerQuality: "MP3_128", maxConcurrentDownloads: 3, realTime: false, fallback: false, - convertTo: '', - bitrate: '', + convertTo: "", + bitrate: "", maxRetries: 3, retryDelaySeconds: 5, retryDelayIncrease: 5, - customDirFormat: '%ar_album%/%album%', - customTrackFormat: '%tracknum%. %music%', + customDirFormat: "%ar_album%/%album%", + customTrackFormat: "%tracknum%. %music%", tracknumPadding: true, saveCover: true, explicitFilter: false, // Add defaults for the new download properties threads: 4, - path: '/downloads', + path: "/downloads", skipExisting: true, m3u: false, hlsThreads: 8, // Add defaults for the new formatting properties - track: '{artist_name}/{album_name}/{track_number} - {track_name}', - album: '{artist_name}/{album_name}', - playlist: 'Playlists/{playlist_name}', - compilation: 'Compilations/{album_name}', + track: "{artist_name}/{album_name}/{track_number} - {track_name}", + album: "{artist_name}/{album_name}", + playlist: "Playlists/{playlist_name}", + compilation: "Compilations/{album_name}", watch: { enabled: false, }, }; const fetchSettings = async (): Promise => { - const { data } = await apiClient.get('/config'); - // Transform the keys before returning the data - return convertKeysToCamelCase(data) as FlatAppSettings; + const { data } = await apiClient.get("/config"); + // Transform the keys before returning the data + return convertKeysToCamelCase(data) as FlatAppSettings; }; export function SettingsProvider({ children }: { children: ReactNode }) { - const { data: settings, isLoading, isError } = useQuery({ - queryKey: ['config'], + const { + data: settings, + isLoading, + isError, + } = useQuery({ + queryKey: ["config"], queryFn: fetchSettings, staleTime: 1000 * 60 * 5, // 5 minutes refetchOnWindowFocus: false, }); // Use default settings on error to prevent app crash - const value = { settings: isError ? defaultSettings : (settings || null), isLoading }; + const value = { settings: isError ? defaultSettings : settings || null, isLoading }; - return ( - - {children} - - ); + return {children}; } diff --git a/spotizerr-ui/src/contexts/queue-context.ts b/spotizerr-ui/src/contexts/queue-context.ts index d053986..9ed5edf 100644 --- a/spotizerr-ui/src/contexts/queue-context.ts +++ b/spotizerr-ui/src/contexts/queue-context.ts @@ -1,26 +1,55 @@ -import { createContext, useContext } from 'react'; +import { createContext, useContext } from "react"; + +export type DownloadType = "track" | "album" | "artist" | "playlist"; +export type QueueStatus = + | "initializing" + | "pending" + | "downloading" + | "processing" + | "completed" + | "error" + | "skipped" + | "cancelled" + | "queued"; export interface QueueItem { - id: string; // This is the Spotify ID - type: 'track' | 'album' | 'artist' | 'playlist'; + id: string; // Unique ID for the queue item (can be task_id from backend) name: string; - // --- Real-time progress fields --- - status: 'pending' | 'downloading' | 'completed' | 'error' | 'queued'; + type: DownloadType; + spotifyId: string; // Original Spotify ID + + // --- Status and Progress --- + status: QueueStatus; taskId?: string; // The backend task ID for polling - progress?: number; + error?: string; + canRetry?: boolean; + + // --- Single Track Progress --- + progress?: number; // 0-100 speed?: string; size?: string; eta?: string; - error?: string; + + // --- Multi-Track (Album/Playlist) Progress --- + currentTrackNumber?: number; + totalTracks?: number; + summary?: { + successful: number; + skipped: number; + failed: number; + failedTracks?: { name: string; reason: string }[]; + }; } export interface QueueContextType { items: QueueItem[]; isVisible: boolean; - addItem: (item: Omit) => void; + addItem: (item: { name: string; type: DownloadType; spotifyId: string }) => void; removeItem: (id: string) => void; + retryItem: (id: string) => void; clearQueue: () => void; toggleVisibility: () => void; + clearCompleted: () => void; } export const QueueContext = createContext(undefined); @@ -28,7 +57,7 @@ export const QueueContext = createContext(undefine export function useQueue() { const context = useContext(QueueContext); if (context === undefined) { - throw new Error('useQueue must be used within a QueueProvider'); + throw new Error("useQueue must be used within a QueueProvider"); } return context; } diff --git a/spotizerr-ui/src/contexts/settings-context.ts b/spotizerr-ui/src/contexts/settings-context.ts index 52d2320..dab378d 100644 --- a/spotizerr-ui/src/contexts/settings-context.ts +++ b/spotizerr-ui/src/contexts/settings-context.ts @@ -1,16 +1,16 @@ -import { createContext, useContext } from 'react'; +import { createContext, useContext } from "react"; // This new type reflects the flat structure of the /api/config response export interface AppSettings { - service: 'spotify' | 'deezer'; + service: "spotify" | "deezer"; spotify: string; - spotifyQuality: 'NORMAL' | 'HIGH' | 'VERY_HIGH'; + spotifyQuality: "NORMAL" | "HIGH" | "VERY_HIGH"; deezer: string; - deezerQuality: 'MP3_128' | 'MP3_320' | 'FLAC'; + deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; maxConcurrentDownloads: number; realTime: boolean; fallback: boolean; - convertTo: 'MP3' | 'AAC' | 'OGG' | 'OPUS' | 'FLAC' | 'WAV' | 'ALAC' | ''; + convertTo: "MP3" | "AAC" | "OGG" | "OPUS" | "FLAC" | "WAV" | "ALAC" | ""; bitrate: string; maxRetries: number; retryDelaySeconds: number; @@ -48,7 +48,7 @@ export const SettingsContext = createContext(un export function useSettings() { const context = useContext(SettingsContext); if (context === undefined) { - throw new Error('useSettings must be used within a SettingsProvider'); + throw new Error("useSettings must be used within a SettingsProvider"); } return context; } diff --git a/spotizerr-ui/src/lib/api-client.ts b/spotizerr-ui/src/lib/api-client.ts index 7e43890..c9a3dd8 100644 --- a/spotizerr-ui/src/lib/api-client.ts +++ b/spotizerr-ui/src/lib/api-client.ts @@ -1,10 +1,10 @@ -import axios from 'axios'; -import { toast } from 'sonner'; +import axios from "axios"; +import { toast } from "sonner"; const apiClient = axios.create({ - baseURL: '/api', + baseURL: "/api", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, timeout: 10000, // 10 seconds timeout }); @@ -12,30 +12,30 @@ const apiClient = axios.create({ // Response interceptor for error handling apiClient.interceptors.response.use( (response) => { - const contentType = response.headers['content-type']; - if (contentType && contentType.includes('application/json')) { + const contentType = response.headers["content-type"]; + if (contentType && contentType.includes("application/json")) { return response; } // If the response is not JSON, reject it to trigger the error handling - const error = new Error('Invalid response type. Expected JSON.'); - toast.error('API Error', { - description: 'Received an invalid response from the server. Expected JSON data.', + const error = new Error("Invalid response type. Expected JSON."); + toast.error("API Error", { + description: "Received an invalid response from the server. Expected JSON data.", }); return Promise.reject(error); }, (error) => { - if (error.code === 'ECONNABORTED') { - toast.error('Request Timed Out', { - description: 'The server did not respond in time. Please try again later.', + if (error.code === "ECONNABORTED") { + toast.error("Request Timed Out", { + description: "The server did not respond in time. Please try again later.", }); } else { - const errorMessage = error.response?.data?.error || error.message || 'An unknown error occurred.'; - toast.error('API Error', { - description: errorMessage, - }); + const errorMessage = error.response?.data?.error || error.message || "An unknown error occurred."; + toast.error("API Error", { + description: errorMessage, + }); } return Promise.reject(error); - } + }, ); export default apiClient; diff --git a/spotizerr-ui/src/main.tsx b/spotizerr-ui/src/main.tsx index 65c5ede..6ace0a2 100644 --- a/spotizerr-ui/src/main.tsx +++ b/spotizerr-ui/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { RouterProvider } from '@tanstack/react-router'; -import { router } from './router'; -import './index.css'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { RouterProvider } from "@tanstack/react-router"; +import { router } from "./router"; +import "./index.css"; -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById("root")!).render( , diff --git a/spotizerr-ui/src/router.tsx b/spotizerr-ui/src/router.tsx index ffd82e9..3024555 100644 --- a/spotizerr-ui/src/router.tsx +++ b/spotizerr-ui/src/router.tsx @@ -1,13 +1,13 @@ -import { createRouter, createRootRoute, createRoute } from '@tanstack/react-router'; -import { Root } from './routes/root'; -import { Album } from './routes/album'; -import { Artist } from './routes/artist'; -import { Track } from './routes/track'; -import { Home } from './routes/home'; -import { Config } from './routes/config'; -import { Playlist } from './routes/playlist'; -import { History } from './routes/history'; -import { Watchlist } from './routes/watchlist'; +import { createRouter, createRootRoute, createRoute } from "@tanstack/react-router"; +import { Root } from "./routes/root"; +import { Album } from "./routes/album"; +import { Artist } from "./routes/artist"; +import { Track } from "./routes/track"; +import { Home } from "./routes/home"; +import { Config } from "./routes/config"; +import { Playlist } from "./routes/playlist"; +import { History } from "./routes/history"; +import { Watchlist } from "./routes/watchlist"; const rootRoute = createRootRoute({ component: Root, @@ -15,49 +15,49 @@ const rootRoute = createRootRoute({ const indexRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/', + path: "/", component: Home, }); const albumRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/album/$albumId', + path: "/album/$albumId", component: Album, }); const artistRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/artist/$artistId', + path: "/artist/$artistId", component: Artist, }); const trackRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/track/$trackId', + path: "/track/$trackId", component: Track, }); const configRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/config', + path: "/config", component: Config, }); const playlistRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/playlist/$playlistId', + path: "/playlist/$playlistId", component: Playlist, }); const historyRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/history', + path: "/history", component: History, }); const watchlistRoute = createRoute({ getParentRoute: () => rootRoute, - path: '/watchlist', + path: "/watchlist", component: Watchlist, }); @@ -74,7 +74,7 @@ const routeTree = rootRoute.addChildren([ export const router = createRouter({ routeTree }); -declare module '@tanstack/react-router' { +declare module "@tanstack/react-router" { interface Register { router: typeof router; } diff --git a/spotizerr-ui/src/routes/album.tsx b/spotizerr-ui/src/routes/album.tsx index bd33058..d31ef43 100644 --- a/spotizerr-ui/src/routes/album.tsx +++ b/spotizerr-ui/src/routes/album.tsx @@ -1,25 +1,31 @@ -import { Link, useParams } from '@tanstack/react-router'; -import { useEffect, useState } from 'react'; -import apiClient from '../lib/api-client'; -import { useQueue } from '../contexts/queue-context'; -import { useSettings } from '../contexts/settings-context'; -import type { AlbumType, TrackType } from '../types/spotify'; +import { Link, useParams } from "@tanstack/react-router"; +import { useEffect, useState, useContext } from "react"; +import apiClient from "../lib/api-client"; +import { QueueContext } from "../contexts/queue-context"; +import { useSettings } from "../contexts/settings-context"; +import type { AlbumType, TrackType } from "../types/spotify"; +import { toast } from "sonner"; export const Album = () => { - const { albumId } = useParams({ from: '/album/$albumId' }); + const { albumId } = useParams({ from: "/album/$albumId" }); const [album, setAlbum] = useState(null); const [error, setError] = useState(null); - const { addItem, toggleVisibility } = useQueue(); + const context = useContext(QueueContext); const { settings } = useSettings(); + if (!context) { + throw new Error("useQueue must be used within a QueueProvider"); + } + const { addItem } = context; + useEffect(() => { const fetchAlbum = async () => { try { const response = await apiClient.get(`/album/info?id=${albumId}`); setAlbum(response.data); } catch (err) { - setError('Failed to load album'); - console.error('Error fetching album:', err); + setError("Failed to load album"); + console.error("Error fetching album:", err); } }; @@ -29,14 +35,15 @@ export const Album = () => { }, [albumId]); const handleDownloadTrack = (track: TrackType) => { - addItem({ id: track.id, type: 'track', name: track.name }); - toggleVisibility(); + if (!track.id) return; + toast.info(`Adding ${track.name} to queue...`); + addItem({ spotifyId: track.id, type: "track", name: track.name }); }; const handleDownloadAlbum = () => { if (!album) return; - addItem({ id: album.id, type: 'album', name: album.name }); - toggleVisibility(); + toast.info(`Adding ${album.name} to queue...`); + addItem({ spotifyId: album.id, type: "album", name: album.name }); }; if (error) { @@ -59,46 +66,42 @@ export const Album = () => { ); } - const hasExplicitTrack = album.tracks.items.some(track => track.explicit); + const hasExplicitTrack = album.tracks.items.some((track) => track.explicit); return (
{album.name}

{album.name}

- By{' '} + By{" "} {album.artists.map((artist, index) => ( - + {artist.name} - {index < album.artists.length - 1 && ', '} + {index < album.artists.length - 1 && ", "} ))}

{new Date(album.release_date).getFullYear()} • {album.total_tracks} songs

-

- {album.label} -

+

{album.label}

- @@ -111,14 +114,17 @@ export const Album = () => { {album.tracks.items.map((track, index) => { if (isExplicitFilterEnabled && track.explicit) { return ( -
-
- {index + 1} +
+
+ {index + 1}

Explicit track filtered

--:--
- ) + ); } return (
{

{track.name}

{track.artists.map((artist, index) => ( - + {artist.name} - {index < track.artists.length - 1 && ', '} + {index < track.artists.length - 1 && ", "} ))}

@@ -148,7 +156,7 @@ export const Album = () => {
{Math.floor(track.duration_ms / 60000)}: - {((track.duration_ms % 60000) / 1000).toFixed(0).padStart(2, '0')} + {((track.duration_ms % 60000) / 1000).toFixed(0).padStart(2, "0")}
- ) + ); })}
); -} +}; diff --git a/spotizerr-ui/src/routes/artist.tsx b/spotizerr-ui/src/routes/artist.tsx index 9999738..08f5d88 100644 --- a/spotizerr-ui/src/routes/artist.tsx +++ b/spotizerr-ui/src/routes/artist.tsx @@ -1,253 +1,107 @@ -import { Link, useParams } from '@tanstack/react-router'; -import { useEffect, useState } from 'react'; -import { toast } from 'sonner'; -import apiClient from '../lib/api-client'; -import { useQueue } from '../contexts/queue-context'; -import type { AlbumType } from '../types/spotify'; - -interface ArtistInfo { - artist: { - name: string; - images: { url: string }[]; - followers: { total: number }; - }; - topTracks: Track[]; - albums: AlbumGroup; -} - -interface Track { - id: string; - name:string; - duration_ms: number; - album: { - id: string; - name: string; - images: { url: string }[]; - }; -} - -interface UAlbum extends AlbumType { - is_known?: boolean; -} - -interface AlbumGroup { - album: UAlbum[]; - single: UAlbum[]; - appears_on: UAlbum[]; -} +import { Link, useParams } from "@tanstack/react-router"; +import { useEffect, useState, useContext } from "react"; +import { toast } from "sonner"; +import apiClient from "../lib/api-client"; +import type { AlbumType, ArtistType, TrackType } from "../types/spotify"; +import { QueueContext } from "../contexts/queue-context"; +import { useSettings } from "../contexts/settings-context"; export const Artist = () => { - const { artistId } = useParams({ from: '/artist/$artistId' }); - const [artistInfo, setArtistInfo] = useState(null); - const [isWatched, setIsWatched] = useState(false); - const [isWatchEnabled, setIsWatchEnabled] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const { addItem, toggleVisibility } = useQueue(); + const { artistId } = useParams({ from: "/artist/$artistId" }); + const [artistInfo, setArtistInfo] = useState<{ + artist: ArtistType; + top_tracks: TrackType[]; + albums: AlbumType[]; + } | null>(null); + const [error, setError] = useState(null); + const context = useContext(QueueContext); + const { settings } = useSettings(); + + if (!context) { + throw new Error("useQueue must be used within a QueueProvider"); + } + const { addItem } = context; useEffect(() => { - const fetchAllData = async () => { - if (!artistId) return; - setIsLoading(true); + const fetchArtistInfo = async () => { try { - const [infoRes, watchConfigRes, watchStatusRes] = await Promise.all([ - apiClient.get(`/artist/info?id=${artistId}`), - apiClient.get('/config/watch'), - apiClient.get(`/artist/watch/status?id=${artistId}`), - ]); - - setArtistInfo(infoRes.data); - setIsWatchEnabled(watchConfigRes.data.enabled); - setIsWatched(watchStatusRes.data.is_watched); - - } catch { - // The API client interceptor will now handle showing the error toast - } finally { - setIsLoading(false); + const response = await apiClient.get(`/artist/info?id=${artistId}`); + setArtistInfo(response.data); + } catch (err) { + setError("Failed to load artist"); + console.error(err); } }; - fetchAllData(); + + if (artistId) { + fetchArtistInfo(); + } }, [artistId]); - const handleDownloadTrack = (track: Track) => { - addItem({ id: track.id, type: 'track', name: track.name }); - toggleVisibility(); + const handleDownloadTrack = (track: TrackType) => { + if (!track.id) return; + toast.info(`Adding ${track.name} to queue...`); + addItem({ spotifyId: track.id, type: "track", name: track.name }); }; - const handleDownloadAll = () => { + const handleDownloadArtist = () => { if (!artistId || !artistInfo) return; - addItem({ id: artistId, type: 'artist', name: artistInfo.artist.name }); - toggleVisibility(); + toast.info(`Adding ${artistInfo.artist.name} to queue...`); + addItem({ + spotifyId: artistId, + type: "artist", + name: artistInfo.artist.name, + }); }; - const handleWatch = async () => { - if (!artistId) return; - const originalState = isWatched; - setIsWatched(!originalState); // Optimistic update - try { - await apiClient.post(originalState ? '/artist/unwatch' : '/artist/watch', { artistId }); - toast.success(`Artist ${originalState ? 'unwatched' : 'watched'} successfully.`); - } catch { - setIsWatched(originalState); // Revert on error + if (error) { + return
{error}
; + } + + if (!artistInfo) { + return
Loading...
; + } + + const filteredAlbums = artistInfo.albums.filter((album) => { + if (settings?.explicitFilter) { + return !album.name.toLowerCase().includes("remix"); } - }; - - const handleSync = async () => { - if (!artistId) return; - toast.info('Syncing artist...', { id: 'sync-artist' }); - try { - await apiClient.post('/artist/sync', { artistId }); - toast.success('Artist sync completed.', { id: 'sync-artist' }); - } catch { - toast.error('Artist sync failed.', { id: 'sync-artist' }); - } - }; - - const handleMarkAsKnown = async (albumId: string, known: boolean) => { - if (!artistId) return; - try { - await apiClient.post('/artist/album/mark', { artistId, albumId, known }); - setArtistInfo(prev => { - if (!prev) return null; - const updateAlbums = (albums: UAlbum[]) => albums.map(a => a.id === albumId ? { ...a, is_known: known } : a); - return { - ...prev, - albums: { - album: updateAlbums(prev.albums.album), - single: updateAlbums(prev.albums.single), - appears_on: updateAlbums(prev.albums.appears_on), - } - } - }); - toast.success(`Album marked as ${known ? 'seen' : 'unseen'}.`); - } catch { - // Error toast handled by interceptor - } - }; - - if (isLoading) return
Loading artist...
; - if (!artistInfo) return
Could not load artist details.
; - - - const { artist, topTracks, albums } = artistInfo; - - const renderAlbumCard = (album: UAlbum) => ( -
- - {album.name} -

{album.name}

-

{new Date(album.release_date).getFullYear()}

- - {isWatched && ( - - )} -
- ); + return true; + }); return ( -
- {/* Artist Header */} -
- {artist.name} -
-

{artist.name}

-

{artist.followers.total.toLocaleString()} followers

-
- - {isWatchEnabled && ( - <> - - {isWatched && ( - - )} - - )} -
-
+
+
+ {artistInfo.artist.name} +

{artistInfo.artist.name}

+
- {/* Top Tracks */} -
-

Top Tracks

-
- {topTracks.map((track) => ( -
-
- {track.album.name} -
-

{track.name}

-

- {Math.floor(track.duration_ms / 60000)}: - {((track.duration_ms % 60000) / 1000).toFixed(0).padStart(2, '0')} -

-
-
- -
- ))} -
-
+

Top Tracks

+
+ {artistInfo.top_tracks.map((track) => ( +
+ + {track.name} + + +
+ ))} +
- {/* Albums */} -
-

Albums

-
- {albums.album.map(renderAlbumCard)} -
-
- - {/* Singles */} -
-

Singles & EPs

-
- {albums.single.map(renderAlbumCard)} -
-
- - {/* Appears On */} -
-

Appears On

-
- {albums.appears_on.map(renderAlbumCard)} -
-
+

Albums

+
+ {filteredAlbums.map((album) => ( +
+ + {album.name} +

{album.name}

+ +
+ ))} +
); -} +}; diff --git a/spotizerr-ui/src/routes/config.tsx b/spotizerr-ui/src/routes/config.tsx index 16f1b47..07ab526 100644 --- a/spotizerr-ui/src/routes/config.tsx +++ b/spotizerr-ui/src/routes/config.tsx @@ -1,14 +1,14 @@ -import { useState } from 'react'; -import { GeneralTab } from '../components/config/GeneralTab'; -import { DownloadsTab } from '../components/config/DownloadsTab'; -import { FormattingTab } from '../components/config/FormattingTab'; -import { AccountsTab } from '../components/config/AccountsTab'; -import { WatchTab } from '../components/config/WatchTab'; -import { ServerTab } from '../components/config/ServerTab'; -import { useSettings } from '../contexts/settings-context'; +import { useState } from "react"; +import { GeneralTab } from "../components/config/GeneralTab"; +import { DownloadsTab } from "../components/config/DownloadsTab"; +import { FormattingTab } from "../components/config/FormattingTab"; +import { AccountsTab } from "../components/config/AccountsTab"; +import { WatchTab } from "../components/config/WatchTab"; +import { ServerTab } from "../components/config/ServerTab"; +import { useSettings } from "../contexts/settings-context"; const ConfigComponent = () => { - const [activeTab, setActiveTab] = useState('general'); + const [activeTab, setActiveTab] = useState("general"); // Get settings from the context instead of fetching here const { settings: config, isLoading } = useSettings(); @@ -18,24 +18,23 @@ const ConfigComponent = () => { if (!config) return

Error loading configuration.

; switch (activeTab) { - case 'general': + case "general": return ; - case 'downloads': + case "downloads": return ; - case 'formatting': + case "formatting": return ; - case 'accounts': + case "accounts": return ; - case 'watch': + case "watch": return ; - case 'server': + case "server": return ; default: return null; } }; - return (
@@ -46,26 +45,51 @@ const ConfigComponent = () => {
-
- {renderTabContent()} -
+
{renderTabContent()}
); }; - export const Config = () => { - return ( - - ) + return ; }; diff --git a/spotizerr-ui/src/routes/history.tsx b/spotizerr-ui/src/routes/history.tsx index 6b50a2c..9b585ef 100644 --- a/spotizerr-ui/src/routes/history.tsx +++ b/spotizerr-ui/src/routes/history.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState, useMemo } from 'react'; -import apiClient from '../lib/api-client'; -import { toast } from 'sonner'; +import { useEffect, useState, useMemo } from "react"; +import apiClient from "../lib/api-client"; +import { toast } from "sonner"; import { createColumnHelper, flexRender, @@ -8,51 +8,30 @@ import { useReactTable, getSortedRowModel, type SortingState, -} from '@tanstack/react-table'; +} from "@tanstack/react-table"; // --- Type Definitions --- type HistoryEntry = { + task_id: string; item_name: string; item_artist: string; - download_type: 'track' | 'album' | 'playlist' | 'artist'; + download_type: "track" | "album" | "playlist" | "artist"; service_used: string; quality_profile: string; - status_final: 'COMPLETED' | 'ERROR' | 'CANCELLED'; + convert_to?: string; + bitrate?: string; + status_final: "COMPLETED" | "ERROR" | "CANCELLED" | "SKIPPED"; timestamp_completed: number; error_message?: string; + parent_task_id?: string; + track_status?: "SUCCESSFUL" | "SKIPPED" | "FAILED"; + total_successful?: number; + total_skipped?: number; + total_failed?: number; }; // --- Column Definitions --- const columnHelper = createColumnHelper(); -const columns = [ - columnHelper.accessor('item_name', { header: 'Name' }), - columnHelper.accessor('item_artist', { header: 'Artist' }), - columnHelper.accessor('download_type', { header: 'Type', cell: info => {info.getValue()} }), - columnHelper.accessor('status_final', { - header: 'Status', - cell: info => { - const status = info.getValue(); - const statusClass = { - COMPLETED: 'text-green-500', - ERROR: 'text-red-500', - CANCELLED: 'text-yellow-500', - }[status]; - return {status}; - }, - }), - columnHelper.accessor('timestamp_completed', { - header: 'Date Completed', - cell: info => new Date(info.getValue() * 1000).toLocaleString(), - }), - columnHelper.accessor('error_message', { - header: 'Details', - cell: info => info.getValue() ? ( - - ) : null, - }) -]; export const History = () => { const [data, setData] = useState([]); @@ -60,39 +39,165 @@ export const History = () => { const [isLoading, setIsLoading] = useState(true); // State for TanStack Table - const [sorting, setSorting] = useState([{ id: 'timestamp_completed', desc: true }]); - const [{ pageIndex, pageSize }, setPagination] = useState({ pageIndex: 0, pageSize: 25 }); + const [sorting, setSorting] = useState([{ id: "timestamp_completed", desc: true }]); + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: 0, + pageSize: 25, + }); // State for filters - const [statusFilter, setStatusFilter] = useState(''); - const [typeFilter, setTypeFilter] = useState(''); + const [statusFilter, setStatusFilter] = useState(""); + const [typeFilter, setTypeFilter] = useState(""); + const [trackStatusFilter, setTrackStatusFilter] = useState(""); + const [hideChildTracks, setHideChildTracks] = useState(true); + const [parentTaskId, setParentTaskId] = useState(null); const pagination = useMemo(() => ({ pageIndex, pageSize }), [pageIndex, pageSize]); + const viewTracksForParent = (taskId: string) => { + setParentTaskId(taskId); + }; + + const columns = useMemo( + () => [ + columnHelper.accessor("item_name", { + header: "Name", + cell: (info) => + info.row.original.parent_task_id ? ( + └─ {info.getValue()} + ) : ( + {info.getValue()} + ), + }), + columnHelper.accessor("item_artist", { header: "Artist" }), + columnHelper.accessor("download_type", { + header: "Type", + cell: (info) => { + const entry = info.row.original; + if (entry.parent_task_id && entry.track_status) { + const statusClass = { + SUCCESSFUL: "text-green-500", + SKIPPED: "text-yellow-500", + FAILED: "text-red-500", + }[entry.track_status]; + return ( + {entry.track_status.toLowerCase()} + ); + } + return {info.getValue()}; + }, + }), + columnHelper.accessor("quality_profile", { + header: "Quality", + cell: (info) => { + const entry = info.row.original; + let qualityDisplay = entry.quality_profile || "N/A"; + + if (entry.convert_to && entry.convert_to !== "None") { + qualityDisplay = `${entry.convert_to.toUpperCase()}`; + if (entry.bitrate && entry.bitrate !== "None") { + qualityDisplay += ` ${entry.bitrate}k`; + } + qualityDisplay += ` (${entry.quality_profile || "Original"})`; + } else if (entry.bitrate && entry.bitrate !== "None") { + qualityDisplay = `${entry.bitrate}k (${entry.quality_profile || "Profile"})`; + } + return qualityDisplay; + }, + }), + columnHelper.accessor("status_final", { + header: "Status", + cell: (info) => { + const status = info.getValue(); + const statusClass = { + COMPLETED: "text-green-500", + ERROR: "text-red-500", + CANCELLED: "text-gray-500", + SKIPPED: "text-yellow-500", + }[status]; + return {status}; + }, + }), + columnHelper.accessor("timestamp_completed", { + header: "Date Completed", + cell: (info) => new Date(info.getValue() * 1000).toLocaleString(), + }), + columnHelper.accessor("error_message", { + header: "Details", + cell: (info) => + info.getValue() ? ( + + ) : null, + }), + columnHelper.display({ + id: "actions", + header: "Actions", + cell: ({ row }) => { + const entry = row.original; + if (!entry.parent_task_id && (entry.download_type === "album" || entry.download_type === "playlist")) { + const hasChildren = + (entry.total_successful ?? 0) > 0 || (entry.total_skipped ?? 0) > 0 || (entry.total_failed ?? 0) > 0; + if (hasChildren) { + return ( +
+ + + {entry.total_successful ?? 0} /{" "} + {entry.total_skipped ?? 0} /{" "} + {entry.total_failed ?? 0} + +
+ ); + } + } + return null; + }, + }), + ], + [], + ); + useEffect(() => { const fetchHistory = async () => { setIsLoading(true); try { const params = new URLSearchParams({ - limit: `${pageSize}`, - offset: `${pageIndex * pageSize}`, - sort_by: sorting[0]?.id ?? 'timestamp_completed', - sort_order: sorting[0]?.desc ? 'DESC' : 'ASC', + limit: `${pageSize}`, + offset: `${pageIndex * pageSize}`, + sort_by: sorting[0]?.id ?? "timestamp_completed", + sort_order: sorting[0]?.desc ? "DESC" : "ASC", }); - if (statusFilter) params.append('status_final', statusFilter); - if (typeFilter) params.append('download_type', typeFilter); + if (statusFilter) params.append("status_final", statusFilter); + if (typeFilter) params.append("download_type", typeFilter); + if (trackStatusFilter) params.append("track_status", trackStatusFilter); + if (hideChildTracks) params.append("hide_child_tracks", "true"); + if (parentTaskId) params.append("parent_task_id", parentTaskId); - const response = await apiClient.get<{ entries: HistoryEntry[], total_count: number }>(`/history?${params.toString()}`); + const response = await apiClient.get<{ + entries: HistoryEntry[]; + total_count: number; + }>(`/history?${params.toString()}`); setData(response.data.entries); setTotalEntries(response.data.total_count); } catch { - toast.error('Failed to load history.'); + toast.error("Failed to load history."); } finally { setIsLoading(false); } }; fetchHistory(); - }, [pageIndex, pageSize, sorting, statusFilter, typeFilter]); + }, [pageIndex, pageSize, sorting, statusFilter, typeFilter, trackStatusFilter, hideChildTracks, parentTaskId]); const table = useReactTable({ data, @@ -107,97 +212,161 @@ export const History = () => { manualSorting: true, }); + const clearFilters = () => { + setStatusFilter(""); + setTypeFilter(""); + setTrackStatusFilter(""); + setHideChildTracks(true); + }; + + const viewParentTask = () => { + setParentTaskId(null); + clearFilters(); + }; + return (

Download History

+ {parentTaskId && ( + + )} {/* Filter Controls */} -
- setStatusFilter(e.target.value)} + className="p-2 border rounded-md dark:bg-gray-800 dark:border-gray-700" + > + + + + + - setTypeFilter(e.target.value)} + className="p-2 border rounded-md dark:bg-gray-800 dark:border-gray-700" + > + + + + + + +
{/* Table */}
- - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} - + ))} - - + + {isLoading ? ( - + + + ) : table.getRowModel().rows.length === 0 ? ( - + + + ) : ( - table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - + {row.getVisibleCells().map((cell) => ( + + ))} - - )) + + ); + }) )} - +
+
{header.isPlaceholder ? null : ( -
+
{flexRender(header.column.columnDef.header, header.getContext())} - {{ asc: ' ▲', desc: ' ▼'}[header.column.getIsSorted() as string] ?? null} -
+ {{ asc: " ▲", desc: " ▼" }[header.column.getIsSorted() as string] ?? null} +
)} -
Loading...
+ Loading... +
No history entries found.
+ No history entries found. +
+ table.getRowModel().rows.map((row) => { + const isParent = + !row.original.parent_task_id && + (row.original.download_type === "album" || row.original.download_type === "playlist"); + const isChild = !!row.original.parent_task_id; + const rowClass = isParent ? "bg-gray-800 font-semibold" : isChild ? "bg-gray-900" : ""; + + return ( +
{flexRender(cell.column.columnDef.cell, cell.getContext())} -
{/* Pagination Controls */}
- - Page{' '} - - {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} - + Page{" "} + + {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + -
); -} +}; diff --git a/spotizerr-ui/src/routes/home.tsx b/spotizerr-ui/src/routes/home.tsx index 63a3c64..33bbd4d 100644 --- a/spotizerr-ui/src/routes/home.tsx +++ b/spotizerr-ui/src/routes/home.tsx @@ -1,44 +1,42 @@ -import { useState, useEffect, useMemo } from 'react'; -import { Link } from '@tanstack/react-router'; -import { useDebounce } from 'use-debounce'; -import apiClient from '../lib/api-client'; -import { useQueue } from '../contexts/queue-context'; +import { useState, useEffect, useMemo, useContext, useCallback } from "react"; +import { Link } from "@tanstack/react-router"; +import { useDebounce } from "use-debounce"; +import apiClient from "../lib/api-client"; +import { toast } from "sonner"; +import type { TrackType, AlbumType, ArtistType } from "../types/spotify"; +import { QueueContext } from "../contexts/queue-context"; -// --- Type Definitions --- -interface Image { url: string; } -interface BaseItem { id: string; name: string; } -interface Artist extends BaseItem { images?: Image[]; } -interface Album extends BaseItem { images?: Image[]; artists: Artist[]; } -interface Track extends BaseItem { album: Album; artists: Artist[]; } -interface Playlist extends BaseItem { images?: Image[]; owner: { display_name: string }; } +type SearchResult = (TrackType | AlbumType | ArtistType) & { + model: "track" | "album" | "artist"; +}; -type SearchResultItem = Artist | Album | Track | Playlist; -type SearchType = 'artist' | 'album' | 'track' | 'playlist'; - -// --- Component --- -export function Home() { - const [query, setQuery] = useState(''); - const [searchType, setSearchType] = useState('track'); - const [results, setResults] = useState([]); +export const Home = () => { + const [query, setQuery] = useState(""); + const [searchType, setSearchType] = useState<"track" | "album" | "artist">("track"); + const [results, setResults] = useState([]); const [isLoading, setIsLoading] = useState(false); const [debouncedQuery] = useDebounce(query, 500); - const { addItem, toggleVisibility } = useQueue(); + const context = useContext(QueueContext); + + if (!context) { + throw new Error("useQueue must be used within a QueueProvider"); + } + const { addItem } = context; useEffect(() => { const performSearch = async () => { - if (debouncedQuery.trim().length < 2) { + if (debouncedQuery.length < 3) { setResults([]); return; } setIsLoading(true); try { - const response = await apiClient.get<{ items: SearchResultItem[] }>('/search', { - params: { q: debouncedQuery, search_type: searchType, limit: 40 }, - }); - setResults(response.data.items); - } catch (error) { - console.error('Search failed:', error); - setResults([]); + const response = await apiClient.get<{ + results: SearchResult[]; + }>(`/search?q=${debouncedQuery}&type=${searchType}`); + setResults(response.data.results); + } catch { + toast.error("Search failed. Please try again."); } finally { setIsLoading(false); } @@ -46,105 +44,86 @@ export function Home() { performSearch(); }, [debouncedQuery, searchType]); - const handleDownloadTrack = (track: Track) => { - addItem({ id: track.id, type: 'track', name: track.name }); - toggleVisibility(); - }; + const handleDownloadTrack = useCallback( + (track: TrackType) => { + addItem({ spotifyId: track.id, type: "track", name: track.name }); + toast.info(`Adding ${track.name} to queue...`); + }, + [addItem], + ); - const renderResult = (item: SearchResultItem) => { + const resultComponent = useMemo(() => { switch (searchType) { - case 'track': { - const track = item as Track; + case "track": return ( -
- {track.album.name} -
-

{track.name}

-

{track.artists.map(a => a.name).join(', ')}

-
- +
+ {results.map( + (item) => + item.model === "track" && ( +
+ + {item.name} + + +
+ ), + )}
); - } - case 'album': { - const album = item as Album; + case "album": return ( - - {album.name} -

{album.name}

-

{album.artists.map(a => a.name).join(', ')}

- +
+ {results.map( + (item) => + item.model === "album" && ( +
+ + {item.name} +

{item.name}

+ +
+ ), + )} +
); - } - case 'artist': { - const artist = item as Artist; + case "artist": return ( - - {artist.name} -

{artist.name}

- +
+ {results.map( + (item) => + item.model === "artist" && ( +
+ +

{item.name}

+ +
+ ), + )} +
); - } - case 'playlist': { - const playlist = item as Playlist; - return ( - - {playlist.name} -

{playlist.name}

-

by {playlist.owner.display_name}

- - ); - } default: return null; } - }; - - const gridClass = useMemo(() => { - switch(searchType) { - case 'album': - case 'artist': - case 'playlist': - return "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4"; - case 'track': - return "flex flex-col gap-1"; - default: - return ""; - } - }, [searchType]); + }, [results, searchType, handleDownloadTrack]); return ( -
-
- +
+

Search Spotify

+
setQuery(e.target.value)} - placeholder="Search for songs, albums, artists..." - className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-700 rounded-full bg-gray-100 dark:bg-gray-800" + placeholder="Search for a track, album, or artist" /> - setSearchType(e.target.value as "track" | "album" | "artist")}> + + +
- -
- {isLoading &&

Loading...

} - {!isLoading && debouncedQuery && results.length === 0 &&

No results found.

} -
- {results.map(renderResult)} -
-
+ {isLoading &&

Loading...

} +
{resultComponent}
); -} +}; diff --git a/spotizerr-ui/src/routes/playlist.tsx b/spotizerr-ui/src/routes/playlist.tsx index 8f13237..644c74b 100644 --- a/spotizerr-ui/src/routes/playlist.tsx +++ b/spotizerr-ui/src/routes/playlist.tsx @@ -1,134 +1,106 @@ -import { Link, useParams } from '@tanstack/react-router'; -import { useEffect, useState } from 'react'; -import apiClient from '../lib/api-client'; -import { useQueue } from '../contexts/queue-context'; -import { useSettings } from '../contexts/settings-context'; -import { toast } from 'sonner'; -import type { ImageType, TrackType } from '../types/spotify'; +import { Link, useParams } from "@tanstack/react-router"; +import { useEffect, useState, useContext } from "react"; +import apiClient from "../lib/api-client"; +import { useSettings } from "../contexts/settings-context"; +import { toast } from "sonner"; +import type { ImageType, TrackType } from "../types/spotify"; +import { QueueContext } from "../contexts/queue-context"; -// --- Type Definitions --- -interface SimplifiedAlbumType { +interface PlaylistItemType { + track: TrackType | null; +} + +interface PlaylistType { id: string; name: string; - images: ImageType[]; -} - -interface PlaylistTrackType extends TrackType { - album: SimplifiedAlbumType; -} -interface PlaylistItemType { track: PlaylistTrackType | null; } - -interface PlaylistDetailsType { - id:string; - name: string; description: string | null; images: ImageType[]; - owner: { display_name?: string }; - followers?: { total: number }; - tracks: { items: PlaylistItemType[]; total: number; }; + tracks: { + items: PlaylistItemType[]; + }; } export const Playlist = () => { - const { playlistId } = useParams({ from: '/playlist/$playlistId' }); - const [playlist, setPlaylist] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const { addItem, toggleVisibility } = useQueue(); + const { playlistId } = useParams({ from: "/playlist/$playlistId" }); + const [playlist, setPlaylist] = useState(null); + const [error, setError] = useState(null); + const context = useContext(QueueContext); const { settings } = useSettings(); + if (!context) { + throw new Error("useQueue must be used within a QueueProvider"); + } + const { addItem } = context; + useEffect(() => { const fetchPlaylist = async () => { if (!playlistId) return; - setIsLoading(true); try { - const response = await apiClient.get(`/playlist/info?id=${playlistId}`); + const response = await apiClient.get(`/playlist/info?id=${playlistId}`); setPlaylist(response.data); - } catch { - toast.error('Failed to load playlist details.'); - } finally { - setIsLoading(false); + } catch (err) { + setError("Failed to load playlist"); + console.error(err); } }; fetchPlaylist(); }, [playlistId]); - const handleDownloadTrack = (track: PlaylistTrackType) => { - addItem({ id: track.id, type: 'track', name: track.name }); - toggleVisibility(); + const handleDownloadTrack = (track: TrackType) => { + if (!track?.id) return; + addItem({ spotifyId: track.id, type: "track", name: track.name }); + toast.info(`Adding ${track.name} to queue...`); }; const handleDownloadPlaylist = () => { - if (!playlist) return; - // This assumes a backend endpoint that can handle a whole playlist download by its ID - addItem({ id: playlist.id, type: 'playlist', name: playlist.name }); - toggleVisibility(); - toast.success(`Queued playlist: ${playlist.name}`); + if (!playlist) return; + addItem({ + spotifyId: playlist.id, + type: "playlist", + name: playlist.name, + }); + toast.info(`Adding ${playlist.name} to queue...`); + }; + + if (error) { + return
{error}
; } - if (isLoading) return
Loading playlist...
; - if (!playlist) return
Playlist not found.
; + if (!playlist) { + return
Loading...
; + } - const isExplicitFilterEnabled = settings?.explicitFilter ?? false; - const hasExplicitTrack = playlist.tracks.items.some(item => item.track?.explicit); + const filteredTracks = playlist.tracks.items.filter(({ track }) => { + if (!track) return false; + if (settings?.explicitFilter && track.explicit) return false; + return true; + }); return ( -
-
- {playlist.name} -
-

{playlist.name}

-

By {playlist.owner.display_name}

- {playlist.description &&

} -

{playlist.followers?.total.toLocaleString()} followers • {playlist.tracks.total} songs

-
- -
+
+
+ {playlist.name} +
+

{playlist.name}

+

{playlist.description}

+
- -
-
- {playlist.tracks.items.map(({ track }, index) => { - if (!track) return null; // Handle cases where a track might be unavailable - - if (isExplicitFilterEnabled && track.explicit) { - return ( -
- {index + 1} - Explicit track filtered -
- ); - } - - return ( -
- {index + 1} - -
-

{track.name}

-

- {track.artists.map(a => {a.name}).reduce((prev, curr) => <>{prev}, {curr})} - {' • '} - {track.album.name} -

-
- - {Math.floor(track.duration_ms / 60000)}:{((track.duration_ms % 60000) / 1000).toFixed(0).padStart(2, '0')} - - -
- ); - })} -
+
+ {filteredTracks.map(({ track }) => { + if (!track) return null; + return ( +
+ + {track.name} + + +
+ ); + })}
); -} +}; diff --git a/spotizerr-ui/src/routes/root.tsx b/spotizerr-ui/src/routes/root.tsx index 6966176..6a97813 100644 --- a/spotizerr-ui/src/routes/root.tsx +++ b/spotizerr-ui/src/routes/root.tsx @@ -1,11 +1,11 @@ -import { Outlet } from '@tanstack/react-router'; -import { QueueProvider } from '../contexts/QueueProvider'; -import { useQueue } from '../contexts/queue-context'; -import { Queue } from '../components/Queue'; -import { Link } from '@tanstack/react-router'; -import { SettingsProvider } from '../contexts/SettingsProvider'; -import { Toaster } from 'sonner'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Outlet } from "@tanstack/react-router"; +import { QueueProvider } from "../contexts/QueueProvider"; +import { useQueue } from "../contexts/queue-context"; +import { Queue } from "../components/Queue"; +import { Link } from "@tanstack/react-router"; +import { SettingsProvider } from "../contexts/SettingsProvider"; +import { Toaster } from "sonner"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // Create a client const queryClient = new QueryClient(); @@ -17,26 +17,26 @@ function AppLayout() { <>
-
- - Logo -

Spotizerr

+
+ + Logo +

Spotizerr

+ +
+ + Watchlist -
- - Watchlist - - - History - - - Settings - - -
-
+ + History + + + Settings + + +
+
diff --git a/spotizerr-ui/src/routes/track.tsx b/spotizerr-ui/src/routes/track.tsx index 483182e..2c96e11 100644 --- a/spotizerr-ui/src/routes/track.tsx +++ b/spotizerr-ui/src/routes/track.tsx @@ -1,92 +1,54 @@ -import { Link, useParams } from '@tanstack/react-router'; -import { useEffect, useState } from 'react'; -import apiClient from '../lib/api-client'; -import { useQueue } from '../contexts/queue-context'; -import type { TrackType, ImageType } from '../types/spotify'; - -interface SimplifiedAlbum { - id: string; - name: string; - images: ImageType[]; - album_type: string; -} - -interface TrackDetails extends TrackType { - album: SimplifiedAlbum; -} +import { useParams } from "@tanstack/react-router"; +import { useEffect, useState, useContext } from "react"; +import apiClient from "../lib/api-client"; +import type { TrackType } from "../types/spotify"; +import { toast } from "sonner"; +import { QueueContext } from "../contexts/queue-context"; export const Track = () => { - const { trackId } = useParams({ from: '/track/$trackId' }); - const [track, setTrack] = useState(null); + const { trackId } = useParams({ from: "/track/$trackId" }); + const [track, setTrack] = useState(null); const [error, setError] = useState(null); - const { addItem, toggleVisibility } = useQueue(); + const context = useContext(QueueContext); + + if (!context) { + throw new Error("useQueue must be used within a QueueProvider"); + } + const { addItem } = context; useEffect(() => { const fetchTrack = async () => { if (!trackId) return; try { - const response = await apiClient.get(`/track/info?id=${trackId}`); + const response = await apiClient.get(`/track/info?id=${trackId}`); setTrack(response.data); } catch (err) { - setError('Failed to load track details.'); + setError("Failed to load track"); console.error(err); } }; fetchTrack(); }, [trackId]); - const handleDownload = () => { + const handleDownloadTrack = () => { if (!track) return; - addItem({ id: track.id, type: 'track', name: track.name }); - toggleVisibility(); + addItem({ spotifyId: track.id, type: "track", name: track.name }); + toast.info(`Adding ${track.name} to queue...`); }; - if (error) return
{error}
; - if (!track) return
Loading...
; + if (error) { + return
{error}
; + } - const minutes = Math.floor(track.duration_ms / 60000); - const seconds = ((track.duration_ms % 60000) / 1000).toFixed(0).padStart(2, '0'); + if (!track) { + return
Loading...
; + } return ( -
- {track.album.name} -
-

{track.name}

-

- By{' '} - {track.artists.map((artist, index) => ( - - - {artist.name} - - {index < track.artists.length - 1 && ', '} - - ))} -

-

- From the {track.album.album_type}{' '} - - {track.album.name} - -

-
- {minutes}:{seconds} - {track.explicit && EXPLICIT} -
-
- -
-
+
+

{track.name}

+

by {track.artists.map((artist) => artist.name).join(", ")}

+
); }; diff --git a/spotizerr-ui/src/routes/watchlist.tsx b/spotizerr-ui/src/routes/watchlist.tsx index c057022..e0b0bad 100644 --- a/spotizerr-ui/src/routes/watchlist.tsx +++ b/spotizerr-ui/src/routes/watchlist.tsx @@ -1,8 +1,8 @@ -import { useState, useEffect, useCallback } from 'react'; -import apiClient from '../lib/api-client'; -import { toast } from 'sonner'; -import { useSettings } from '../contexts/settings-context'; -import { Link } from '@tanstack/react-router'; +import { useState, useEffect, useCallback } from "react"; +import apiClient from "../lib/api-client"; +import { toast } from "sonner"; +import { useSettings } from "../contexts/settings-context"; +import { Link } from "@tanstack/react-router"; // --- Type Definitions --- interface Image { @@ -10,7 +10,7 @@ interface Image { } interface WatchedArtist { - itemType: 'artist'; + itemType: "artist"; spotify_id: string; name: string; images?: Image[]; @@ -18,7 +18,7 @@ interface WatchedArtist { } interface WatchedPlaylist { - itemType: 'playlist'; + itemType: "playlist"; spotify_id: string; name: string; images?: Image[]; @@ -37,16 +37,16 @@ export const Watchlist = () => { setIsLoading(true); try { const [artistsRes, playlistsRes] = await Promise.all([ - apiClient.get[]>('/artist/watch/list'), - apiClient.get[]>('/playlist/watch/list'), + apiClient.get[]>("/artist/watch/list"), + apiClient.get[]>("/playlist/watch/list"), ]); - const artists: WatchedItem[] = artistsRes.data.map(a => ({ ...a, itemType: 'artist' })); - const playlists: WatchedItem[] = playlistsRes.data.map(p => ({ ...p, itemType: 'playlist' })); + const artists: WatchedItem[] = artistsRes.data.map((a) => ({ ...a, itemType: "artist" })); + const playlists: WatchedItem[] = playlistsRes.data.map((p) => ({ ...p, itemType: "playlist" })); setItems([...artists, ...playlists]); } catch { - toast.error('Failed to load watchlist.'); + toast.error("Failed to load watchlist."); } finally { setIsLoading(false); } @@ -61,35 +61,33 @@ export const Watchlist = () => { }, [settings, settingsLoading, fetchWatchlist]); const handleUnwatch = async (item: WatchedItem) => { - toast.promise( - apiClient.delete(`/${item.itemType}/watch/${item.spotify_id}`), { - loading: `Unwatching ${item.name}...`, - success: () => { - setItems(prev => prev.filter(i => i.spotify_id !== item.spotify_id)); - return `${item.name} has been unwatched.`; - }, - error: `Failed to unwatch ${item.name}.` + toast.promise(apiClient.delete(`/${item.itemType}/watch/${item.spotify_id}`), { + loading: `Unwatching ${item.name}...`, + success: () => { + setItems((prev) => prev.filter((i) => i.spotify_id !== item.spotify_id)); + return `${item.name} has been unwatched.`; + }, + error: `Failed to unwatch ${item.name}.`, }); }; const handleCheck = async (item: WatchedItem) => { - toast.promise( - apiClient.post(`/${item.itemType}/watch/trigger_check/${item.spotify_id}`), { - loading: `Checking ${item.name} for updates...`, - success: (res: { data: { message?: string }}) => res.data.message || `Check triggered for ${item.name}.`, - error: `Failed to trigger check for ${item.name}.`, + toast.promise(apiClient.post(`/${item.itemType}/watch/trigger_check/${item.spotify_id}`), { + loading: `Checking ${item.name} for updates...`, + success: (res: { data: { message?: string } }) => res.data.message || `Check triggered for ${item.name}.`, + error: `Failed to trigger check for ${item.name}.`, }); }; const handleCheckAll = () => { - toast.promise(Promise.all([ - apiClient.post('/artist/watch/trigger_check'), - apiClient.post('/playlist/watch/trigger_check'), - ]), { - loading: 'Triggering checks for all watched items...', - success: 'Successfully triggered checks for all items.', - error: 'Failed to trigger one or more checks.' - }); + toast.promise( + Promise.all([apiClient.post("/artist/watch/trigger_check"), apiClient.post("/playlist/watch/trigger_check")]), + { + loading: "Triggering checks for all watched items...", + success: "Successfully triggered checks for all items.", + error: "Failed to trigger one or more checks.", + }, + ); }; if (isLoading || settingsLoading) { @@ -101,7 +99,9 @@ export const Watchlist = () => {

Watchlist Disabled

The watchlist feature is currently disabled. You can enable it in the settings.

- Go to Settings + + Go to Settings +
); } @@ -120,28 +120,38 @@ export const Watchlist = () => {

Watched Artists & Playlists

- {items.map(item => ( + {items.map((item) => (
- - {item.name} -

{item.name}

-

{item.itemType}

-
-
- - -
+ + {item.name} +

{item.name}

+

{item.itemType}

+
+
+ + +
))}
); -} +}; diff --git a/spotizerr-ui/src/types/settings.ts b/spotizerr-ui/src/types/settings.ts index 9bc9005..7ef39c1 100644 --- a/spotizerr-ui/src/types/settings.ts +++ b/spotizerr-ui/src/types/settings.ts @@ -1,14 +1,14 @@ // This new type reflects the flat structure of the /api/config response export interface AppSettings { - service: 'spotify' | 'deezer'; + service: "spotify" | "deezer"; spotify: string; - spotifyQuality: 'NORMAL' | 'HIGH' | 'VERY_HIGH'; + spotifyQuality: "NORMAL" | "HIGH" | "VERY_HIGH"; deezer: string; - deezerQuality: 'MP3_128' | 'MP3_320' | 'FLAC'; + deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; maxConcurrentDownloads: number; realTime: boolean; fallback: boolean; - convertTo: 'MP3' | 'AAC' | 'OGG' | 'OPUS' | 'FLAC' | 'WAV' | 'ALAC' | ''; + convertTo: "MP3" | "AAC" | "OGG" | "OPUS" | "FLAC" | "WAV" | "ALAC" | ""; bitrate: string; maxRetries: number; retryDelaySeconds: number; diff --git a/spotizerr-ui/src/types/spotify.ts b/spotizerr-ui/src/types/spotify.ts index f8012fb..af60b5d 100644 --- a/spotizerr-ui/src/types/spotify.ts +++ b/spotizerr-ui/src/types/spotify.ts @@ -7,6 +7,7 @@ export interface ImageType { export interface ArtistType { id: string; name: string; + images: ImageType[]; } export interface TrackType { diff --git a/spotizerr-ui/tsconfig.json b/spotizerr-ui/tsconfig.json index 1ffef60..d32ff68 100644 --- a/spotizerr-ui/tsconfig.json +++ b/spotizerr-ui/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/spotizerr-ui/vite.config.ts b/spotizerr-ui/vite.config.ts index 49fc371..a96df9a 100644 --- a/spotizerr-ui/vite.config.ts +++ b/spotizerr-ui/vite.config.ts @@ -1,27 +1,26 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { fileURLToPath } from 'url' -import { dirname, resolve } from 'path' -import tailwindcss from '@tailwindcss/vite' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; +import tailwindcss from "@tailwindcss/vite"; - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { - '@': resolve(__dirname, './src'), + "@": resolve(__dirname, "./src"), }, }, server: { proxy: { - '/api': { - target: 'http://localhost:7171', + "/api": { + target: "http://localhost:7171", changeOrigin: true, }, }, }, -}) +});