From 40df89d6aae2791d5f7175be00dace7fa92e6ea7 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 20:11:42 +0100 Subject: [PATCH 01/19] setup frontend --- golem-ui-service/frontend/.gitignore | 24 + golem-ui-service/frontend/README.md | 50 + golem-ui-service/frontend/eslint.config.js | 28 + golem-ui-service/frontend/index.html | 13 + golem-ui-service/frontend/package-lock.json | 5793 ++++++++++++++++++ golem-ui-service/frontend/package.json | 45 + golem-ui-service/frontend/postcss.config.js | 6 + golem-ui-service/frontend/tailwind.config.js | 11 + golem-ui-service/frontend/tsconfig.app.json | 26 + golem-ui-service/frontend/tsconfig.json | 7 + 10 files changed, 6003 insertions(+) create mode 100644 golem-ui-service/frontend/.gitignore create mode 100644 golem-ui-service/frontend/README.md create mode 100644 golem-ui-service/frontend/eslint.config.js create mode 100644 golem-ui-service/frontend/index.html create mode 100644 golem-ui-service/frontend/package-lock.json create mode 100644 golem-ui-service/frontend/package.json create mode 100644 golem-ui-service/frontend/postcss.config.js create mode 100644 golem-ui-service/frontend/tailwind.config.js create mode 100644 golem-ui-service/frontend/tsconfig.app.json create mode 100644 golem-ui-service/frontend/tsconfig.json diff --git a/golem-ui-service/frontend/.gitignore b/golem-ui-service/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/golem-ui-service/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/golem-ui-service/frontend/README.md b/golem-ui-service/frontend/README.md new file mode 100644 index 000000000..74872fd4a --- /dev/null +++ b/golem-ui-service/frontend/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/golem-ui-service/frontend/eslint.config.js b/golem-ui-service/frontend/eslint.config.js new file mode 100644 index 000000000..092408a9f --- /dev/null +++ b/golem-ui-service/frontend/eslint.config.js @@ -0,0 +1,28 @@ +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' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/golem-ui-service/frontend/index.html b/golem-ui-service/frontend/index.html new file mode 100644 index 000000000..e4b78eae1 --- /dev/null +++ b/golem-ui-service/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/golem-ui-service/frontend/package-lock.json b/golem-ui-service/frontend/package-lock.json new file mode 100644 index 000000000..195dc26ab --- /dev/null +++ b/golem-ui-service/frontend/package-lock.json @@ -0,0 +1,5793 @@ +{ + "name": "golem-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "golem-ui", + "version": "0.1.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.0.0", + "@radix-ui/react-tabs": "^1.0.0", + "@tanstack/react-query": "^5.0.0", + "axios": "^1.6.0", + "date-fns": "^4.1.0", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", + "react-router-dom": "^6.8.0", + "zustand": "^4.5.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@tanstack/react-query-devtools": "^5.62.7", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^8.18.0", + "@typescript-eslint/parser": "^8.18.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.0", + "eslint": "^9.15.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.15.0", + "vite": "^6.0.1", + "vitest": "^2.1.8" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.2", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.21.0", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.28.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.62.7", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.61.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.61.4.tgz", + "integrity": "sha512-21Tw+u8E3IJJj4A/Bct4H0uBaDTEu7zBrR79FeSyY+mS2gx5/m316oDtJiKkILc819VSTYt+sFzODoJNcpPqZQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.62.7", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.62.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.62.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.62.7.tgz", + "integrity": "sha512-wxXsdTZJRs//hMtJMU5aNlUaTclRFPqLvDNeWbRj8YpGD3aoo4zyu53W55W2DY16+ycg3fti21uCW4N9oyj91w==", + "dev": true, + "dependencies": { + "@tanstack/query-devtools": "5.61.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.62.7", + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.16", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/type-utils": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.18.0", + "dev": true, + "license": "MITClause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.8", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/autoprefixer/node_modules/caniuse-lite": { + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/axios": { + "version": "1.7.9", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserslist/node_modules/caniuse-lite": { + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/chai": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.73", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.0", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" + } + }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.16", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.13.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/magic-string": { + "version": "0.30.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "license": "ISC" + }, + "node_modules/pathe": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.0", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.28.0", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.28.0", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.28.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", + "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.18.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.18.0", + "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/utils": "8.18.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@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": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.4.11", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "5.4.11", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.6.1", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "4.5.5", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/golem-ui-service/frontend/package.json b/golem-ui-service/frontend/package.json new file mode 100644 index 000000000..3faf2cc2a --- /dev/null +++ b/golem-ui-service/frontend/package.json @@ -0,0 +1,45 @@ +{ + "name": "golem-ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint src --ext ts,tsx", + "test": "vitest" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.0.0", + "@radix-ui/react-tabs": "^1.0.0", + "@tanstack/react-query": "^5.0.0", + "axios": "^1.6.0", + "date-fns": "^4.1.0", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", + "react-router-dom": "^6.8.0", + "zustand": "^4.5.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@tanstack/react-query-devtools": "^5.62.7", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^8.18.0", + "@typescript-eslint/parser": "^8.18.0", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.0", + "eslint": "^9.15.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.15.0", + "vite": "^6.0.1", + "vitest": "^2.1.8" + } +} diff --git a/golem-ui-service/frontend/postcss.config.js b/golem-ui-service/frontend/postcss.config.js new file mode 100644 index 000000000..33ad091d2 --- /dev/null +++ b/golem-ui-service/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/golem-ui-service/frontend/tailwind.config.js b/golem-ui-service/frontend/tailwind.config.js new file mode 100644 index 000000000..89a305e02 --- /dev/null +++ b/golem-ui-service/frontend/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git a/golem-ui-service/frontend/tsconfig.app.json b/golem-ui-service/frontend/tsconfig.app.json new file mode 100644 index 000000000..11c783af6 --- /dev/null +++ b/golem-ui-service/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + }, + "include": ["src", "src/App.tsx"] +} diff --git a/golem-ui-service/frontend/tsconfig.json b/golem-ui-service/frontend/tsconfig.json new file mode 100644 index 000000000..1ffef600d --- /dev/null +++ b/golem-ui-service/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} From c21b53d4b0fbf6f5d337c0cbe8daf205d7be44a2 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 20:12:35 +0100 Subject: [PATCH 02/19] setup axios connectors --- .../frontend/src/api/api-definitions.ts | 127 ++++++++++ .../frontend/src/api/components.ts | 137 +++++++++++ golem-ui-service/frontend/src/api/plugins.ts | 211 ++++++++++++++++ golem-ui-service/frontend/src/api/workers.ts | 231 ++++++++++++++++++ golem-ui-service/frontend/src/types/api.ts | 169 +++++++++++++ golem-ui-service/frontend/src/types/error.ts | 25 ++ 6 files changed, 900 insertions(+) create mode 100644 golem-ui-service/frontend/src/api/api-definitions.ts create mode 100644 golem-ui-service/frontend/src/api/components.ts create mode 100644 golem-ui-service/frontend/src/api/plugins.ts create mode 100644 golem-ui-service/frontend/src/api/workers.ts create mode 100644 golem-ui-service/frontend/src/types/api.ts create mode 100644 golem-ui-service/frontend/src/types/error.ts diff --git a/golem-ui-service/frontend/src/api/api-definitions.ts b/golem-ui-service/frontend/src/api/api-definitions.ts new file mode 100644 index 000000000..bab361e36 --- /dev/null +++ b/golem-ui-service/frontend/src/api/api-definitions.ts @@ -0,0 +1,127 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { ApiDefinition } from '../types/api'; +import { apiClient } from '../lib/api-client'; + +// Query keys +export const apiDefinitionKeys = { + all: ['api-definitions'] as const, + lists: () => [...apiDefinitionKeys.all, 'list'] as const, + list: (filters: Record) => [...apiDefinitionKeys.lists(), filters] as const, + details: () => [...apiDefinitionKeys.all, 'detail'] as const, + detail: (id: string, version: string) => [...apiDefinitionKeys.details(), id, version] as const, +}; + +// API Functions +export const getApiDefinitions = async (apiDefinitionId?: string) => { + const { data } = await apiClient.get('/v1/api/definitions', { + params: { 'api-definition-id': apiDefinitionId } + }); + return data; +}; + +export const getApiDefinition = async (id: string, version: string) => { + const { data } = await apiClient.get( + `/v1/api/definitions/${id}/${version}` + ); + return data; +}; + +export const createApiDefinition = async (definition: Omit) => { + const { data } = await apiClient.post('/v1/api/definitions', definition); + return data; +}; + +export const updateApiDefinition = async ({ + id, + version, + definition +}: { + id: string; + version: string; + definition: Partial; +}) => { + const { data } = await apiClient.put( + `/v1/api/definitions/${id}/${version}`, + definition + ); + return data; +}; + +export const deleteApiDefinition = async (id: string, version: string) => { + const { data } = await apiClient.delete(`/v1/api/definitions/${id}/${version}`); + return data; +}; + +export const importOpenApiDefinition = async (openApiDoc: any) => { + const { data } = await apiClient.put( + '/v1/api/definitions/import', + openApiDoc + ); + return data; +}; + +// Hooks +export const useApiDefinitions = (apiDefinitionId?: string) => { + return useQuery({ + queryKey: apiDefinitionKeys.list({ apiDefinitionId }), + queryFn: () => getApiDefinitions(apiDefinitionId), + }); +}; + +export const useApiDefinition = (id: string, version: string) => { + return useQuery({ + queryKey: apiDefinitionKeys.detail(id, version), + queryFn: () => getApiDefinition(id, version), + }); +}; + +export const useCreateApiDefinition = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createApiDefinition, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: apiDefinitionKeys.lists() }); + }, + }); +}; + +export const useUpdateApiDefinition = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: updateApiDefinition, + onSuccess: (_, { id, version }) => { + queryClient.invalidateQueries({ + queryKey: apiDefinitionKeys.detail(id, version) + }); + queryClient.invalidateQueries({ + queryKey: apiDefinitionKeys.lists() + }); + }, + }); +}; + +export const useDeleteApiDefinition = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, version }: { id: string; version: string }) => + deleteApiDefinition(id, version), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: apiDefinitionKeys.lists() }); + }, + }); +}; + +export const useImportOpenApiDefinition = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: importOpenApiDefinition, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: apiDefinitionKeys.lists() }); + }, + }); +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/api/components.ts b/golem-ui-service/frontend/src/api/components.ts new file mode 100644 index 000000000..2553a4169 --- /dev/null +++ b/golem-ui-service/frontend/src/api/components.ts @@ -0,0 +1,137 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { Component } from "../types/api"; +import { apiClient } from "../lib/api-client"; + +// Query keys +export const componentKeys = { + all: ["components"] as const, + lists: () => [...componentKeys.all, "list"] as const, + list: (filters: Record) => + [...componentKeys.lists(), filters] as const, + details: () => [...componentKeys.all, "detail"] as const, + detail: (id: string) => [...componentKeys.details(), id] as const, + versions: (id: string) => [...componentKeys.detail(id), "versions"] as const, +}; + +// API functions +export const getComponents = async (componentName?: string) => { + const { data } = await apiClient.get("/v1/components", { + params: { "component-name": componentName }, + }); + return data; +}; + +export const getComponentVersions = async (componentId: string) => { + const { data } = await apiClient.get( + `/v1/components/${componentId}` + ); + return data; +}; + +export const deleteComponent = async (componentId: string) => { + const { data } = await apiClient.delete(`/v1/components/${componentId}`); + return data; +}; + +export const getComponentVersion = async ( + componentId: string, + version: number +) => { + const { data } = await apiClient.get( + `/v1/components/${componentId}/versions/${version}` + ); + return data; +}; + +export const createComponent = async (formData: FormData) => { + const { data } = await apiClient.post("/v1/components", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + return data; +}; + +export const updateComponent = async ({ + componentId, + formData, +}: { + componentId: string; + formData: FormData; +}) => { + const { data } = await apiClient.post( + `/v1/components/${componentId}/updates`, + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + return data; +}; + +// Hooks +export const useComponents = (componentName?: string) => { + return useQuery({ + queryKey: componentKeys.list({ componentName }), + queryFn: () => getComponents(componentName), + }); +}; + +export const useComponentVersions = (componentId: string) => { + return useQuery({ + queryKey: componentKeys.versions(componentId), + queryFn: () => getComponentVersions(componentId), + }); +}; + +export const useCreateComponent = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createComponent, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: componentKeys.lists() }); + }, + }); +}; + +export const useUpdateComponent = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: updateComponent, + onSuccess: (_, { componentId }) => { + queryClient.invalidateQueries({ + queryKey: componentKeys.detail(componentId), + }); + }, + }); +}; +export const useDeleteComponent = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (params: { id: string }) => deleteComponent(params.id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: componentKeys.lists() }); + }, + }); +}; + +export const useComponent = (componentId: string) => { + return useQuery({ + queryKey: componentKeys.detail(componentId), + queryFn: () => getComponent(componentId), + enabled: !!componentId, // Only run if componentId is provided + }); +}; + +export const getComponent = async (componentId: string) => { + const { data } = await apiClient.get( + `/v1/components/${componentId}/latest` + ); + return data; +}; diff --git a/golem-ui-service/frontend/src/api/plugins.ts b/golem-ui-service/frontend/src/api/plugins.ts new file mode 100644 index 000000000..fd83be50b --- /dev/null +++ b/golem-ui-service/frontend/src/api/plugins.ts @@ -0,0 +1,211 @@ +import { InstalledPlugin, Plugin } from '../types/api'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { apiClient } from '../lib/api-client'; + +// Query keys +export const pluginKeys = { + all: ['plugins'] as const, + lists: () => [...pluginKeys.all, 'list'] as const, + list: (scope?: string) => [...pluginKeys.lists(), { scope }] as const, + details: () => [...pluginKeys.all, 'detail'] as const, + detail: (name: string) => [...pluginKeys.details(), name] as const, + version: (name: string, version: string) => [...pluginKeys.detail(name), version] as const, + installs: (componentId: string, version: number) => + ['components', componentId, 'versions', version, 'plugins', 'installs'] as const, +}; + +// API Types +export interface CreatePluginPayload { + name: string; + version: string; + description: string; + icon?: number[]; + homepage: string; + specs: { + type: 'ComponentTransformer'; + providedWitPackage: string; + jsonSchema: string; + validateUrl: string; + transformUrl: string; + }; + scope: { + type: 'Global'; + }; +} + +export interface InstallPluginPayload { + name: string; + version: string; + priority: number; + parameters: Record; +} + +export interface UpdatePluginInstallPayload { + priority: number; + parameters: Record; +} + +// Plugin API Functions +export const getPlugins = async (scope?: string) => { + const { data } = await apiClient.get('/v1/plugins', { + params: { scope }, + }); + return data; +}; + +export const getPluginVersions = async (name: string) => { + const { data } = await apiClient.get(`/v1/plugins/${name}`); + return data; +}; + +export const getPluginVersion = async (name: string, version: string) => { + const { data } = await apiClient.get(`/v1/plugins/${name}/${version}`); + return data; +}; + +export const createPlugin = async (payload: CreatePluginPayload) => { + const { data } = await apiClient.post('/v1/plugins', payload); + return data; +}; + +export const deletePlugin = async (name: string, version: string) => { + const { data } = await apiClient.delete(`/v1/plugins/${name}/${version}`); + return data; +}; + +// Component Plugin Installation Functions +export const getInstalledPlugins = async (componentId: string, version: number) => { + const { data } = await apiClient.get( + `/v1/components/${componentId}/versions/${version}/plugins/installs` + ); + return data; +}; + +export const installPlugin = async ( + componentId: string, + payload: InstallPluginPayload +) => { + const { data } = await apiClient.post( + `/v1/components/${componentId}/latest/plugins/installs`, + payload + ); + return data; +}; + +export const updatePluginInstallation = async ( + componentId: string, + installationId: string, + payload: UpdatePluginInstallPayload +) => { + const { data } = await apiClient.put( + `/v1/components/${componentId}/versions/latest/plugins/installs/${installationId}`, + payload + ); + return data; +}; + +export const uninstallPlugin = async ( + componentId: string, + installationId: string +) => { + const { data } = await apiClient.delete( + `/v1/components/${componentId}/latest/plugins/installs/${installationId}` + ); + return data; +}; + +// Hooks for Plugin Management +export const usePlugins = (scope?: string) => { + return useQuery({ + queryKey: pluginKeys.list(scope), + queryFn: () => getPlugins(scope), + }); +}; + +export const usePluginVersions = (name: string) => { + return useQuery({ + queryKey: pluginKeys.detail(name), + queryFn: () => getPluginVersions(name), + }); +}; + +export const usePluginVersion = (name: string, version: string) => { + return useQuery({ + queryKey: pluginKeys.version(name, version), + queryFn: () => getPluginVersion(name, version), + }); +}; + +export const useCreatePlugin = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createPlugin, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: pluginKeys.lists() }); + }, + }); +}; + +export const useDeletePlugin = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ name, version }: { name: string; version: string }) => + deletePlugin(name, version), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: pluginKeys.lists() }); + }, + }); +}; + +// Hooks for Component Plugin Installation +export const useInstalledPlugins = (componentId: string, version: number) => { + return useQuery({ + queryKey: pluginKeys.installs(componentId, version), + queryFn: () => getInstalledPlugins(componentId, version), + }); +}; + +export const useInstallPlugin = (componentId: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (payload: InstallPluginPayload) => installPlugin(componentId, payload), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: pluginKeys.installs(componentId, 'latest') + }); + }, + }); +}; + +export const useUpdatePluginInstallation = (componentId: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ installationId, payload }: { + installationId: string; + payload: UpdatePluginInstallPayload; + }) => updatePluginInstallation(componentId, installationId, payload), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: pluginKeys.installs(componentId, 'latest') + }); + }, + }); +}; + +export const useUninstallPlugin = (componentId: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (installationId: string) => uninstallPlugin(componentId, installationId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: pluginKeys.installs(componentId, 'latest') + }); + }, + }); +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/api/workers.ts b/golem-ui-service/frontend/src/api/workers.ts new file mode 100644 index 000000000..1ec9e2902 --- /dev/null +++ b/golem-ui-service/frontend/src/api/workers.ts @@ -0,0 +1,231 @@ +import { + UseMutationOptions, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; +import { Worker, WorkerStatus } from "../types/api"; + +import { GolemError } from "../types/error"; +import { apiClient } from "../lib/api-client"; + +// Query keys +export const workerKeys = { + all: ["workers"] as const, + lists: () => [...workerKeys.all, "list"] as const, + list: (componentId: string, filters: Record) => + [...workerKeys.lists(), componentId, filters] as const, + details: () => [...workerKeys.all, "detail"] as const, + detail: (componentId: string, workerName: string) => + [...workerKeys.details(), componentId, workerName] as const, +}; + +interface WorkerFilter { + type: "Name" | "Version" | "Status" | "Env" | "CreatedAt"; + comparator: string; + value: string | number | WorkerStatus; + name?: string; // For Env filter +} + +interface WorkerListResponse { + workers: Worker[]; + cursor?: { + cursor: number; + layer: number; + }; +} + +// API functions +export const getWorkers = async ( + componentId: string, + filter?: WorkerFilter[], + cursor?: string, + count?: number +) => { + const { data } = await apiClient.get( + `/v1/components/${componentId}/workers`, + { + params: { filter, cursor, count }, + } + ); + return data; +}; + +export const getWorker = async (componentId: string, workerName: string) => { + const { data } = await apiClient.get( + `/v1/components/${componentId}/workers/${workerName}` + ); + return data; +}; + +export interface CreateWorkerPayload { + name: string; + args?: string[]; + env?: Record; +} + +export const createWorker = async ( + componentId: string, + payload: CreateWorkerPayload +) => { + const { data } = await apiClient.post( + `/v1/components/${componentId}/workers`, + payload + ); + return data; +}; + +export const deleteWorker = async (componentId: string, workerName: string) => { + const { data } = await apiClient.delete( + `/v1/components/${componentId}/workers/${workerName}` + ); + return data; +}; + +export const interruptWorker = async (workerId: { + componentId: string; + workerName: string; + recoverImmediately?: boolean; +}) => { + const { componentId, workerName, recoverImmediately } = workerId; + const { data } = await apiClient.post( + `/v1/components/${componentId}/workers/${workerName}/interrupt`, + null, + { + params: { "recovery-immediately": recoverImmediately }, + } + ); + return data; +}; + +export const resumeWorker = async (workerId: { + componentId: string; + workerName: string; + recoverImmediately?: boolean; +}) => { + const { data } = await apiClient.post( + `/v1/components/${workerId.componentId}/workers/${workerId.workerName}/resume` + ); + return data; +}; + +// Hooks +export const useWorkers = ( + componentId: string, + filter?: WorkerFilter[], + cursor?: string, + count?: number +) => { + return useQuery({ + queryKey: workerKeys.list(componentId, { filter, cursor, count }), + queryFn: () => getWorkers(componentId, filter, cursor, count), + }); +}; + +export const useWorker = (componentId: string, workerName: string) => { + return useQuery({ + queryKey: workerKeys.detail(componentId, workerName), + queryFn: () => getWorker(componentId, workerName), + }); +}; + +export const useCreateWorker = (componentId: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (payload: CreateWorkerPayload) => + createWorker(componentId, payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: workerKeys.lists() }); + }, + }); +}; + +export const useDeleteWorker = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + componentId, + workerName, + }: { + componentId: string; + workerName: string; + }) => deleteWorker(componentId, workerName), + onSuccess: (_, { componentId }) => { + queryClient.invalidateQueries({ queryKey: workerKeys.lists() }); + }, + }); +}; + +interface InterruptWorkerParams { + componentId: string; + workerName: string; + recoverImmediately?: boolean; +} + +export const useInterruptWorker = ( + options?: UseMutationOptions +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: interruptWorker, + onSuccess: ( + _, + { + componentId, + workerName, + }: { + componentId: string; + workerName: string; + } + ) => { + // Invalidate specific worker query + queryClient.invalidateQueries({ + queryKey: workerKeys.detail(componentId, workerName), + }); + + // Invalidate worker list for the component + queryClient.invalidateQueries({ + queryKey: workerKeys.lists(), + }); + }, + ...options, + }); +}; + +export const useResumeWorker = ( + options?: UseMutationOptions< + void, + GolemError, + { componentId: string; workerName: string } + > +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: resumeWorker, + onSuccess: ( + _, + { + componentId, + workerName, + }: { + componentId: string; + workerName: string; + } + ) => { + // Invalidate specific worker query + queryClient.invalidateQueries({ + queryKey: workerKeys.detail(componentId, workerName), + }); + + // Invalidate worker list for the component + queryClient.invalidateQueries({ + queryKey: workerKeys.lists(), + }); + }, + ...options, + }); +}; diff --git a/golem-ui-service/frontend/src/types/api.ts b/golem-ui-service/frontend/src/types/api.ts new file mode 100644 index 000000000..76099b864 --- /dev/null +++ b/golem-ui-service/frontend/src/types/api.ts @@ -0,0 +1,169 @@ +// Component Types +export interface VersionedComponentId { + componentId: string; + version: number; + } + + export interface ComponentMetadata { + exports: ComponentExport[]; + producers: Producer[]; + memories: Memory[]; + } + + export interface ComponentExport { + type: 'Function'; + name: string; + parameters: Parameter[]; + results: Result[]; + } + + export interface Parameter { + name: string; + typ: TypeDefinition; + } + + export interface Result { + name: string; + typ: TypeDefinition; + } + + export interface TypeDefinition { + type: 'Variant'; + cases: Array<{ + name: string; + typ: Record; + }>; + } + + export interface Producer { + fields: Array<{ + name: string; + values: Array<{ + name: string; + version: string; + }>; + }>; + } + + export interface Memory { + initial: number; + maximum: number; + } + + export interface ComponentFile { + key: string; + path: string; + permissions: 'read-only' | 'read-write'; + } + + export interface InstalledPlugin { + id: string; + name: string; + version: string; + priority: number; + parameters: Record; + } + + export interface Component { + versionedComponentId: VersionedComponentId; + componentName: string; + componentSize: number; + metadata: ComponentMetadata; + createdAt: string; + componentType: 'Durable' | 'Ephemeral'; + files: ComponentFile[]; + installedPlugins: InstalledPlugin[]; + } + + // Worker Types + export interface WorkerId { + componentId: string; + workerName: string; + } + + export type WorkerStatus = 'Running' | 'Idle' | 'Suspended' | 'Interrupted' | 'Retrying' | 'Failed' | 'Exited'; + + export interface WorkerUpdate { + type: 'pendingUpdate'; + timestamp: string; + targetVersion: number; + } + + export interface WorkerResource { + createdAt: string; + indexed: { + resourceName: string; + resourceParams: string[]; + }; + } + + export interface Worker { + workerId: WorkerId; + args: string[]; + env: Record; + status: WorkerStatus; + componentVersion: number; + retryCount: number; + pendingInvocationCount: number; + updates: WorkerUpdate[]; + createdAt: string; + lastError?: string; + componentSize: number; + totalLinearMemorySize: number; + ownedResources: Record; + activePlugins: string[]; + } + + // API Definition Types + export interface ApiRoute { + method: string; + path: string; + security?: string; + binding: { + componentId: VersionedComponentId; + workerName: string; + idempotencyKey?: string; + response?: string; + bindingType: 'default'; + responseMappingInput?: Record; + workerNameInput?: Record; + idempotencyKeyInput?: Record; + corsPreflight?: { + allowOrigin: string; + allowMethods: string; + allowHeaders: string; + exposeHeaders: string; + allowCredentials: boolean; + maxAge: number; + }; + responseMappingOutput?: Record; + }; + } + + export interface ApiDefinition { + id: string; + version: string; + routes: ApiRoute[]; + draft: boolean; + createdAt: string; + } + + // Plugin Types + export interface Plugin { + name: string; + version: string; + description: string; + icon: number[]; + homepage: string; + specs: { + type: 'ComponentTransformer'; + providedWitPackage: string; + jsonSchema: string; + validateUrl: string; + transformUrl: string; + }; + scope: { + type: 'Global'; + }; + owner?: Record; + } \ No newline at end of file diff --git a/golem-ui-service/frontend/src/types/error.ts b/golem-ui-service/frontend/src/types/error.ts new file mode 100644 index 000000000..8ed6a42a4 --- /dev/null +++ b/golem-ui-service/frontend/src/types/error.ts @@ -0,0 +1,25 @@ +export interface GolemError { + error?: string; + errors?: string[]; + type?: string; + golemError?: { + type: string; + details: string; + }; + } + + export const getErrorMessage = (error: GolemError): string => { + if (error.golemError) { + return `${error.golemError.type}: ${error.golemError.details}`; + } + + if (error.errors?.length) { + return error.errors.join(', '); + } + + if (error.error) { + return error.error; + } + + return 'An unknown error occurred'; + }; \ No newline at end of file From 1c3494d3e15978036a3b2b85719f61590a2d540f Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 20:13:16 +0100 Subject: [PATCH 03/19] setum axum server and proxy --- golem-ui-service/src/lib.rs | 207 +++++++++++++++++++++++++++++++++++ golem-ui-service/src/main.rs | 16 +++ 2 files changed, 223 insertions(+) create mode 100644 golem-ui-service/src/lib.rs create mode 100644 golem-ui-service/src/main.rs diff --git a/golem-ui-service/src/lib.rs b/golem-ui-service/src/lib.rs new file mode 100644 index 000000000..1add97a7b --- /dev/null +++ b/golem-ui-service/src/lib.rs @@ -0,0 +1,207 @@ +use axum::{ + body::{Body, Bytes}, + extract::State, + http::{header, HeaderMap, Method, Request, StatusCode, Uri}, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use clap::Parser; +use hyper::{client::HttpConnector, Client}; +use mime_guess::from_path; +use rust_embed::RustEmbed; +use std::{net::SocketAddr, sync::Arc}; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::TraceLayer, +}; +use tracing::error; + +// CLI Arguments +#[derive(Parser, Debug)] +#[command(author, version, about)] +pub struct CliArgs { + /// Server port + #[arg(long, default_value = "3000")] + pub port: u16, + + /// Server host + #[arg(long, default_value = "127.0.0.1")] + pub host: String, + + /// API base URL + #[arg(long, default_value = "http://localhost:9881")] + pub api_url: String, + + /// Development mode (uses CORS for Vite dev server) + #[arg(long)] + pub dev: bool, +} + +// Embed the UI dist folder into the binary +#[derive(RustEmbed)] +#[folder = "frontend/dist"] +struct Assets; + +#[derive(Clone)] +struct AppState { + client: Client, + api_url: String, +} + +pub struct UiService { + addr: SocketAddr, + api_url: String, + dev_mode: bool, +} + +impl UiService { + pub fn new(args: CliArgs) -> Self { + let addr = SocketAddr::new(args.host.parse().expect("Invalid host address"), args.port); + Self { + addr, + api_url: args.api_url, + dev_mode: args.dev, + } + } + + async fn serve_index() -> impl IntoResponse { + match Assets::get("index.html") { + Some(content) => Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/html") + .body(Body::from(content.data)) + .unwrap() + .into_response(), + None => { + error!("index.html not found in embedded assets"); + (StatusCode::NOT_FOUND, "Not found").into_response() + } + } + } + + async fn serve_static(uri: Uri) -> impl IntoResponse { + let path = uri.path().trim_start_matches('/'); + + // Special case for assets directory + let asset_path = if path.starts_with("assets/") { + path.to_string() + } else { + format!("assets/{}", path) + }; + + match Assets::get(&asset_path) { + Some(content) => { + let mime = from_path(&asset_path).first_or_octet_stream(); + Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, mime.as_ref()) + .header(header::CACHE_CONTROL, "public, max-age=3600") + .body(Body::from(content.data)) + .unwrap() + .into_response() + } + None => { + error!("Asset not found: {}", asset_path); + (StatusCode::NOT_FOUND, "Not found").into_response() + } + } + } + + async fn proxy_handler( + State(state): State>, + method: Method, + uri: Uri, + headers: HeaderMap, + body: Bytes, + ) -> Result, StatusCode> { + // Reconstruct the URI for the backend API + let path_and_query = uri.path_and_query().map(|x| x.as_str()).unwrap_or(""); + + let backend_uri = format!( + "{}{}", + state.api_url, + path_and_query + .strip_prefix("/api") + .unwrap_or(path_and_query) + ) + .parse::() + .map_err(|e| { + error!("Failed to parse backend URI: {}", e); + StatusCode::BAD_REQUEST + })?; + + // Create the proxied request + let mut req = Request::builder() + .uri(backend_uri.clone()) + .method(method.clone()); + + // Forward relevant headers + for (key, value) in headers.iter() { + if key != "host" { + req = req.header(key, value); + } + } + + let req = req.body(Body::from(body)).map_err(|e| { + error!("Failed to create proxy request: {}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Send the request to the backend + let response = state.client.request(req).await.map_err(|e| { + error!("Proxy request failed: {}", e); + StatusCode::BAD_GATEWAY + })?; + + Ok(response) + } + + pub async fn run(&self) -> Result<(), Box> { + // Configure CORS based on mode + let cors = if self.dev_mode { + CorsLayer::new() + .allow_origin(["http://localhost:5173".parse()?]) + .allow_methods(Any) + .allow_headers(["content-type".parse()?, "authorization".parse()?]) + } else { + CorsLayer::new() + .allow_methods(Any) + .allow_headers(["content-type".parse()?, "authorization".parse()?]) + }; + + let state = Arc::new(AppState { + client: Client::new(), + api_url: self.api_url.clone(), + }); + + let app = Router::new() + // API proxy route + .route( + "/api/*path", + get(Self::proxy_handler) + .post(Self::proxy_handler) + .put(Self::proxy_handler) + .delete(Self::proxy_handler) + .patch(Self::proxy_handler), + ) + // Static assets route + .route("/assets/*path", get(Self::serve_static)) + // SPA fallback + .fallback(get(Self::serve_index)) + .with_state(state) + .layer(TraceLayer::new_for_http()) + .layer(cors); + + println!("UI Service listening on {}", self.addr); + println!("API proxy configured for {}", self.api_url); + if self.dev_mode { + println!("Running in development mode"); + } + axum::Server::bind(&self.addr) + .serve(app.into_make_service_with_connect_info::()) + .await?; + + Ok(()) + } +} diff --git a/golem-ui-service/src/main.rs b/golem-ui-service/src/main.rs new file mode 100644 index 000000000..92062285f --- /dev/null +++ b/golem-ui-service/src/main.rs @@ -0,0 +1,16 @@ +use clap::Parser; +use golem_ui_service::CliArgs; +use golem_ui_service::UiService; +#[tokio::main] +pub async fn main() { + let args = CliArgs::parse(); + + let res = UiService::new(args) + .run() + .await; + + match res { + Err(e) => println!("Error starting UI: {}", e), + Ok(_) => println!("UI Server stopped"), + } +} \ No newline at end of file From f9bb8c2aec182f0d81e972b905c3c03b6df8a1cd Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 20:13:50 +0100 Subject: [PATCH 04/19] cargo dependencies --- Cargo.lock | 281 ++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 +- golem-ui-service/Cargo.toml | 27 ++++ 3 files changed, 295 insertions(+), 16 deletions(-) create mode 100644 golem-ui-service/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index bbc1fb0b9..3cc8c6173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -1060,6 +1071,38 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes 1.9.0", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde 1.0.216", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "tokio", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + [[package]] name = "axum" version = "0.7.9" @@ -1067,7 +1110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.4.5", "bytes 1.9.0", "futures-util", "http 1.2.0", @@ -1095,6 +1138,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes 1.9.0", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.4.5" @@ -1931,7 +1991,7 @@ checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ "lazy_static 1.5.0", "nom 5.1.3", - "rust-ini", + "rust-ini 0.13.0", "serde 1.0.216", "serde-hjson", "serde_json", @@ -1939,6 +1999,25 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static 1.5.0", + "nom 7.1.3", + "pathdiff", + "ron", + "rust-ini 0.18.0", + "serde 1.0.216", + "serde_json", + "toml 0.5.11", + "yaml-rust", +] + [[package]] name = "console" version = "0.15.8" @@ -2797,6 +2876,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "docker_credential" version = "1.3.1" @@ -2966,6 +3051,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embed-resource" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4762ce03154ba57ebaeee60cc631901ceae4f18219cbb874e464347471594742" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.19", + "vswhom", + "winreg 0.52.0", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -4166,7 +4265,7 @@ dependencies = [ "async_zip", "aws-config", "aws-sdk-s3", - "axum", + "axum 0.7.9", "bigdecimal", "bincode", "bytes 1.9.0", @@ -4296,6 +4395,24 @@ dependencies = [ "uuid", ] +[[package]] +name = "golem-ui-service" +version = "0.0.0" +dependencies = [ + "axum 0.6.20", + "clap", + "config 0.13.4", + "embed-resource", + "hyper 0.14.31", + "mime_guess", + "rust-embed", + "serde 1.0.216", + "serde_json", + "tokio", + "tower-http 0.4.4", + "tracing", +] + [[package]] name = "golem-wasm-ast" version = "0.0.0" @@ -4442,7 +4559,7 @@ dependencies = [ "async-trait", "aws-config", "aws-sdk-s3", - "axum", + "axum 0.7.9", "bincode", "bitflags 2.6.0", "bytes 1.9.0", @@ -4714,6 +4831,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -4721,7 +4841,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.11", ] [[package]] @@ -4730,7 +4850,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", "serde 1.0.216", ] @@ -4925,6 +5045,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.9.5" @@ -5438,7 +5564,7 @@ dependencies = [ "anyhow", "assert2", "async-trait", - "axum", + "axum 0.7.9", "clap", "console-subscriber", "golem-api-grpc", @@ -5648,6 +5774,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde 1.0.216", +] + [[package]] name = "jsonpath-rust" version = "0.7.3" @@ -5767,7 +5904,7 @@ dependencies = [ "tokio", "tokio-util", "tower 0.5.1", - "tower-http", + "tower-http 0.6.2", "tracing", ] @@ -5808,7 +5945,7 @@ version = "0.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f348cc3e6c9be0ae17f300594bde541b667d10ab8934a119edd61ab5123c43e" dependencies = [ - "ahash", + "ahash 0.8.11", "async-broadcast 0.7.1", "async-stream", "async-trait", @@ -6744,6 +6881,16 @@ dependencies = [ "num-traits 0.2.19", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -7846,7 +7993,7 @@ checksum = "a0de80796b316aec75344095a6d2ef68ec9b8f573b9e7adc821149ba3598e270" dependencies = [ "ansi_term", "atty", - "config", + "config 0.11.0", "directories", "petgraph", "serde 1.0.216", @@ -8138,7 +8285,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.25.4", - "winreg", + "winreg 0.50.0", ] [[package]] @@ -8245,6 +8392,17 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde 1.0.216", +] + [[package]] name = "rpassword" version = "7.3.1" @@ -8286,12 +8444,56 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.90", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust-ini" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -10170,7 +10372,7 @@ checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum", + "axum 0.7.9", "base64 0.22.1", "bytes 1.9.0", "flate2", @@ -10276,6 +10478,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.6.0", + "bytes 1.9.0", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" version = "0.6.2" @@ -10693,6 +10914,26 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "vte" version = "0.11.1" @@ -11205,7 +11446,7 @@ version = "0.207.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" dependencies = [ - "ahash", + "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", "indexmap 2.7.0", @@ -11218,7 +11459,7 @@ version = "0.208.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd921789c9dcc495f589cb37d200155dee65b4a4beeb853323b5e24e0a5f9c58" dependencies = [ - "ahash", + "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", "indexmap 2.7.0", @@ -11232,7 +11473,7 @@ version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07035cc9a9b41e62d3bb3a3815a66ab87c993c06fe1cf6b2a3f2a18499d937db" dependencies = [ - "ahash", + "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", "indexmap 2.7.0", @@ -12046,6 +12287,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winsafe" version = "0.0.19" diff --git a/Cargo.toml b/Cargo.toml index 096234cd8..b84dc39f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ members = [ "wasm-ast", "wasm-rpc", "wasm-rpc-stubgen", - "wasm-rpc-stubgen/tests-integration" + "wasm-rpc-stubgen/tests-integration", + "golem-ui-service" ] exclude = [ diff --git a/golem-ui-service/Cargo.toml b/golem-ui-service/Cargo.toml new file mode 100644 index 000000000..3dfce530f --- /dev/null +++ b/golem-ui-service/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "golem-ui-service" +version = "0.0.0" +edition = "2021" + +[dependencies] +axum = "0.6" +tokio = { version = "1.0", features = ["full"] } +tower-http = { version = "0.4", features = ["trace", "cors"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +config = "0.13" +tracing = "0.1" +hyper = { version = "0.14"} +rust-embed = "8.5.0" +mime_guess = "2.0" +clap = { version = "4.0", features = ["derive"] } + +[build-dependencies] +embed-resource = "3.0.1" + + +[[bin]] +name = "golem-ui" +path = "src/main.rs" +harness = false +test = false \ No newline at end of file From 914e351bdaae730d9a56e0e7ce850bfa68743ecf Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 20:14:07 +0100 Subject: [PATCH 05/19] setup ts files --- golem-ui-service/frontend/src/App.css | 42 ++++++++++++++++ golem-ui-service/frontend/src/App.tsx | 49 +++++++++++++++++++ .../frontend/src/assets/react.svg | 1 + golem-ui-service/frontend/src/index.css | 3 ++ golem-ui-service/frontend/src/main.tsx | 15 ++++++ golem-ui-service/frontend/src/vite-env.d.ts | 1 + golem-ui-service/frontend/tsconfig.node.json | 25 ++++++++++ golem-ui-service/frontend/vite.config.ts | 7 +++ 8 files changed, 143 insertions(+) create mode 100644 golem-ui-service/frontend/src/App.css create mode 100644 golem-ui-service/frontend/src/App.tsx create mode 100644 golem-ui-service/frontend/src/assets/react.svg create mode 100644 golem-ui-service/frontend/src/index.css create mode 100644 golem-ui-service/frontend/src/main.tsx create mode 100644 golem-ui-service/frontend/src/vite-env.d.ts create mode 100644 golem-ui-service/frontend/tsconfig.node.json create mode 100644 golem-ui-service/frontend/vite.config.ts diff --git a/golem-ui-service/frontend/src/App.css b/golem-ui-service/frontend/src/App.css new file mode 100644 index 000000000..b9d355df2 --- /dev/null +++ b/golem-ui-service/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/golem-ui-service/frontend/src/App.tsx b/golem-ui-service/frontend/src/App.tsx new file mode 100644 index 000000000..ae840e955 --- /dev/null +++ b/golem-ui-service/frontend/src/App.tsx @@ -0,0 +1,49 @@ +import { BrowserRouter, Route, Routes } from 'react-router-dom'; + +import { + ComponentDetail +} from './pages/ComponentDetail'; +import { Components } from './pages/Components'; +import { Layout } from './components/layout/Layout'; +import { Overview } from './pages/Overview'; +import { Toaster } from 'react-hot-toast'; + +function App() { + return ( + + + + } /> + Workers Page} /> + } /> + } /> + Plugins Page} /> + API Page} /> + +
+ {/* This component will render the toasts */} + +
+
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/assets/react.svg b/golem-ui-service/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/golem-ui-service/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/golem-ui-service/frontend/src/index.css b/golem-ui-service/frontend/src/index.css new file mode 100644 index 000000000..bd6213e1d --- /dev/null +++ b/golem-ui-service/frontend/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/main.tsx b/golem-ui-service/frontend/src/main.tsx new file mode 100644 index 000000000..f32152793 --- /dev/null +++ b/golem-ui-service/frontend/src/main.tsx @@ -0,0 +1,15 @@ +import './index.css' +import './index.css' + +import App from './App' +import { QueryProvider } from './providers/query-provider' +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' + +createRoot(document.getElementById('root')!).render( + + + + + , +) diff --git a/golem-ui-service/frontend/src/vite-env.d.ts b/golem-ui-service/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/golem-ui-service/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/golem-ui-service/frontend/tsconfig.node.json b/golem-ui-service/frontend/tsconfig.node.json new file mode 100644 index 000000000..96a562e7e --- /dev/null +++ b/golem-ui-service/frontend/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "jsx": "react-jsx" + }, + "include": ["vite.config.ts"] +} diff --git a/golem-ui-service/frontend/vite.config.ts b/golem-ui-service/frontend/vite.config.ts new file mode 100644 index 000000000..8b0f57b91 --- /dev/null +++ b/golem-ui-service/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) From 45a6940f03a07e1ba2ee48dcf3793b9b736895ba Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 20:14:13 +0100 Subject: [PATCH 06/19] build UI pages --- .../components/CreateComponentModal.tsx | 274 ++++++++++++++++++ .../frontend/src/components/layout/Layout.tsx | 51 ++++ .../components/workers/CreateWorkerModal.tsx | 128 ++++++++ .../components/workers/UpdateWorkerModal.tsx | 137 +++++++++ .../frontend/src/hooks/useQueryError.ts | 35 +++ .../frontend/src/lib/api-client.ts | 30 ++ .../frontend/src/pages/ComponentDetail.tsx | 245 ++++++++++++++++ .../frontend/src/pages/Components.tsx | 60 ++++ .../frontend/src/pages/Overview.tsx | 133 +++++++++ .../frontend/src/providers/query-provider.tsx | 32 ++ 10 files changed, 1125 insertions(+) create mode 100644 golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx create mode 100644 golem-ui-service/frontend/src/components/layout/Layout.tsx create mode 100644 golem-ui-service/frontend/src/components/workers/CreateWorkerModal.tsx create mode 100644 golem-ui-service/frontend/src/components/workers/UpdateWorkerModal.tsx create mode 100644 golem-ui-service/frontend/src/hooks/useQueryError.ts create mode 100644 golem-ui-service/frontend/src/lib/api-client.ts create mode 100644 golem-ui-service/frontend/src/pages/ComponentDetail.tsx create mode 100644 golem-ui-service/frontend/src/pages/Components.tsx create mode 100644 golem-ui-service/frontend/src/pages/Overview.tsx create mode 100644 golem-ui-service/frontend/src/providers/query-provider.tsx diff --git a/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx b/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx new file mode 100644 index 000000000..b8b1badc9 --- /dev/null +++ b/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx @@ -0,0 +1,274 @@ +import { Folder, Upload, X } from 'lucide-react'; +import { useCreateComponent, useUpdateComponent } from '../../api/components'; +import { useEffect, useRef, useState } from 'react'; + +import { Component } from '../../types/api'; +import toast from 'react-hot-toast'; + +type ComponentType = 'Durable' | 'Ephemeral'; + +interface ComponentModalProps { + isOpen: boolean; + onClose: () => void; + existingComponent?: Component; // Pass this for update mode +} + +const CreateComponentModal = ({ isOpen, onClose, existingComponent }: ComponentModalProps) => { + const isUpdateMode = !!existingComponent; + const [dragActive, setDragActive] = useState(false); + const [mainFile, setMainFile] = useState(null); + const [additionalFiles, setAdditionalFiles] = useState([]); + const [name, setName] = useState(''); + const [componentType, setComponentType] = useState('Durable'); + const [isSubmitting, setIsSubmitting] = useState(false); + const mainInputRef = useRef(null); + const additionalInputRef = useRef(null); + + const createComponent = useCreateComponent(); + const updateComponent = useUpdateComponent(); + + useEffect(() => { + if (existingComponent) { + setName(existingComponent.componentName); + setComponentType(existingComponent.componentType); + } + }, [existingComponent]); + + const handleMainFileDrop = (e: React.DragEvent) => { + e.preventDefault(); + setDragActive(false); + const droppedFile = e.dataTransfer.files[0]; + if (droppedFile?.name.endsWith('.wasm')) { + setMainFile(droppedFile); + } else { + toast.error('Please upload a .wasm file'); + } + }; + + const handleMainFileSelect = (e: React.ChangeEvent) => { + const selectedFile = e.target.files?.[0] || null; + if (selectedFile?.name.endsWith('.wasm')) { + setMainFile(selectedFile); + } else { + toast.error('Please upload a .wasm file'); + } + }; + + const handleAdditionalFileSelect = (e: React.ChangeEvent) => { + const newFiles = Array.from(e.target.files || []); + setAdditionalFiles(prev => [...prev, ...newFiles]); + }; + + const removeAdditionalFile = (index: number) => { + setAdditionalFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const handleSubmit = async () => { + if (!name || (!mainFile && !isUpdateMode)) return; + + setIsSubmitting(true); + const formData = new FormData(); + formData.append('name', name); + formData.append('componentType', componentType); + + if (mainFile) { + formData.append('component', mainFile); + } + + // Append additional files + additionalFiles.forEach(file => { + formData.append('files', file); + }); + + try { + if (isUpdateMode && existingComponent) { + await updateComponent.mutateAsync({ + componentId: existingComponent.versionedComponentId.componentId, + formData + }); + toast.success('Component updated successfully'); + } else { + await createComponent.mutateAsync(formData); + toast.success('Component created successfully'); + } + + // Reset form + setMainFile(null); + setAdditionalFiles([]); + setName(''); + setComponentType('Durable'); + setIsSubmitting(false); + onClose(); + } catch (error) { + toast.error(`Failed to ${isUpdateMode ? 'update' : 'create'} component`); + setIsSubmitting(false); + console.error(`Failed to ${isUpdateMode ? 'update' : 'create'} component:`, error); + } + }; + + return isOpen ? ( +
+
+

Create New Component

+ +
+
+ + setName(e.target.value)} + className="w-full px-3 py-2 bg-gray-700 rounded-md focus:ring-2 focus:ring-blue-500" + placeholder="Enter component name" + disabled={isSubmitting || isUpdateMode} + /> +
+ +
+ + +
+ +
+ +
!isSubmitting && mainInputRef.current?.click()} + onDragOver={(e) => { + e.preventDefault(); + !isSubmitting && setDragActive(true); + }} + onDragLeave={() => setDragActive(false)} + onDrop={handleMainFileDrop} + className={`border-2 border-dashed rounded-lg p-8 text-center + ${isSubmitting ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'} + ${dragActive ? 'border-blue-500 bg-blue-500 bg-opacity-10' : 'border-gray-600'}`} + > + {mainFile ? ( +
+ + {mainFile.name} + {!isSubmitting && ( + + )} +
+ ) : ( +
+ +
+

Drag and drop your WASM file here

+

or click to browse

+
+
+ )} + +
+
+ +
+ +
+ {additionalFiles.map((file, index) => ( +
+ {file.name} + {!isSubmitting && ( + + )} +
+ ))} + + +
+
+ +
+ + +
+
+
+
+ ) : null; +}; + +export default CreateComponentModal; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/components/layout/Layout.tsx b/golem-ui-service/frontend/src/components/layout/Layout.tsx new file mode 100644 index 000000000..c58da8fa3 --- /dev/null +++ b/golem-ui-service/frontend/src/components/layout/Layout.tsx @@ -0,0 +1,51 @@ +import { Link, useLocation } from 'react-router-dom'; + +import React from 'react'; + +const navItems = [ + { label: 'Overview', path: '/' }, + // { label: 'Workers', path: '/workers' }, + { label: 'Components', path: '/components' }, + { label: 'Plugins', path: '/plugins' }, + { label: 'API', path: '/api' }, +]; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const location = useLocation(); + + return ( +
+ {/* Header */} +
+
+
+ {/* Logo */} +
Golem UI
+ + {/* Navigation */} + +
+
+
+ + {/* Main content */} +
+ {children} +
+
+ ); +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/components/workers/CreateWorkerModal.tsx b/golem-ui-service/frontend/src/components/workers/CreateWorkerModal.tsx new file mode 100644 index 000000000..bcaf9418e --- /dev/null +++ b/golem-ui-service/frontend/src/components/workers/CreateWorkerModal.tsx @@ -0,0 +1,128 @@ +import { useCreateWorker } from "../../api/workers"; +import { useState } from "react"; + +export const CreateWorkerModal = ({ + isOpen, + onClose, + componentId +}: { + isOpen: boolean; + onClose: () => void; + componentId: string; +}) => { + const [name, setName] = useState(''); + const [env, setEnv] = useState<{ key: string; value: string }[]>([{ key: '', value: '' }]); + const [args, setArguments] = useState([]); + const createWorker = useCreateWorker(componentId); + + const handleSubmit = () => { + const envRecord = env.reduce((acc, { key, value }) => { + if (key) acc[key] = value; + return acc; + }, {} as Record); + + createWorker.mutate({ + name: name.replace(/ /g, "-"), + env: envRecord, + args + }, { + onSuccess: onClose + }); + }; + + return isOpen ? ( +
+
+

Create New Worker

+ +
+
+ + setName(e.target.value)} + className="w-full px-3 py-2 bg-gray-700 rounded-md" + placeholder="Enter worker name" + /> +
+ +
+ + {env.map((item, index) => ( +
+ { + const newEnv = [...env]; + newEnv[index].key = e.target.value; + setEnv(newEnv); + }} + className="flex-1 px-3 py-2 bg-gray-700 rounded-md" + /> + { + const newEnv = [...env]; + newEnv[index].value = e.target.value; + setEnv(newEnv); + }} + className="flex-1 px-3 py-2 bg-gray-700 rounded-md" + /> +
+ ))} + + +
+ + {/* Arguments */} +
+ + {args.map((arg, index) => ( + { + const newArgs = [...args]; + newArgs[index] = e.target.value; + setArguments(newArgs); + }} + className="w-full px-3 py-2 bg-gray-700 rounded-md" + placeholder="Enter argument" + /> + ))} + +
+ +
+ + +
+
+
+
+ ) : null; +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/components/workers/UpdateWorkerModal.tsx b/golem-ui-service/frontend/src/components/workers/UpdateWorkerModal.tsx new file mode 100644 index 000000000..30fabe762 --- /dev/null +++ b/golem-ui-service/frontend/src/components/workers/UpdateWorkerModal.tsx @@ -0,0 +1,137 @@ +import { AlertCircle, PauseCircle, PlayCircle, XCircle } from 'lucide-react'; +import { useInterruptWorker, useResumeWorker } from '../../api/workers'; + +import toast from 'react-hot-toast'; +import { useState } from 'react'; + +// import { useWorkers } from '../api/workers'; + +interface WorkerActionModalProps { + isOpen: boolean; + onClose: () => void; + workerId: { + componentId: string; + workerName: string; + }; + action: 'interrupt' | 'resume'; + currentStatus: string; +} + +export const WorkerActionModal = ({ + isOpen, + onClose, + workerId, + action, + currentStatus +}: WorkerActionModalProps) => { + const [recoverImmediately, setRecoverImmediately] = useState(false); + const { componentId, workerName } = workerId; + + const { mutate: interruptWorker } = useInterruptWorker({ + onSuccess: () => { + toast.success('Worker interrupted successfully'); + onClose(); + }, + onError: (error) => { + toast.error(`Failed to interrupt worker: ${error.message}`); + }, + }); + + const { mutate: resumeWorker } = useResumeWorker({ + onSuccess: () => { + toast.success('Worker resumed successfully'); + onClose(); + }, + onError: (error) => { + toast.error(`Failed to resume worker: ${error.message}`); + }, + }); + + const handleAction = () => { + if (action === 'interrupt') { + interruptWorker({componentId, workerName, recoverImmediately}); + } else { + resumeWorker({componentId, workerName}); + } + }; + + if (!isOpen) return null; + + return ( +
+
+
+
+ {action === 'interrupt' ? ( + + ) : ( + + )} +

+ {action === 'interrupt' ? 'Interrupt Worker' : 'Resume Worker'} +

+
+ +
+ +
+
+

+ Worker: {workerName} +

+

+ Current Status: {currentStatus} +

+
+ + {action === 'interrupt' && ( +
+ setRecoverImmediately(e.target.checked)} + className="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500" + /> + +
+ )} + +
+ +

+ {action === 'interrupt' + ? "Interrupting a worker will pause its execution. The worker's status will be 'Interrupted' unless recover-immediately is selected." + : "Resuming a worker will continue its execution from the interrupted state."} +

+
+ +
+ + +
+
+
+
+ ); +}; diff --git a/golem-ui-service/frontend/src/hooks/useQueryError.ts b/golem-ui-service/frontend/src/hooks/useQueryError.ts new file mode 100644 index 000000000..12e5e1e40 --- /dev/null +++ b/golem-ui-service/frontend/src/hooks/useQueryError.ts @@ -0,0 +1,35 @@ +import { UseQueryResult } from '@tanstack/react-query' + +interface ApiError { + error?: string + errors?: string[] + type?: string + golemError?: { + type: string + details: string + } +} + +export const useQueryError = (query: UseQueryResult) => { + if (!query.error) return null + + if (query.error instanceof Error) { + return query.error.message + } + + const error = query.error as ApiError + + if (error.golemError) { + return `${error.golemError.type}: ${error.golemError.details}` + } + + if (error.errors?.length) { + return error.errors.join(', ') + } + + if (error.error) { + return error.error + } + + return 'An unknown error occurred' +} \ No newline at end of file diff --git a/golem-ui-service/frontend/src/lib/api-client.ts b/golem-ui-service/frontend/src/lib/api-client.ts new file mode 100644 index 000000000..40acf3c64 --- /dev/null +++ b/golem-ui-service/frontend/src/lib/api-client.ts @@ -0,0 +1,30 @@ +import axios from 'axios'; + +// Create axios instance with default config +export const apiClient = axios.create({ + baseURL: '/api', + headers: { + 'Content-Type': 'application/json', + }, +}); + +// Add response interceptor for error handling +apiClient.interceptors.response.use( + (response) => response, + (error) => { + // Handle different error types + if (error.response) { + // Server responded with non-2xx status + console.error('API Error:', error.response.data); + return Promise.reject(error.response.data); + } else if (error.request) { + // Request made but no response received + console.error('Network Error:', error.request); + return Promise.reject(new Error('Network error occurred')); + } else { + // Error in request setup + console.error('Request Error:', error.message); + return Promise.reject(error); + } + } +); \ No newline at end of file diff --git a/golem-ui-service/frontend/src/pages/ComponentDetail.tsx b/golem-ui-service/frontend/src/pages/ComponentDetail.tsx new file mode 100644 index 000000000..30e8bfd6a --- /dev/null +++ b/golem-ui-service/frontend/src/pages/ComponentDetail.tsx @@ -0,0 +1,245 @@ +import { AlertCircle, Code2, Pause, Play, Plus, Settings, Trash2 } from 'lucide-react'; +import { deleteComponent, useComponent, } from '../api/components'; +import { useDeleteWorker, useWorkers } from '../api/workers'; +import { useNavigate, useParams } from 'react-router-dom'; + +import CreateComponentModal from '../components/components/CreateComponentModal'; +import { CreateWorkerModal } from '../components/workers/CreateWorkerModal'; +import { WorkerActionModal } from '../components/workers/UpdateWorkerModal'; +import { useState } from 'react'; + +// Stats Card Component +const StatCard = ({ title, value }: { title: string; value: number | string }) => ( +
+

{title}

+

{value}

+
+); + +export const ComponentDetail = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [showCreateWorkerModal, setShowCreateWorkerModal] = useState(false); + const [showUpdateModal, setShowUpdateModal] = useState(false); + const deleteWorker = useDeleteWorker(); + const [actionModal, setActionModal] = useState<{ + isOpen: boolean; + workerId: { componentId: string; workerName: string } | null; + action: 'interrupt' | 'resume'; + currentStatus: string; + }>({ + isOpen: false, + workerId: null, + action: 'interrupt', + currentStatus: '', + }); + + const { data: component, isLoading } = useComponent(id!); + const { data: workers } = useWorkers(id!); + + if (isLoading) { + return
Loading...
; + } + + if (!component) { + return
Component not found
; + } + + const deleteWorkerA = async (workerName: string, componentId: string) => { + if (window.confirm('Are you sure you want to delete this worker?')) { + deleteWorker.mutate({ + componentId, + workerName + }); + } + } + + const handleAction = ( + workerId: { componentId: string; workerName: string }, + action: 'interrupt' | 'resume', + currentStatus: string + ) => { + setActionModal({ + isOpen: true, + workerId, + action, + currentStatus, + }); + }; + + // Calculate worker stats + const activeWorkers = workers?.workers.filter(w => w.status != "Failed").length ?? 0; + const runningWorkers = workers?.workers.filter(w => w.status === 'Running').length ?? 0; + const failedWorkers = workers?.workers.filter(w => w.status === 'Failed').length ?? 0; + + return ( +
+ {/* Header */} +
+
+

{component.componentName}

+

Version {component.versionedComponentId.version}

+
+
+ + + +
+
+ + {/* Stats Grid */} +
+ + + + +
+ + {/* Main Content Grid */} +
+ {/* Exports Section - 2/7 width */} +
+

+ + Exports +

+
+ {component.metadata.exports.map((exp, index) => ( +
+
{exp.name}
+
+ {exp.functions.length} functions +
+ {/* show each function */} + {exp.functions.map((func, index) => ( +
+
+

{ + `${exp.name}.{${func.name}}` + }

+
+
+ ))} +
+ ))} + {component.metadata.exports.length === 0 && ( +
+ No exports available +
+ )} +
+
+ + {/* Workers Section - 5/7 width */} +
+

Workers

+
+ {workers?.workers.map((worker) => ( +
+
+

{worker.workerId.workerName}

+
+ + Status: {worker.status} + + {worker.env && Object.keys(worker.env).length > 0 && ( + + {Object.keys(worker.env).length} env variables + + )} +
+
+
+ + + +
+
+ ))} + + {(!workers?.workers || workers.workers.length === 0) && ( +
+ +

No workers found

+
+ )} +
+
+
+ + {/* Create Worker Modal */} + setShowCreateWorkerModal(false)} + componentId={id!} + /> + + setShowUpdateModal(false)} + existingComponent={component} + /> + + + {actionModal.workerId && ( + setActionModal({ ...actionModal, isOpen: false })} + workerId={actionModal.workerId} + action={actionModal.action} + currentStatus={actionModal.currentStatus} + /> + )} +
+ ); +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/pages/Components.tsx b/golem-ui-service/frontend/src/pages/Components.tsx new file mode 100644 index 000000000..492d7ca04 --- /dev/null +++ b/golem-ui-service/frontend/src/pages/Components.tsx @@ -0,0 +1,60 @@ +import { Clock, Folder, Plus, Upload } from 'lucide-react'; +import { useRef, useState } from 'react'; + +import CreateComponentModal from '../components/components/CreateComponentModal'; +import { Link } from 'react-router-dom'; +import { format } from 'date-fns'; +import { useComponents } from '../api/components'; + +export const Components = () => { + const [showCreateModal, setShowCreateModal] = useState(false); + const { data: components, isLoading } = useComponents(); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+
+

Components

+ +
+ +
+ {components?.map((component) => ( + +

{component.componentName}

+
+
+ + {format(new Date(component.createdAt), 'MMM d, yyyy')} +
+
+ Version: {component.versionedComponentId.version} +
+
+ Type: {component.componentType} +
+
+ + ))} +
+ + setShowCreateModal(false)} + /> +
+ ); +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/pages/Overview.tsx b/golem-ui-service/frontend/src/pages/Overview.tsx new file mode 100644 index 000000000..015b890f9 --- /dev/null +++ b/golem-ui-service/frontend/src/pages/Overview.tsx @@ -0,0 +1,133 @@ +import { Link } from 'react-router-dom'; +import { useApiDefinitions } from '../api/api-definitions'; +import { useComponents } from '../api/components'; +import { usePlugins } from '../api/plugins'; + +const SectionCard = ({ + title, + viewMoreLink, + children +}: { + title: string; + viewMoreLink: string; + children: React.ReactNode; +}) => ( +
+
+

{title}

+ + View more + +
+ {children} +
+); + +const ListItem = ({ title, subtitle, status }: { + title: string; + subtitle?: string; + status?: 'active' | 'inactive' | 'error' +}) => { + const statusColors = { + active: 'bg-green-500', + inactive: 'bg-gray-500', + error: 'bg-red-500', + }; + + return ( +
+
+

{title}

+ {subtitle && ( +

{subtitle}

+ )} +
+ {status && ( +
+ )} +
+ ); +}; + +export const Overview = () => { + const { data: apis, isLoading: apisLoading } = useApiDefinitions(); + const { data: components, isLoading: componentsLoading } = useComponents(); + const { data: plugins, isLoading: pluginsLoading } = usePlugins(); + + return ( +
+ {/* Grid section */} +
+ {/* APIs - 3/8 width */} +
+ + {apisLoading ? ( +
Loading...
+ ) : ( +
+ {apis?.slice(0, 5).map((api) => ( + + ))} + {!apis?.length && ( +

No APIs defined

+ )} +
+ )} +
+
+ + {/* Components - 5/8 width */} +
+ + {componentsLoading ? ( +
Loading...
+ ) : ( +
+ {components?.slice(0, 5).map((component) => ( + + ))} + {!components?.length && ( +

No components available

+ )} +
+ )} +
+
+
+ + {/* Plugins section - Full width */} + + {pluginsLoading ? ( +
Loading...
+ ) : ( +
+ {plugins?.slice(0, 5).map((plugin) => ( + + ))} + {!plugins?.length && ( +

No plugins installed

+ )} +
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/providers/query-provider.tsx b/golem-ui-service/frontend/src/providers/query-provider.tsx new file mode 100644 index 000000000..beaf6abe1 --- /dev/null +++ b/golem-ui-service/frontend/src/providers/query-provider.tsx @@ -0,0 +1,32 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +import { ReactNode } from 'react' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' + +// Configure default options for React Query +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // Data considered fresh for 5 minutes + gcTime: 10 * 60 * 1000, // Keep unused data in cache for 10 minutes + retry: 3, // Retry failed requests 3 times + refetchOnWindowFocus: true,// Refetch when window regains focus + }, + mutations: { + retry: 2, // Retry failed mutations twice + }, + }, +}) + +interface QueryProviderProps { + children: ReactNode +} + +export const QueryProvider = ({ children }: QueryProviderProps) => { + return ( + + {children} + + + ) +} \ No newline at end of file From 978caa8e2c4d38f85ccd0af92221f5d96938f2fd Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 22:01:13 +0100 Subject: [PATCH 07/19] use single executable golem --- Cargo.lock | 1 + golem-cli/Cargo.toml | 1 + golem-cli/src/command.rs | 6 ++++ golem-cli/src/command/ui.rs | 57 +++++++++++++++++++++++++++++++++++++ golem-cli/src/oss/cli.rs | 1 + golem-ui-service/Cargo.toml | 8 ++---- 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 golem-cli/src/command/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 3cc8c6173..e576a1a4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3955,6 +3955,7 @@ dependencies = [ "golem-examples", "golem-rib", "golem-test-framework", + "golem-ui-service", "golem-wasm-ast", "golem-wasm-rpc", "golem-wasm-rpc-stubgen", diff --git a/golem-cli/Cargo.toml b/golem-cli/Cargo.toml index 81b143e9e..e0a8208b4 100644 --- a/golem-cli/Cargo.toml +++ b/golem-cli/Cargo.toml @@ -29,6 +29,7 @@ golem-rib = { path = "../golem-rib", version = "0.0.0", default-features = false golem-wasm-ast = { path = "../wasm-ast", version = "0.0.0", default-features = false, features = ["analysis"] } golem-wasm-rpc = { path = "../wasm-rpc", version = "0.0.0", default-features = false } golem-wasm-rpc-stubgen = { path = "../wasm-rpc-stubgen", version = "0.0.0" } +golem-ui-service = { path = "../golem-ui-service", version = "0.0.0" } anyhow.workspace = true assert2 = { workspace = true } diff --git a/golem-cli/src/command.rs b/golem-cli/src/command.rs index 9c9ee1d3e..84eb6f0ee 100644 --- a/golem-cli/src/command.rs +++ b/golem-cli/src/command.rs @@ -18,6 +18,7 @@ pub mod api_security; pub mod component; pub mod plugin; pub mod profile; +pub mod ui; pub mod worker; use crate::command::api_security::ApiSecuritySchemeSubcommand; @@ -36,6 +37,7 @@ use golem_common::uri::oss::uri::ComponentUri; use golem_wasm_rpc_stubgen::App; use plugin::PluginSubcommand; use profile::{ProfileSubCommand, UniversalProfileAdd}; +use ui::UiCommand; use std::future::Future; use std::path::PathBuf; use worker::WorkerSubcommand; @@ -210,6 +212,10 @@ pub enum SharedCommand< #[arg(long = "generate", value_enum)] generator: clap_complete::Shell, }, + + /// Open web console + #[command()] + Ui(UiCommand), } /// Context before the user has initialized the profile. diff --git a/golem-cli/src/command/ui.rs b/golem-cli/src/command/ui.rs new file mode 100644 index 000000000..17043c704 --- /dev/null +++ b/golem-cli/src/command/ui.rs @@ -0,0 +1,57 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::model::{GolemError, GolemResult}; +use clap::Args; +use golem_ui_service::UiService; +// use std::net::SocketAddr; + +#[derive(Args, Debug)] +pub struct UiCommand { + /// Server port + #[arg(long, default_value = "3000")] + port: u16, + + /// Server host + #[arg(long, default_value = "127.0.0.1")] + host: String, + + /// API base URL + #[arg(long, default_value = "http://localhost:9881")] + api_url: String, + + /// Development mode + #[arg(long)] + dev: bool, +} + +impl UiCommand { + pub async fn handle(self) -> Result { + // let addr = SocketAddr::new( + // self.host.parse().map_err(|e| GolemError(format!("Invalid host address: {}", e)))?, + // self.port, + // ); + + let service = UiService::new(golem_ui_service::CliArgs { + port: self.port, + host: self.host, + api_url: self.api_url, + dev: self.dev, + }); + + service.run().await.map_err(|e| GolemError(e.to_string()))?; + + Ok(GolemResult::Str("UI server stopped".to_string())) + } +} \ No newline at end of file diff --git a/golem-cli/src/oss/cli.rs b/golem-cli/src/oss/cli.rs index 0dd6e7e8a..71b9a3627 100644 --- a/golem-cli/src/oss/cli.rs +++ b/golem-cli/src/oss/cli.rs @@ -266,6 +266,7 @@ impl> CliCommand cmd.handle().await, } } } diff --git a/golem-ui-service/Cargo.toml b/golem-ui-service/Cargo.toml index 3dfce530f..dce41dd7f 100644 --- a/golem-ui-service/Cargo.toml +++ b/golem-ui-service/Cargo.toml @@ -19,9 +19,5 @@ clap = { version = "4.0", features = ["derive"] } [build-dependencies] embed-resource = "3.0.1" - -[[bin]] -name = "golem-ui" -path = "src/main.rs" -harness = false -test = false \ No newline at end of file +[lib] +harness = false \ No newline at end of file From d9a3525ae389fcc20d6e11be2b81cc4f8908ac1d Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 22:37:34 +0100 Subject: [PATCH 08/19] CRUD plugins --- .../components/shared/DeleteConfirmDialog.tsx | 69 +++++ .../frontend/src/pages/PluginDetail.tsx | 284 ++++++++++++++++++ .../frontend/src/pages/Plugins.tsx | 135 +++++++++ 3 files changed, 488 insertions(+) create mode 100644 golem-ui-service/frontend/src/components/shared/DeleteConfirmDialog.tsx create mode 100644 golem-ui-service/frontend/src/pages/PluginDetail.tsx create mode 100644 golem-ui-service/frontend/src/pages/Plugins.tsx diff --git a/golem-ui-service/frontend/src/components/shared/DeleteConfirmDialog.tsx b/golem-ui-service/frontend/src/components/shared/DeleteConfirmDialog.tsx new file mode 100644 index 000000000..6b25c6604 --- /dev/null +++ b/golem-ui-service/frontend/src/components/shared/DeleteConfirmDialog.tsx @@ -0,0 +1,69 @@ +import { AlertTriangle, Loader2, Trash2 } from "lucide-react"; + +const DeleteConfirmDialog = ({ + isOpen, + onClose, + onConfirm, + pluginName, + isDeleting, + modelName = "Plugin" +}: { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + pluginName: string; + isDeleting: boolean; + modelName: string +}) => { + if (!isOpen) return null; + + return ( +
+
+
+
+ +
+
+

Delete {modelName}

+

+ Are you sure you want to delete {pluginName}? + This action cannot be undone. +

+ +
+ + +
+
+
+
+
+ ); +}; + +export default DeleteConfirmDialog \ No newline at end of file diff --git a/golem-ui-service/frontend/src/pages/PluginDetail.tsx b/golem-ui-service/frontend/src/pages/PluginDetail.tsx new file mode 100644 index 000000000..63db3a428 --- /dev/null +++ b/golem-ui-service/frontend/src/pages/PluginDetail.tsx @@ -0,0 +1,284 @@ +import { + AlertCircle, + ArrowLeft, + CheckCircle2, + Code, + Copy, + ExternalLink, + Globe, + Loader2, + Package, + Puzzle, + Server, + Settings, + Terminal +} from 'lucide-react'; +import { useEffect, useState } from 'react'; + +import DeleteConfirmDialog from '../components/shared/DeleteConfirmDialog'; +import { Link } from 'react-router-dom'; +import { +Trash2, +} from 'lucide-react'; +import toast from 'react-hot-toast'; +import { useDeletePlugin } from '../api/plugins'; +import { useNavigate } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; +import { usePluginVersion } from '../api/plugins'; + +const JsonDisplay = ({ data }: { data: string }) => { + const [formattedJson, setFormattedJson] = useState(''); + const [copied, setCopied] = useState(false); + + useEffect(() => { + try { + const parsed = JSON.parse(data); + setFormattedJson(JSON.stringify(parsed, null, 2)); + } catch (e) { + setFormattedJson(data); + } + }, [data]); + + const handleCopy = async () => { + await navigator.clipboard.writeText(formattedJson); + setCopied(true); + toast.success('Copied to clipboard'); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+                
+                    {formattedJson}
+                
+            
+ +
+ ); +}; + +const DetailsCard = ({ title, icon: Icon, children }: any) => ( +
+
+
+ +
+

{title}

+
+ {children} +
+); + +export const PluginDetailPage = () => { + const { name, version } = useParams<{ name: string; version: string }>(); + const { data: plugin, isLoading } = usePluginVersion(name!, version!); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const deletePlugin = useDeletePlugin(); + const navigate = useNavigate(); + + const handleDelete = async () => { + try { + await deletePlugin.mutateAsync({ + name: plugin!.name, + version: plugin!.version + }); + toast.success('Plugin deleted successfully'); + navigate('/plugins'); + } catch (error) { + toast.error('Failed to delete plugin'); + } + }; + + if (isLoading) { + return ( +
+
+ + Loading plugin details... +
+
+ ); + } + + if (!plugin) { + return ( +
+ +

Plugin not found

+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+ + + +
+
+ +
+
+

{plugin.name}

+
+ + Version {plugin.version} +
+
+
+
+
+ + {plugin.scope.type} + + +
+
+
+ + {/* Main content */} +
+ {/* Info Panel */} +
+ +
+
+ +
+ {plugin.specs.type === 'ComponentTransformer' ? ( + + ) : ( + + )} + {plugin.specs.type} +
+
+ {plugin.homepage && ( + + )} +
+
+ + {plugin.description && ( + +

+ {plugin.description} +

+
+ )} +
+ + {/* Main Panel */} +
+ {plugin.specs.type === 'ComponentTransformer' ? ( + <> + +
+
+ +
+ {plugin.specs.validateUrl} +
+
+
+ +
+ {plugin.specs.transformUrl} +
+
+
+
+ + {plugin.specs.jsonSchema && ( + + + + )} + + ) : ( + +
+
+ +
+ {plugin.specs.componentId} +
+
+
+ +
+ + {plugin.specs.version} +
+
+
+
+ )} +
+ + setShowDeleteConfirm(false)} + onConfirm={handleDelete} + pluginName={plugin.name} + isDeleting={deletePlugin.isLoading} + modelName='Plugin' + /> +
+
+ ); +}; + +export default PluginDetailPage; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/pages/Plugins.tsx b/golem-ui-service/frontend/src/pages/Plugins.tsx new file mode 100644 index 000000000..d0e7cfc78 --- /dev/null +++ b/golem-ui-service/frontend/src/pages/Plugins.tsx @@ -0,0 +1,135 @@ +import { Cog, Database, ExternalLink, GitBranch, Package, Plus, Tag, Target, Trash2 } from 'lucide-react'; +import { useDeletePlugin, usePlugins } from '../api/plugins'; + +import { CreatePluginModal } from '../components/plugins/CreatePluginModal'; +import { Link } from 'react-router-dom'; +import { useState } from 'react'; + +export const PluginsPage = () => { + const [showCreateModal, setShowCreateModal] = useState(false); + const { data: plugins, isLoading } = usePlugins(); + const { mutate: deletePlugin } = useDeletePlugin(); + + if (isLoading) { + return ( +
+
+ + Loading plugins... +
+
+ ); + } + + return ( +
+
+
+

+ + Plugins +

+

Manage your system plugins and extensions

+
+ +
+ +
+ {plugins?.map((plugin) => ( +
+
+
+

+ {plugin.specs.type === 'OplogProcessor' ? ( + + ) : ( + + )} + + {plugin.name} + +

+
+ + Version {plugin.version} +
+
+ +
+ +
+

{plugin.description}

+ +
+
+ + Type: {plugin.specs.type} +
+
+ + Scope: {plugin.scope.type} +
+
+ + {plugin.specs.type === 'OplogProcessor' && ( +
+
+ + Component ID: {plugin.specs.componentId} +
+
+ + Version: {plugin.specs.componentVersion} +
+
+ )} + + {plugin.specs.type === 'ComponentTransformer' && ( +
+
+ + Validate URL: {plugin.specs.validateUrl} +
+
+ + Transform URL: {plugin.specs.transformUrl} +
+
+ )} +
+
+ ))} + + {(!plugins || plugins.length === 0) && ( +
+ +

No plugins found

+

Create your first plugin to get started

+
+ )} +
+ + setShowCreateModal(false)} + /> +
+ ); +}; \ No newline at end of file From 28c57717393a6b81e8f16e13c5e937d512dfda13 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Sun, 15 Dec 2024 22:37:50 +0100 Subject: [PATCH 09/19] make the UI more responsive --- golem-ui-service/frontend/src/App.tsx | 5 +- .../components/CreateComponentModal.tsx | 355 +++++++++++------- .../frontend/src/components/layout/Layout.tsx | 126 +++++-- .../components/plugins/CreatePluginModal.tsx | 281 ++++++++++++++ .../components/workers/CreateWorkerModal.tsx | 265 +++++++++---- .../frontend/src/lib/api-client.ts | 3 +- .../frontend/src/pages/ComponentDetail.tsx | 267 ++++++++----- .../frontend/src/pages/Components.tsx | 105 ++++-- .../frontend/src/pages/Overview.tsx | 125 ++++-- 9 files changed, 1143 insertions(+), 389 deletions(-) create mode 100644 golem-ui-service/frontend/src/components/plugins/CreatePluginModal.tsx diff --git a/golem-ui-service/frontend/src/App.tsx b/golem-ui-service/frontend/src/App.tsx index ae840e955..105db7597 100644 --- a/golem-ui-service/frontend/src/App.tsx +++ b/golem-ui-service/frontend/src/App.tsx @@ -6,6 +6,8 @@ import { import { Components } from './pages/Components'; import { Layout } from './components/layout/Layout'; import { Overview } from './pages/Overview'; +import PluginDetailPage from './pages/PluginDetail'; +import { PluginsPage } from './pages/Plugins'; import { Toaster } from 'react-hot-toast'; function App() { @@ -17,8 +19,9 @@ function App() { Workers Page
} /> } /> } /> - Plugins Page} /> + } /> API Page} /> + } />
{/* This component will render the toasts */} diff --git a/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx b/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx index b8b1badc9..cfb540762 100644 --- a/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx +++ b/golem-ui-service/frontend/src/components/components/CreateComponentModal.tsx @@ -1,4 +1,4 @@ -import { Folder, Upload, X } from 'lucide-react'; +import { AlertCircle, Cloud, FileIcon, Folder, Loader2, Plus, Server, Upload, X } from 'lucide-react'; import { useCreateComponent, useUpdateComponent } from '../../api/components'; import { useEffect, useRef, useState } from 'react'; @@ -10,9 +10,118 @@ type ComponentType = 'Durable' | 'Ephemeral'; interface ComponentModalProps { isOpen: boolean; onClose: () => void; - existingComponent?: Component; // Pass this for update mode + existingComponent?: Component; } +const Input = ({ label, error, ...props }: any) => ( +
+ + + {error && ( +
+ + {error} +
+ )} +
+); + +const FileDropzone = ({ + onFileDrop, + onFileSelect, + inputRef, + file, + onRemove, + isSubmitting, + accept = "*", + multiple = false, + dragActive, + setDragActive, + placeholder +}: any) => ( +
!isSubmitting && inputRef.current?.click()} + onDragOver={(e) => { + e.preventDefault(); + !isSubmitting && setDragActive(true); + }} + onDragLeave={() => setDragActive(false)} + onDrop={onFileDrop} + className={`border-2 border-dashed rounded-lg p-6 text-center transition-all duration-200 + ${isSubmitting ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:border-blue-400/50'} + ${dragActive ? 'border-blue-500 bg-blue-500/10' : 'border-gray-600'}`} + > + {file || (multiple && file?.length > 0) ? ( +
+ {multiple ? ( + file.map((f: File, index: number) => ( +
+
+ + {f.name} +
+ {!isSubmitting && ( + + )} +
+ )) + ) : ( +
+
+ + {file.name} +
+ {!isSubmitting && ( + + )} +
+ )} +
+ ) : ( +
+ +
+

{placeholder}

+

or click to browse

+
+
+ )} + +
+); + const CreateComponentModal = ({ isOpen, onClose, existingComponent }: ComponentModalProps) => { const isUpdateMode = !!existingComponent; const [dragActive, setDragActive] = useState(false); @@ -75,7 +184,6 @@ const CreateComponentModal = ({ isOpen, onClose, existingComponent }: ComponentM formData.append('component', mainFile); } - // Append additional files additionalFiles.forEach(file => { formData.append('files', file); }); @@ -102,173 +210,152 @@ const CreateComponentModal = ({ isOpen, onClose, existingComponent }: ComponentM } catch (error) { toast.error(`Failed to ${isUpdateMode ? 'update' : 'create'} component`); setIsSubmitting(false); - console.error(`Failed to ${isUpdateMode ? 'update' : 'create'} component:`, error); } }; - return isOpen ? ( -
-
-

Create New Component

+ if (!isOpen) return null; -
-
- - setName(e.target.value)} - className="w-full px-3 py-2 bg-gray-700 rounded-md focus:ring-2 focus:ring-blue-500" - placeholder="Enter component name" - disabled={isSubmitting || isUpdateMode} - /> + return ( +
+
+
+
+
+ +
+
+

+ {isUpdateMode ? 'Update Component' : 'Create New Component'} +

+

+ Configure your component settings +

+
+ +
+ +
+ setName(e.target.value)} + placeholder="Enter component name" + disabled={isSubmitting || isUpdateMode} + />
- - + +
+ {[ + { value: 'Durable', label: 'Durable', icon: Server }, + { value: 'Ephemeral', label: 'Ephemeral', icon: Cloud } + ].map(option => ( + + ))} +
-
- -
!isSubmitting && mainInputRef.current?.click()} - onDragOver={(e) => { - e.preventDefault(); - !isSubmitting && setDragActive(true); - }} - onDragLeave={() => setDragActive(false)} - onDrop={handleMainFileDrop} - className={`border-2 border-dashed rounded-lg p-8 text-center - ${isSubmitting ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'} - ${dragActive ? 'border-blue-500 bg-blue-500 bg-opacity-10' : 'border-gray-600'}`} - > - {mainFile ? ( -
- - {mainFile.name} - {!isSubmitting && ( - - )} -
- ) : ( -
- -
-

Drag and drop your WASM file here

-

or click to browse

-
-
- )} - +
+ + { + setMainFile(null); + if (mainInputRef.current) { + mainInputRef.current.value = ''; + } + }} + isSubmitting={isSubmitting} accept=".wasm" - onChange={handleMainFileSelect} - className="hidden" - disabled={isSubmitting} + dragActive={dragActive} + setDragActive={setDragActive} + placeholder="Drag and drop your WASM file here" />
-
-
- -
- {additionalFiles.map((file, index) => ( -
- {file.name} - {!isSubmitting && ( - - )} -
- ))} - - + +
-
+
- ) : null; + ); }; export default CreateComponentModal; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/components/layout/Layout.tsx b/golem-ui-service/frontend/src/components/layout/Layout.tsx index c58da8fa3..249110c50 100644 --- a/golem-ui-service/frontend/src/components/layout/Layout.tsx +++ b/golem-ui-service/frontend/src/components/layout/Layout.tsx @@ -1,51 +1,129 @@ +import { ChevronRight, Menu, Package, Puzzle, TableOfContents, Webhook, X } from 'lucide-react'; import { Link, useLocation } from 'react-router-dom'; - -import React from 'react'; +import React, { useState } from 'react'; const navItems = [ - { label: 'Overview', path: '/' }, - // { label: 'Workers', path: '/workers' }, - { label: 'Components', path: '/components' }, - { label: 'Plugins', path: '/plugins' }, - { label: 'API', path: '/api' }, + { label: 'Overview', path: '/', icon: TableOfContents }, + { label: 'Components', path: '/components', icon: Package }, + { label: 'Plugins', path: '/plugins', icon: Puzzle }, + { label: 'API', path: '/api', icon: Webhook }, ]; +const NavLink = ({ item, isActive }: { item: typeof navItems[0], isActive: boolean }) => { + const Icon = item.icon; + + return ( + + + {item.label} + {isActive && ( + + )} + + ); +}; + export const Layout = ({ children }: { children: React.ReactNode }) => { const location = useLocation(); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); return (
{/* Header */} -
-
+
+
{/* Logo */} -
Golem UI
- - {/* Navigation */} -
+ + {/* Mobile Navigation */} + {mobileMenuOpen && ( + + )}
{/* Main content */} -
+
{children}
+ + {/* Footer */} +
); }; \ No newline at end of file diff --git a/golem-ui-service/frontend/src/components/plugins/CreatePluginModal.tsx b/golem-ui-service/frontend/src/components/plugins/CreatePluginModal.tsx new file mode 100644 index 000000000..490f615c2 --- /dev/null +++ b/golem-ui-service/frontend/src/components/plugins/CreatePluginModal.tsx @@ -0,0 +1,281 @@ +import { AlertCircle, ArrowRight, Code, Download, Globe, Loader2, Plus, Server, Settings, Upload, X } from 'lucide-react'; + +import toast from 'react-hot-toast'; +import { useComponents } from '../../api/components'; +import { useCreatePlugin } from '../../api/plugins'; +import { useState } from 'react'; + +type PluginType = 'OplogProcessor' | 'ComponentTransformer'; + +interface CreatePluginModalProps { + isOpen: boolean; + onClose: () => void; +} + +const Input = ({ label, error, ...props }: any) => ( +
+ + + {error && ( +
+ + {error} +
+ )} +
+); + +export const CreatePluginModal = ({ isOpen, onClose }: CreatePluginModalProps) => { + const [name, setName] = useState(''); + const [version, setVersion] = useState(''); + const [description, setDescription] = useState(''); + const [homepage, setHomepage] = useState(''); + const [type, setType] = useState('ComponentTransformer'); + const [isSubmitting, setIsSubmitting] = useState(false); + + // OplogProcessor fields + const [selectedComponentId, setSelectedComponentId] = useState(''); + const [selectedVersion, setSelectedVersion] = useState(0); + + // ComponentTransformer fields + const [jsonSchema, setJsonSchema] = useState(''); + const [validateUrl, setValidateUrl] = useState(''); + const [transformUrl, setTransformUrl] = useState(''); + + const { data: components } = useComponents(); + const createPlugin = useCreatePlugin(); + + const handleSubmit = async () => { + setIsSubmitting(true); + + const pluginData = { + name, + version, + description, + specs: type === 'OplogProcessor' + ? { + type: 'OplogProcessor', + componentId: selectedComponentId, + componentVersion: selectedVersion, + } + : { + type: 'ComponentTransformer', + jsonSchema, + validateUrl, + transformUrl, + }, + scope: { + type: 'Global' + }, + icon: [0], + homepage + }; + + try { + await createPlugin.mutateAsync(pluginData); + toast.success('Plugin created successfully'); + resetForm(); + onClose(); + } catch (error) { + toast.error('Failed to create plugin'); + } finally { + setIsSubmitting(false); + } + }; + + const resetForm = () => { + setName(''); + setVersion(''); + setDescription(''); + setType('ComponentTransformer'); + setSelectedComponentId(''); + setSelectedVersion(0); + setJsonSchema(''); + setValidateUrl(''); + setTransformUrl(''); + }; + + if (!isOpen) return null; + + return ( +
+
+
+
+
+ +
+
+

Create New Plugin

+

Configure your plugin settings

+
+
+ +
+ +
+
+ setName(e.target.value)} + disabled={isSubmitting} + placeholder="Enter plugin name" + /> + setVersion(e.target.value)} + disabled={isSubmitting} + placeholder="e.g., 1.0.0" + /> +
+ + setDescription(e.target.value)} + disabled={isSubmitting} + placeholder="Brief description of your plugin" + /> + + setHomepage(e.target.value)} + disabled={isSubmitting} + placeholder="https://" + /> + +
+ +
+ {[ + { value: 'OplogProcessor', label: 'Oplog Processor', icon: Server }, + { value: 'ComponentTransformer', label: 'Component Transformer', icon: Settings } + ].map(option => ( + + ))} +
+
+ + {type === 'OplogProcessor' ? ( +
+
+ + +
+ + {selectedComponentId && ( + setSelectedVersion(Number(e.target.value))} + disabled={isSubmitting} + min="0" + /> + )} +
+ ) : ( +
+
+ +