From 5b4834e6a5ac46ef088b72155755f32ac0940041 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Wed, 1 Jul 2026 17:10:03 +0530 Subject: [PATCH] Migrate frontend from Next.js to Vite + React Router Full technology change: Next.js App Router replaced with Vite + react-router-dom v6. All 21 pages and 5 landing components converted (next/link -> Link, next/navigation -> react-router-dom hooks, next/font -> @fontsource/inter, process.env -> import.meta.env). Dropped the unused Stripe API routes, which can't run in a client-only Vite build anyway and were never wired to any page. Co-Authored-By: Claude Sonnet 5 --- .env.example | 2 + .gitignore | 1 + index.html | 13 + package-lock.json | 3229 +++++++++++++++++++++++ package.json | 32 + postcss.config.cjs | 6 + src/App.tsx | 52 + src/components/landing/Features.tsx | 56 + src/components/landing/Hero.tsx | 95 + src/components/landing/HowItWorks.tsx | 33 + src/components/landing/Navbar.tsx | 58 + src/components/landing/Pricing.tsx | 148 ++ src/index.css | 21 + src/lib/api.ts | 363 +++ src/main.tsx | 14 + src/pages/ForgotPassword.tsx | 91 + src/pages/Landing.tsx | 67 + src/pages/Login.tsx | 102 + src/pages/ResetPassword.tsx | 126 + src/pages/Signup.tsx | 166 ++ src/pages/admin/AdminAnalytics.tsx | 86 + src/pages/admin/AdminLayout.tsx | 101 + src/pages/admin/AdminLogin.tsx | 81 + src/pages/admin/AdminOverview.tsx | 146 + src/pages/admin/AdminUserDetail.tsx | 187 ++ src/pages/admin/AdminUsers.tsx | 146 + src/pages/dashboard/Billing.tsx | 97 + src/pages/dashboard/Connections.tsx | 173 ++ src/pages/dashboard/DashboardHome.tsx | 192 ++ src/pages/dashboard/DashboardLayout.tsx | 122 + src/pages/dashboard/Endpoints.tsx | 154 ++ src/pages/dashboard/Logs.tsx | 148 ++ src/pages/dashboard/Tokens.tsx | 197 ++ src/pages/dashboard/Usage.tsx | 210 ++ src/vite-env.d.ts | 1 + tailwind.config.cjs | 23 + tsconfig.json | 21 + vite.config.ts | 8 + 38 files changed, 6768 insertions(+) create mode 100644 .env.example create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.cjs create mode 100644 src/App.tsx create mode 100644 src/components/landing/Features.tsx create mode 100644 src/components/landing/Hero.tsx create mode 100644 src/components/landing/HowItWorks.tsx create mode 100644 src/components/landing/Navbar.tsx create mode 100644 src/components/landing/Pricing.tsx create mode 100644 src/index.css create mode 100644 src/lib/api.ts create mode 100644 src/main.tsx create mode 100644 src/pages/ForgotPassword.tsx create mode 100644 src/pages/Landing.tsx create mode 100644 src/pages/Login.tsx create mode 100644 src/pages/ResetPassword.tsx create mode 100644 src/pages/Signup.tsx create mode 100644 src/pages/admin/AdminAnalytics.tsx create mode 100644 src/pages/admin/AdminLayout.tsx create mode 100644 src/pages/admin/AdminLogin.tsx create mode 100644 src/pages/admin/AdminOverview.tsx create mode 100644 src/pages/admin/AdminUserDetail.tsx create mode 100644 src/pages/admin/AdminUsers.tsx create mode 100644 src/pages/dashboard/Billing.tsx create mode 100644 src/pages/dashboard/Connections.tsx create mode 100644 src/pages/dashboard/DashboardHome.tsx create mode 100644 src/pages/dashboard/DashboardLayout.tsx create mode 100644 src/pages/dashboard/Endpoints.tsx create mode 100644 src/pages/dashboard/Logs.tsx create mode 100644 src/pages/dashboard/Tokens.tsx create mode 100644 src/pages/dashboard/Usage.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.cjs create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f474397 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +VITE_API_URL=https://odoo-mcp.thedomainnest.com +VITE_APP_URL=https://your-frontend-domain.com diff --git a/.gitignore b/.gitignore index 67337df..3d99db7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ *.local .DS_Store *.log +*.tsbuildinfo diff --git a/index.html b/index.html new file mode 100644 index 0000000..8dd1f09 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + OdooMCP Cloud — Secure MCP Server for Odoo + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..db24ef8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3229 @@ +{ + "name": "odoomcp-cloud-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "odoomcp-cloud-frontend", + "version": "0.1.0", + "dependencies": { + "@fontsource/inter": "^5.0.0", + "clsx": "^2.1.1", + "lucide-react": "^0.400.0", + "react": "^18", + "react-dom": "^18", + "react-router-dom": "^6.26.0", + "recharts": "^3.9.1" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.0.1", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5", + "vite": "^5.4.0", + "vite-tsconfig-paths": "^5.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "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/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fontsource/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "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", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "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.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", + "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "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.28.4", + "caniuse-lite": "^1.0.30001799", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "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": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "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" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "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", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.383", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.383.tgz", + "integrity": "sha512-I2484/KkAvl8lm9VyjH2JnbOIV0d/UCqT7gbzs6l+o6Vmn9wgB66uVcKX+Vk6HrXtY6fbWTOEXuv8waDTuFNCw==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", + "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "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/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immer": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.9.tgz", + "integrity": "sha512-sc/z0Cyti70bZa0ZU4sWfAElfovFb9Ni8tArJZLuklYWxegPiK3pDOql1Rq5H0FIRAW9LSQRG6OX4KqBldbhBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.400.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.400.0.tgz", + "integrity": "sha512-rpp7pFHh3Xd93KHixNgB0SqThMHpYNzsGUu69UaQbSZ75Q/J3m5t6EhKyMT3m4w2WOxmJ2mY0tD3vebnXqQryQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "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.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "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.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "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.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "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", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.9.1.tgz", + "integrity": "sha512-WMcwlXcB7l+BbxiEdyClkG+1sxrMHNZpzT577LEvU4+rXPd8oTAy1wXk72hnk2KOOmxuLvw3z5DtXT7HEAydtg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "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.7", + "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 || ^5.0 || ^6.0", + "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", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "deprecated": "unmaintained", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "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/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5969ec3 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "odoomcp-cloud-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "build": "tsc --noEmit && vite build", + "preview": "vite preview --port 3000", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@fontsource/inter": "^5.0.0", + "clsx": "^2.1.1", + "lucide-react": "^0.400.0", + "react": "^18", + "react-dom": "^18", + "react-router-dom": "^6.26.0", + "recharts": "^3.9.1" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.0.1", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5", + "vite": "^5.4.0", + "vite-tsconfig-paths": "^5.0.0" + } +} diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..75808d9 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,52 @@ +import { Routes, Route } from 'react-router-dom'; +import Landing from './pages/Landing'; +import Login from './pages/Login'; +import Signup from './pages/Signup'; +import ForgotPassword from './pages/ForgotPassword'; +import ResetPassword from './pages/ResetPassword'; + +import DashboardLayout from './pages/dashboard/DashboardLayout'; +import DashboardHome from './pages/dashboard/DashboardHome'; +import Connections from './pages/dashboard/Connections'; +import Billing from './pages/dashboard/Billing'; +import Endpoints from './pages/dashboard/Endpoints'; +import Tokens from './pages/dashboard/Tokens'; +import Usage from './pages/dashboard/Usage'; +import Logs from './pages/dashboard/Logs'; + +import AdminLogin from './pages/admin/AdminLogin'; +import AdminLayout from './pages/admin/AdminLayout'; +import AdminOverview from './pages/admin/AdminOverview'; +import AdminAnalytics from './pages/admin/AdminAnalytics'; +import AdminUsers from './pages/admin/AdminUsers'; +import AdminUserDetail from './pages/admin/AdminUserDetail'; + +export default function App() { + return ( + + } /> + } /> + } /> + } /> + } /> + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + } /> + }> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/src/components/landing/Features.tsx b/src/components/landing/Features.tsx new file mode 100644 index 0000000..78a1097 --- /dev/null +++ b/src/components/landing/Features.tsx @@ -0,0 +1,56 @@ +import { Shield, Zap, Code2, Key, Database, Settings2 } from 'lucide-react'; + +const FEATURES = [ + { + icon: Shield, + title: 'Secure & Hosted', + desc: 'We host and maintain the MCP server infrastructure.', + }, + { + icon: Zap, + title: 'Claude Ready', + desc: 'Codex, Cursor and other clients supported.', + }, + { + icon: Code2, + title: 'Developer Friendly', + desc: 'Isolated tokens, rotation, usage limits and logs.', + }, + { + icon: Key, + title: 'Secure Tokens', + desc: 'Isolated tokens, rotation, usage limits and logs.', + }, + { + icon: Database, + title: 'Odoo Modules', + desc: 'CRM, Sales, Inventory, Accounting and more.', + }, + { + icon: Settings2, + title: 'Admin Controls', + desc: 'Granular permissions and full activity logs.', + }, +]; + +export default function Features() { + return ( +
+
+
+ {FEATURES.map(({ icon: Icon, title, desc }) => ( +
+
+ +
+
+

{title}

+

{desc}

+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/landing/Hero.tsx b/src/components/landing/Hero.tsx new file mode 100644 index 0000000..555f676 --- /dev/null +++ b/src/components/landing/Hero.tsx @@ -0,0 +1,95 @@ +import { Link } from 'react-router-dom'; +import { ArrowRight, Shield } from 'lucide-react'; + +const AI_TOOLS = [ + { name: 'Claude', color: 'bg-orange-100 text-orange-700' }, + { name: 'Codex', color: 'bg-green-100 text-green-700' }, + { name: 'Cursor', color: 'bg-blue-100 text-blue-700' }, + { name: 'AI Agents', color: 'bg-purple-100 text-purple-700' }, +]; + +export default function Hero() { + return ( +
+
+
+ {/* Left: copy */} +
+
+ + Secure MCP Server for Odoo +
+

+ Connect your Odoo ERP to{' '} + Claude, Codex, and AI agents +

+

+ A secure hosted MCP server for Odoo. Subscribe, connect your Odoo + instance, and instantly use your ERP data inside your favorite AI tools. +

+
+ + Start Free Trial + + + View Setup Guide + +
+

No credit card required · 7-day free trial · Cancel anytime

+
+ + {/* Right: diagram */} +
+
+ {/* Odoo ERP → OdooMCP → AI Tools */} +
+
+
+ 🗄️ +
+ Odoo ERP +
+
+
+
+
+
+ MCP +
+ OdooMCP Cloud +
+
+
+
+
+ {AI_TOOLS.map((t) => ( +
+ {t.name} +
+ ))} +
+
+ {/* MCP endpoint badge */} +
+

Your MCP Endpoint

+

+ https://odoo-mcp.thedomainnest.com/mcp/sse +

+
+ + Active + + Tools: 24 enabled + Claude: Connected +
+
+
+
+
+
+
+ ); +} diff --git a/src/components/landing/HowItWorks.tsx b/src/components/landing/HowItWorks.tsx new file mode 100644 index 0000000..d53f555 --- /dev/null +++ b/src/components/landing/HowItWorks.tsx @@ -0,0 +1,33 @@ +const STEPS = [ + { n: 1, title: 'Create Account', desc: 'Sign up and choose the best plan for your needs.' }, + { n: 2, title: 'Connect Odoo', desc: 'Add your Odoo instance and select modules.' }, + { n: 3, title: 'Get MCP Endpoint', desc: 'We generate your secure MCP URL and token.' }, + { n: 4, title: 'Connect AI Tools', desc: 'Use our guides for Claude, Codex, Cursor and more.' }, + { n: 5, title: 'Ask Your ERP', desc: 'Query your Odoo data using natural language.' }, +]; + +export default function HowItWorks() { + return ( +
+
+

How it works

+
+ {/* connecting line */} +
+ {STEPS.map((s, i) => ( +
+
+ {s.n} +
+

{s.title}

+

{s.desc}

+ {i < STEPS.length - 1 && ( +
+ )} +
+ ))} +
+
+
+ ); +} diff --git a/src/components/landing/Navbar.tsx b/src/components/landing/Navbar.tsx new file mode 100644 index 0000000..5bdd115 --- /dev/null +++ b/src/components/landing/Navbar.tsx @@ -0,0 +1,58 @@ +import { Link } from 'react-router-dom'; +import { useState } from 'react'; +import { Menu, X } from 'lucide-react'; + +export default function Navbar() { + const [open, setOpen] = useState(false); + return ( + + ); +} diff --git a/src/components/landing/Pricing.tsx b/src/components/landing/Pricing.tsx new file mode 100644 index 0000000..8e769b8 --- /dev/null +++ b/src/components/landing/Pricing.tsx @@ -0,0 +1,148 @@ +import { Link } from 'react-router-dom'; +import { Check } from 'lucide-react'; +import { clsx } from 'clsx'; + +const PLANS = [ + { + name: 'Starter', + price: 19, + desc: 'For small Odoo users', + popular: false, + features: [ + '1 Odoo connection', + '1 MCP endpoint', + 'Read-only tools', + 'Token rotation', + 'Claude/Codex setup guide', + '1,000 MCP requests/month', + 'Email support', + ], + cta: 'Start Free Trial', + href: '/signup?plan=starter', + }, + { + name: 'Pro', + price: 49, + desc: 'For growing businesses', + popular: true, + features: [ + '3 Odoo connections', + 'Read / Write tools', + 'Token rotation', + 'Access logs', + '10,000 MCP requests/month', + 'Priority support', + ], + cta: 'Start Free Trial', + href: '/signup?plan=pro', + }, + { + name: 'Agency', + price: 149, + desc: 'For agencies & consultants', + popular: false, + features: [ + '25 client workspaces', + 'White-label endpoint', + 'Team members', + 'Usage dashboard', + 'Custom tool permissions', + '50,000 MCP requests/month', + ], + cta: 'Start Free Trial', + href: '/signup?plan=agency', + }, + { + name: 'Enterprise', + price: null, + desc: 'For large organisations', + popular: false, + features: [ + 'Dedicated MCP server', + 'Private deployment', + 'SSO / OAuth', + 'Advanced audit logs', + 'Custom Odoo modules', + 'SLA & dedicated support', + ], + cta: 'Contact Sales', + href: '/contact', + }, +]; + +export default function Pricing() { + return ( +
+
+
+

Simple, transparent pricing

+

Choose the plan that fits your business. All plans include 7-day free trial.

+
+
+ {PLANS.map((plan) => ( +
+ {plan.popular && ( +
+ Most Popular +
+ )} +
+

+ {plan.name} +

+

+ {plan.desc} +

+
+ {plan.price ? ( + <> + + ${plan.price} + + + /month + + + ) : ( + + Custom + + )} +
+
+ +
    + {plan.features.map((f) => ( +
  • + + {f} +
  • + ))} +
+ + + {plan.cta} + +
+ ))} +
+
+
+ ); +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..9f1b022 --- /dev/null +++ b/src/index.css @@ -0,0 +1,21 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply bg-white text-gray-900 antialiased; + } +} + +@layer utilities { + .gradient-brand { + background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%); + } + .text-gradient { + background: linear-gradient(135deg, #7c3aed, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } +} diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..0bd3392 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,363 @@ +const BASE = import.meta.env.VITE_API_URL ?? 'https://odoo-mcp.thedomainnest.com'; + +async function req(path: string, opts?: RequestInit): Promise { + const res = await fetch(`${BASE}${path}`, { + headers: { 'Content-Type': 'application/json', ...opts?.headers }, + ...opts, + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.detail ?? 'Request failed'); + return data as T; +} + +export interface SignupPayload { + email: string; + password: string; + odoo_url?: string; + odoo_database?: string; + odoo_username?: string; + odoo_credential?: string; + odoo_is_api_key?: boolean; +} + +export interface SignupResult { + user_id: number; + email: string; + api_key: string; + mcp_connection: { url: string; header: string }; +} + +export interface MeResult { + user_id: number; + email: string; + api_key_prefix: string; + created_at: string; + odoo_instances: Array<{ + instance_name: string; + url: string; + database: string; + username: string; + type: string; + }>; +} + +export interface CredentialPayload { + instance_name: string; + url: string; + database: string; + username: string; + credential: string; + is_api_key: boolean; +} + +export interface ApiKeyInfo { + id: number; + name: string; + key_prefix: string; + created_at: string; + last_used_at: string | null; + revoked_at: string | null; + is_active: boolean; +} + +export interface AnalyticsSummary { + total_calls: number; + success_count: number; + error_count: number; + avg_duration_ms: number; + total_duration_ms: number; + unique_tools: number; + unique_instances: number; +} + +export interface ToolStat { + tool_name: string; + calls: number; + success_count: number; + error_count: number; + avg_duration_ms: number; + total_duration_ms: number; + last_used_at: string; +} + +export interface InstanceStat { + instance_name: string; + calls: number; + success_count: number; + error_count: number; + avg_duration_ms: number; + total_duration_ms: number; +} + +export interface TimeseriesBucket { + period: string; + calls: number; + avg_duration_ms: number; + error_count: number; +} + +export interface ToolDetail { + tool_name: string; + summary: { + calls: number; + success_count: number; + error_count: number; + avg_duration_ms: number; + total_duration_ms: number; + }; + timeseries: TimeseriesBucket[]; +} + +export interface LogEntry { + id: number; + tool_name: string; + instance_name: string; + started_at: string; + duration_ms: number; + success: boolean; + error_message: string | null; +} + +export interface LogFilters { + tool_name?: string; + instance_name?: string; + success?: boolean; +} + +function authHeader(apiKey: string) { + return { Authorization: `Bearer ${apiKey}` }; +} + +function qs(params: Record): string { + const parts = Object.entries(params) + .filter(([, v]) => v !== undefined && v !== null && v !== '') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`); + return parts.length ? `?${parts.join('&')}` : ''; +} + +export const api = { + signup: (body: SignupPayload) => + req('/api/signup', { method: 'POST', body: JSON.stringify(body) }), + + login: (email: string, password: string) => + req<{ user_id: number; email: string; api_key_prefix: string }>('/api/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }), + + me: (apiKey: string) => + req('/api/me', { headers: authHeader(apiKey) }), + + regenKey: (apiKey: string) => + req<{ api_key: string; mcp_connection: { url: string; header: string } }>('/api/api-key/regenerate', { + method: 'POST', + headers: authHeader(apiKey), + }), + + addCredential: (apiKey: string, body: CredentialPayload) => + req('/api/credentials', { + method: 'POST', + headers: authHeader(apiKey), + body: JSON.stringify(body), + }), + + listCredentials: (apiKey: string) => + req<{ instances: MeResult['odoo_instances'] }>('/api/credentials', { + headers: authHeader(apiKey), + }), + + deleteCredential: (apiKey: string, name: string) => + req(`/api/credentials/${name}`, { method: 'DELETE', headers: authHeader(apiKey) }), + + forgotPassword: (email: string) => + req<{ message: string }>('/api/password/forgot', { + method: 'POST', + body: JSON.stringify({ email }), + }), + + resetPassword: (token: string, new_password: string) => + req<{ message: string }>('/api/password/reset', { + method: 'POST', + body: JSON.stringify({ token, new_password }), + }), + + listApiKeys: (apiKey: string) => + req<{ keys: ApiKeyInfo[]; total: number }>('/api/keys', { headers: authHeader(apiKey) }), + + createApiKey: (apiKey: string, name: string) => + req<{ id: number; name: string; api_key: string; mcp_connection: { url: string; header: string } }>( + '/api/keys', + { method: 'POST', headers: authHeader(apiKey), body: JSON.stringify({ name }) }, + ), + + revokeApiKey: (apiKey: string, id: number) => + req<{ success: boolean }>(`/api/keys/${id}`, { method: 'DELETE', headers: authHeader(apiKey) }), + + analyticsSummary: (apiKey: string, since?: string, until?: string) => + req(`/api/analytics/summary${qs({ since, until })}`, { + headers: authHeader(apiKey), + }), + + analyticsByTool: (apiKey: string, since?: string, until?: string) => + req<{ tools: ToolStat[] }>(`/api/analytics/by-tool${qs({ since, until })}`, { + headers: authHeader(apiKey), + }), + + analyticsByInstance: (apiKey: string, since?: string, until?: string) => + req<{ instances: InstanceStat[] }>(`/api/analytics/by-instance${qs({ since, until })}`, { + headers: authHeader(apiKey), + }), + + analyticsTimeseries: (apiKey: string, since?: string, until?: string, bucket: 'hour' | 'day' | 'week' = 'day') => + req<{ buckets: TimeseriesBucket[] }>(`/api/analytics/timeseries${qs({ since, until, bucket })}`, { + headers: authHeader(apiKey), + }), + + analyticsToolDetail: (apiKey: string, toolName: string, since?: string, until?: string) => + req(`/api/analytics/tools/${encodeURIComponent(toolName)}${qs({ since, until })}`, { + headers: authHeader(apiKey), + }), + + listLogs: (apiKey: string, page = 1, pageSize = 25, filters?: LogFilters) => + req<{ items: LogEntry[]; page: number; page_size: number; total: number }>( + `/api/logs${qs({ page, page_size: pageSize, ...filters })}`, + { headers: authHeader(apiKey) }, + ), +}; + +// Session helpers (localStorage) +export const session = { + save: (apiKey: string, email: string) => { + if (typeof window === 'undefined') return; + localStorage.setItem('odoomcp_key', apiKey); + localStorage.setItem('odoomcp_email', email); + }, + getKey: (): string | null => { + if (typeof window === 'undefined') return null; + return localStorage.getItem('odoomcp_key'); + }, + getEmail: (): string | null => { + if (typeof window === 'undefined') return null; + return localStorage.getItem('odoomcp_email'); + }, + clear: () => { + if (typeof window === 'undefined') return; + localStorage.removeItem('odoomcp_key'); + localStorage.removeItem('odoomcp_email'); + }, +}; + +// ─── Admin Panel ──────────────────────────────────────────────────────────── + +export interface AdminUserSummary { + id: number; + email: string; + created_at: string; + is_active: boolean; + credential_count: number; + active_key_count: number; + total_calls: number; + last_active_at: string | null; +} + +export interface AdminUserDetail { + id: number; + email: string; + is_active: boolean; + created_at: string; + odoo_instances: MeResult['odoo_instances']; + api_keys: ApiKeyInfo[]; + usage_summary: AnalyticsSummary; +} + +export interface AdminSystemSummary { + total_users: number; + active_users: number; + disabled_users: number; + total_calls: number; + success_count: number; + error_count: number; + avg_duration_ms: number; + active_callers: number; +} + +function adminAuthHeader(token: string) { + return { Authorization: `Bearer ${token}` }; +} + +export const adminApi = { + login: (email: string, password: string) => + req<{ admin_token: string; message: string }>('/api/admin/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }), + + logout: (token: string) => + req<{ success: boolean }>('/api/admin/logout', { method: 'POST', headers: adminAuthHeader(token) }), + + listUsers: (token: string, page = 1, pageSize = 25, since?: string, until?: string) => + req<{ items: AdminUserSummary[]; page: number; page_size: number; total: number }>( + `/api/admin/users${qs({ page, page_size: pageSize, since, until })}`, + { headers: adminAuthHeader(token) }, + ), + + getUser: (token: string, userId: number) => + req(`/api/admin/users/${userId}`, { headers: adminAuthHeader(token) }), + + setUserStatus: (token: string, userId: number, isActive: boolean) => + req<{ success: boolean }>(`/api/admin/users/${userId}/status`, { + method: 'PATCH', + headers: adminAuthHeader(token), + body: JSON.stringify({ is_active: isActive }), + }), + + deleteUser: (token: string, userId: number) => + req<{ success: boolean }>(`/api/admin/users/${userId}`, { + method: 'DELETE', + headers: adminAuthHeader(token), + }), + + revokeUserKey: (token: string, userId: number, keyId: number) => + req<{ success: boolean }>(`/api/admin/users/${userId}/keys/${keyId}/revoke`, { + method: 'POST', + headers: adminAuthHeader(token), + }), + + analyticsSummary: (token: string, since?: string, until?: string) => + req(`/api/admin/analytics/summary${qs({ since, until })}`, { + headers: adminAuthHeader(token), + }), + + analyticsByTool: (token: string, since?: string, until?: string) => + req<{ tools: ToolStat[] }>(`/api/admin/analytics/by-tool${qs({ since, until })}`, { + headers: adminAuthHeader(token), + }), + + analyticsTimeseries: (token: string, since?: string, until?: string, bucket: 'hour' | 'day' | 'week' = 'day') => + req<{ buckets: TimeseriesBucket[] }>(`/api/admin/analytics/timeseries${qs({ since, until, bucket })}`, { + headers: adminAuthHeader(token), + }), +}; + +// Admin session helpers — deliberately separate localStorage keys from the +// regular user `session` above so the two auth states never collide. +export const adminSession = { + save: (token: string, email: string) => { + if (typeof window === 'undefined') return; + localStorage.setItem('odoomcp_admin_token', token); + localStorage.setItem('odoomcp_admin_email', email); + }, + getToken: (): string | null => { + if (typeof window === 'undefined') return null; + return localStorage.getItem('odoomcp_admin_token'); + }, + getEmail: (): string | null => { + if (typeof window === 'undefined') return null; + return localStorage.getItem('odoomcp_admin_email'); + }, + clear: () => { + if (typeof window === 'undefined') return; + localStorage.removeItem('odoomcp_admin_token'); + localStorage.removeItem('odoomcp_admin_email'); + }, +}; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..0007acc --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import '@fontsource/inter'; +import './index.css'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +); diff --git a/src/pages/ForgotPassword.tsx b/src/pages/ForgotPassword.tsx new file mode 100644 index 0000000..6f01afb --- /dev/null +++ b/src/pages/ForgotPassword.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { api } from '@/lib/api'; + +export default function ForgotPassword() { + const [email, setEmail] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [sent, setSent] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(''); + setLoading(true); + try { + await api.forgotPassword(email); + setSent(true); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Something went wrong'); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+ +
+ O +
+ OdooMCP Cloud + +

Reset your password

+

+ Enter your email and we'll send you a reset link +

+
+ +
+ {sent ? ( +
+
+ If that email is registered, a reset link has been sent. Check your inbox. +
+ + Back to login + +
+ ) : ( +
+
+ + setEmail(e.target.value)} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="you@company.com" + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+ )} +
+ +

+ Remembered your password?{' '} + + Log in + +

+
+
+ ); +} diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx new file mode 100644 index 0000000..98ce336 --- /dev/null +++ b/src/pages/Landing.tsx @@ -0,0 +1,67 @@ +import Navbar from '@/components/landing/Navbar'; +import Hero from '@/components/landing/Hero'; +import Features from '@/components/landing/Features'; +import HowItWorks from '@/components/landing/HowItWorks'; +import Pricing from '@/components/landing/Pricing'; + +const TRUST_LOGOS = ['odoo', 'ACSONE', 'VENTURE', 'CYBROSYS', 'Camptocamp', 'Shuva', 'PRAGMATIC']; + +export default function Landing() { + return ( + <> + +
+ + + + + + {/* Trust logos */} +
+
+

+ Trusted by Odoo professionals teams worldwide +

+
+ {TRUST_LOGOS.map((name) => ( + + {name} + + ))} +
+
+
+ + {/* Footer */} +
+
+
+
+
+ OdooMCP Cloud +
+

Secure MCP Server for Odoo ERP

+
+
+
+

Product

+ {['Features', 'Pricing', 'Security', 'Docs'].map((l) => ( +

{l}

+ ))} +
+
+

Company

+ {['About', 'Blog', 'Contact', 'Privacy'].map((l) => ( +

{l}

+ ))} +
+
+
+
+ © {new Date().getFullYear()} OdooMCP Cloud. All rights reserved. +
+
+
+ + ); +} diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 0000000..8167717 --- /dev/null +++ b/src/pages/Login.tsx @@ -0,0 +1,102 @@ +import { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { api, session } from '@/lib/api'; + +export default function Login() { + const navigate = useNavigate(); + const [form, setForm] = useState({ email: '', password: '' }); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(''); + setLoading(true); + try { + const savedKey = session.getKey(); + if (!savedKey) { + setError('No API key found. Please use your API key directly or re-generate it from a previous signup.'); + setLoading(false); + return; + } + // Verify credentials via /api/login then confirm key works + await api.login(form.email, form.password); + session.save(savedKey, form.email); + navigate('/dashboard'); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Login failed'); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+ +
+ O +
+ OdooMCP Cloud + +

Welcome back

+

Log in to your dashboard

+
+ +
+
+
+ + setForm({ ...form, email: e.target.value })} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="you@company.com" + /> +
+
+
+ + + Forgot password? + +
+ setForm({ ...form, password: e.target.value })} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+
+ +

+ Don't have an account?{' '} + + Start free trial + +

+
+
+ ); +} diff --git a/src/pages/ResetPassword.tsx b/src/pages/ResetPassword.tsx new file mode 100644 index 0000000..228568c --- /dev/null +++ b/src/pages/ResetPassword.tsx @@ -0,0 +1,126 @@ +import { useState } from 'react'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; +import { api } from '@/lib/api'; + +export default function ResetPassword() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const token = searchParams.get('token') ?? ''; + + const [password, setPassword] = useState(''); + const [confirm, setConfirm] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [done, setDone] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(''); + + if (!token) { + setError('Missing or invalid reset link. Request a new one.'); + return; + } + if (password.length < 8) { + setError('Password must be at least 8 characters.'); + return; + } + if (password !== confirm) { + setError('Passwords do not match.'); + return; + } + + setLoading(true); + try { + await api.resetPassword(token, password); + setDone(true); + setTimeout(() => navigate('/login'), 2500); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Reset failed'); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+ +
+ O +
+ OdooMCP Cloud + +

Set a new password

+

+ Choose a new password for your account +

+
+ +
+ {done ? ( +
+
+ Password reset. All existing API keys were revoked. Redirecting to login… +
+
+ ) : ( +
+ {!token && ( +
+ No reset token found in the link. Please use the link from your email, or{' '} + request a new one. +
+ )} +
+ + setPassword(e.target.value)} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="••••••••" + /> +
+
+ + setConfirm(e.target.value)} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+ )} +
+ +

+ + Back to login + +

+
+
+ ); +} diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx new file mode 100644 index 0000000..80516f9 --- /dev/null +++ b/src/pages/Signup.tsx @@ -0,0 +1,166 @@ +import { useState } from 'react'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; +import { api, session } from '@/lib/api'; +import { Copy, Check, AlertCircle } from 'lucide-react'; + +export default function Signup() { + const navigate = useNavigate(); + const [params] = useSearchParams(); + const planFromUrl = params.get('plan') ?? 'starter'; + + const [step, setStep] = useState<'account' | 'key'>('account'); + const [form, setForm] = useState({ email: '', password: '', confirmPassword: '' }); + const [apiKey, setApiKey] = useState(''); + const [copied, setCopied] = useState(false); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + async function handleSignup(e: React.FormEvent) { + e.preventDefault(); + if (form.password !== form.confirmPassword) { + setError('Passwords do not match'); + return; + } + setError(''); + setLoading(true); + try { + const result = await api.signup({ email: form.email, password: form.password }); + setApiKey(result.api_key); + session.save(result.api_key, result.email); + setStep('key'); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Signup failed'); + } finally { + setLoading(false); + } + } + + function copyKey() { + navigator.clipboard.writeText(apiKey); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( +
+ {step === 'key' ? ( +
+
+
+
+ +
+
+

Account created!

+

Save your API key — shown once only

+
+
+ +
+
+ +

+ This is your API key. It will NOT be shown again. Copy and store it securely. +

+
+
+ {apiKey} + +
+
+ +
+

Your MCP Server URL:

+ + {import.meta.env.VITE_API_URL}/mcp/sse + +

Bearer Token:

+ {apiKey} +
+ + +
+
+ ) : ( +
+
+ +
+ O +
+ OdooMCP Cloud + +

Start your free trial

+

+ Plan: {planFromUrl} · 7 days free +

+
+ +
+
+
+ + setForm({ ...form, email: e.target.value })} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="you@company.com" + /> +
+
+ + setForm({ ...form, password: e.target.value })} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="Min 8 characters" + /> +
+
+ + setForm({ ...form, confirmPassword: e.target.value })} + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ + {error && ( +
+ {error} +
+ )} + + + +

+ No credit card required · Cancel anytime +

+
+
+ +

+ Already have an account?{' '} + Log in +

+
+ )} +
+ ); +} diff --git a/src/pages/admin/AdminAnalytics.tsx b/src/pages/admin/AdminAnalytics.tsx new file mode 100644 index 0000000..a710f91 --- /dev/null +++ b/src/pages/admin/AdminAnalytics.tsx @@ -0,0 +1,86 @@ +import { useState, useEffect, useCallback } from 'react'; +import { adminApi, adminSession, ToolStat } from '@/lib/api'; + +const RANGES = [ + { label: '7 days', days: 7 }, + { label: '30 days', days: 30 }, + { label: '90 days', days: 90 }, +]; + +function isoDaysAgo(days: number): string { + const d = new Date(); + d.setUTCDate(d.getUTCDate() - days); + return d.toISOString(); +} + +export default function AdminAnalytics() { + const [rangeDays, setRangeDays] = useState(30); + const [byTool, setByTool] = useState([]); + const [loading, setLoading] = useState(true); + + const load = useCallback(() => { + const token = adminSession.getToken(); + if (!token) return; + setLoading(true); + adminApi + .analyticsByTool(token, isoDaysAgo(rangeDays)) + .then((r) => setByTool(r.tools)) + .catch(console.error) + .finally(() => setLoading(false)); + }, [rangeDays]); + + useEffect(load, [load]); + + return ( +
+
+
+

System Analytics

+

Every tool called, aggregated across all users.

+
+
+ {RANGES.map((r) => ( + + ))} +
+
+ +
+ {byTool.length === 0 && !loading ? ( +
No tool calls in this range yet.
+ ) : ( + + + + + + + + + + + + {byTool.map((t) => ( + + + + + + + + ))} + +
ToolTotal CallsErrorsAvg DurationLast Used
{t.tool_name}{t.calls}{t.error_count}{t.avg_duration_ms.toFixed(1)} ms{t.last_used_at}
+ )} +
+
+ ); +} diff --git a/src/pages/admin/AdminLayout.tsx b/src/pages/admin/AdminLayout.tsx new file mode 100644 index 0000000..e4902db --- /dev/null +++ b/src/pages/admin/AdminLayout.tsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from 'react'; +import { useNavigate, useLocation, Link, Outlet } from 'react-router-dom'; +import { adminApi, adminSession } from '@/lib/api'; +import { + ShieldCheck, LayoutDashboard, Users, BarChart2, LogOut, Menu, +} from 'lucide-react'; +import { clsx } from 'clsx'; + +const NAV = [ + { label: 'Overview', href: '/admin', icon: LayoutDashboard }, + { label: 'Users', href: '/admin/users', icon: Users }, + { label: 'System Analytics', href: '/admin/analytics', icon: BarChart2 }, +]; + +export default function AdminLayout() { + const navigate = useNavigate(); + const location = useLocation(); + const pathname = location.pathname; + const [email, setEmail] = useState(''); + const [sidebarOpen, setSidebarOpen] = useState(false); + + useEffect(() => { + const token = adminSession.getToken(); + const em = adminSession.getEmail(); + if (!token) { navigate('/admin/login', { replace: true }); return; } + if (em) setEmail(em); + }, [navigate]); + + async function logout() { + const token = adminSession.getToken(); + if (token) { + try { await adminApi.logout(token); } catch { /* token may already be expired server-side */ } + } + adminSession.clear(); + navigate('/admin/login'); + } + + const Sidebar = ( + + ); + + return ( +
+
{Sidebar}
+ + {sidebarOpen && ( +
+
{Sidebar}
+
setSidebarOpen(false)} /> +
+ )} + +
+
+ + Admin Panel +
+
+
+
+ ); +} diff --git a/src/pages/admin/AdminLogin.tsx b/src/pages/admin/AdminLogin.tsx new file mode 100644 index 0000000..b55c897 --- /dev/null +++ b/src/pages/admin/AdminLogin.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { adminApi, adminSession } from '@/lib/api'; +import { ShieldCheck } from 'lucide-react'; + +export default function AdminLogin() { + const navigate = useNavigate(); + const [form, setForm] = useState({ email: '', password: '' }); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(''); + setLoading(true); + try { + const result = await adminApi.login(form.email, form.password); + adminSession.save(result.admin_token, form.email); + navigate('/admin'); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Login failed'); + } finally { + setLoading(false); + } + } + + return ( +
+
+
+
+ +
+

Admin Panel

+

Operator access only

+
+ +
+
+
+ + setForm({ ...form, email: e.target.value })} + className="w-full bg-gray-800 border border-gray-700 text-white rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="admin@yourcompany.com" + /> +
+
+ + setForm({ ...form, password: e.target.value })} + className="w-full bg-gray-800 border border-gray-700 text-white rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+
+
+
+ ); +} diff --git a/src/pages/admin/AdminOverview.tsx b/src/pages/admin/AdminOverview.tsx new file mode 100644 index 0000000..1b83d7f --- /dev/null +++ b/src/pages/admin/AdminOverview.tsx @@ -0,0 +1,146 @@ +import { useState, useEffect, useCallback } from 'react'; +import { adminApi, adminSession, AdminSystemSummary, ToolStat, TimeseriesBucket } from '@/lib/api'; +import { + LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, +} from 'recharts'; +import { Users, Activity, XCircle, Clock } from 'lucide-react'; + +const RANGES = [ + { label: '7 days', days: 7 }, + { label: '30 days', days: 30 }, + { label: '90 days', days: 90 }, +]; + +function isoDaysAgo(days: number): string { + const d = new Date(); + d.setUTCDate(d.getUTCDate() - days); + return d.toISOString(); +} + +export default function AdminOverview() { + const [rangeDays, setRangeDays] = useState(30); + const [summary, setSummary] = useState(null); + const [byTool, setByTool] = useState([]); + const [timeseries, setTimeseries] = useState([]); + const [loading, setLoading] = useState(true); + + const load = useCallback(() => { + const token = adminSession.getToken(); + if (!token) return; + setLoading(true); + const since = isoDaysAgo(rangeDays); + Promise.all([ + adminApi.analyticsSummary(token, since), + adminApi.analyticsByTool(token, since), + adminApi.analyticsTimeseries(token, since, undefined, rangeDays > 30 ? 'week' : 'day'), + ]) + .then(([s, t, ts]) => { + setSummary(s); + setByTool(t.tools); + setTimeseries(ts.buckets); + }) + .catch(console.error) + .finally(() => setLoading(false)); + }, [rangeDays]); + + useEffect(load, [load]); + + return ( +
+
+
+

System Overview

+

Activity across every registered user.

+
+
+ {RANGES.map((r) => ( + + ))} +
+
+ +
+ + + + +
+ +
+ + + 0 ? `${((summary.success_count / summary.total_calls) * 100).toFixed(1)}%` : '—'} color="green" /> +
+ +
+

Calls Over Time (All Users)

+ {timeseries.length === 0 && !loading ? ( + + ) : ( + + + + + + + + + + )} +
+ +
+

Most Used Tools (System-Wide)

+ {byTool.length === 0 && !loading ? ( + + ) : ( + + + + + + + + + + )} +
+
+ ); +} + +function StatCard({ icon: Icon, label, value, color }: { + icon: typeof Activity; label: string; value: string | number; color: 'blue' | 'green' | 'red' | 'purple'; +}) { + const colors = { + blue: 'bg-blue-50 text-blue-600', + green: 'bg-green-50 text-green-600', + red: 'bg-red-50 text-red-600', + purple: 'bg-purple-50 text-purple-600', + }; + return ( +
+
+ +
+

{value}

+

{label}

+
+ ); +} + +function EmptyChart() { + return ( +
+ No data in this range yet. +
+ ); +} diff --git a/src/pages/admin/AdminUserDetail.tsx b/src/pages/admin/AdminUserDetail.tsx new file mode 100644 index 0000000..a43deb4 --- /dev/null +++ b/src/pages/admin/AdminUserDetail.tsx @@ -0,0 +1,187 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useParams, useNavigate, Link } from 'react-router-dom'; +import { adminApi, adminSession, AdminUserDetail as AdminUserDetailType } from '@/lib/api'; +import { ArrowLeft, Trash2, Activity, CheckCircle2, XCircle, Clock } from 'lucide-react'; + +export default function AdminUserDetailPage() { + const params = useParams<{ id: string }>(); + const navigate = useNavigate(); + const userId = Number(params.id); + + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + const load = useCallback(() => { + const token = adminSession.getToken(); + if (!token) return; + setLoading(true); + adminApi + .getUser(token, userId) + .then(setUser) + .catch(console.error) + .finally(() => setLoading(false)); + }, [userId]); + + useEffect(load, [load]); + + async function toggleActive() { + if (!user) return; + const token = adminSession.getToken(); + if (!token) return; + await adminApi.setUserStatus(token, user.id, !user.is_active); + load(); + } + + async function handleDelete() { + if (!user) return; + if (!confirm(`Permanently delete "${user.email}"? This cannot be undone.`)) return; + const token = adminSession.getToken(); + if (!token) return; + await adminApi.deleteUser(token, user.id); + navigate('/admin/users'); + } + + async function revokeKey(keyId: number, name: string) { + if (!user) return; + if (!confirm(`Force-revoke "${name}"? Any client using this key will immediately lose access.`)) return; + const token = adminSession.getToken(); + if (!token) return; + await adminApi.revokeUserKey(token, user.id, keyId); + load(); + } + + if (loading && !user) { + return
Loading…
; + } + if (!user) { + return
User not found.
; + } + + return ( +
+ + Back to users + + +
+
+

{user.email}

+

+ Joined {user.created_at} ·{' '} + + {user.is_active ? 'Active' : 'Disabled'} + +

+
+
+ + +
+
+ + {/* Usage summary */} +
+ + + + +
+ + {/* Odoo instances */} +
+

Odoo Instances

+ {user.odoo_instances.length === 0 ? ( +

No Odoo instances connected.

+ ) : ( +
+ {user.odoo_instances.map((inst) => ( +
+ {inst.instance_name} + {inst.url} · {inst.database} · {inst.type} +
+ ))} +
+ )} +
+ + {/* API keys */} +
+
+

API Keys

+
+ {user.api_keys.length === 0 ? ( +
No API keys.
+ ) : ( + + + + + + + + + + + + {user.api_keys.map((k) => ( + + + + + + + + ))} + +
NameKeyLast UsedStatus
{k.name}{k.key_prefix}...{k.last_used_at ?? 'Never'} + {k.is_active ? ( + Active + ) : ( + Revoked + )} + + {k.is_active && ( + + )} +
+ )} +
+
+ ); +} + +function StatCard({ icon: Icon, label, value, color }: { + icon: typeof Activity; label: string; value: string | number; color: 'blue' | 'green' | 'red' | 'purple'; +}) { + const colors = { + blue: 'bg-blue-50 text-blue-600', + green: 'bg-green-50 text-green-600', + red: 'bg-red-50 text-red-600', + purple: 'bg-purple-50 text-purple-600', + }; + return ( +
+
+ +
+

{value}

+

{label}

+
+ ); +} diff --git a/src/pages/admin/AdminUsers.tsx b/src/pages/admin/AdminUsers.tsx new file mode 100644 index 0000000..2558d41 --- /dev/null +++ b/src/pages/admin/AdminUsers.tsx @@ -0,0 +1,146 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Link } from 'react-router-dom'; +import { adminApi, adminSession, AdminUserSummary } from '@/lib/api'; +import { ChevronLeft, ChevronRight, Trash2, Users as UsersIcon } from 'lucide-react'; + +const PAGE_SIZE = 25; + +export default function AdminUsers() { + const [items, setItems] = useState([]); + const [page, setPage] = useState(1); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(true); + + const load = useCallback(() => { + const token = adminSession.getToken(); + if (!token) return; + setLoading(true); + adminApi + .listUsers(token, page, PAGE_SIZE) + .then((r) => { + setItems(r.items); + setTotal(r.total); + }) + .catch(console.error) + .finally(() => setLoading(false)); + }, [page]); + + useEffect(load, [load]); + + async function toggleActive(user: AdminUserSummary) { + const token = adminSession.getToken(); + if (!token) return; + try { + await adminApi.setUserStatus(token, user.id, !user.is_active); + load(); + } catch (err) { + alert(err instanceof Error ? err.message : 'Failed to update status'); + } + } + + async function handleDelete(user: AdminUserSummary) { + if (!confirm(`Permanently delete "${user.email}"? This removes their Odoo connections, API keys, and usage history. This cannot be undone.`)) return; + const token = adminSession.getToken(); + if (!token) return; + try { + await adminApi.deleteUser(token, user.id); + load(); + } catch (err) { + alert(err instanceof Error ? err.message : 'Failed to delete user'); + } + } + + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + + return ( +
+
+

Users

+

Every registered account on this server.

+
+ +
+ {items.length === 0 && !loading ? ( +
+ +

No users yet

+
+ ) : ( + + + + + + + + + + + + + + + {items.map((u) => ( + + + + + + + + + + + ))} + +
EmailJoinedInstancesActive KeysTotal CallsLast ActiveStatus
+ + {u.email} + + {u.created_at}{u.credential_count}{u.active_key_count}{u.total_calls}{u.last_active_at ?? 'Never'} + + + +
+ )} +
+ + {total > PAGE_SIZE && ( +
+ Page {page} of {totalPages} ({total} total) +
+ + +
+
+ )} +
+ ); +} diff --git a/src/pages/dashboard/Billing.tsx b/src/pages/dashboard/Billing.tsx new file mode 100644 index 0000000..96bbeca --- /dev/null +++ b/src/pages/dashboard/Billing.tsx @@ -0,0 +1,97 @@ +import { Check } from 'lucide-react'; + +const PLANS = [ + { name: 'Starter', price: 19, current: false, features: ['1 connection', '1 MCP endpoint', 'Read-only', '1,000 req/mo'] }, + { name: 'Pro', price: 49, current: true, features: ['3 connections', 'Read/Write', 'Access logs', '10,000 req/mo'] }, + { name: 'Agency', price: 149, current: false, features: ['25 workspaces', 'White-label', 'Team members', '50,000 req/mo'] }, +]; + +export default function Billing() { + return ( +
+
+

Billing

+

Manage your subscription and payment details.

+
+ + {/* Current plan */} +
+
+
+

Current Plan

+

Pro Plan

+

$49/month · Renews July 29, 2026

+
+ Active +
+
+ + +
+
+ + {/* Plan comparison */} +
+

Change Plan

+
+ {PLANS.map((plan) => ( +
+

{plan.name}

+

${plan.price}/mo

+
    + {plan.features.map((f) => ( +
  • + {f} +
  • + ))} +
+ {plan.current ? ( +
Current Plan
+ ) : ( + + )} +
+ ))} +
+
+ + {/* Invoice history */} +
+

Invoice History

+ + + + + + + + + + + {[ + { date: 'Jun 29, 2026', desc: 'Pro Plan — Monthly', amount: '$49.00', status: 'Paid' }, + { date: 'May 29, 2026', desc: 'Pro Plan — Monthly', amount: '$49.00', status: 'Paid' }, + { date: 'Apr 29, 2026', desc: 'Pro Plan — Monthly', amount: '$49.00', status: 'Paid' }, + ].map((row, i) => ( + + + + + + + ))} + +
DateDescriptionAmountStatus
{row.date}{row.desc}{row.amount} + {row.status} +
+
+
+ ); +} diff --git a/src/pages/dashboard/Connections.tsx b/src/pages/dashboard/Connections.tsx new file mode 100644 index 0000000..5846cd9 --- /dev/null +++ b/src/pages/dashboard/Connections.tsx @@ -0,0 +1,173 @@ +import { useState, useEffect } from 'react'; +import { api, session } from '@/lib/api'; +import { Plus, Trash2, TestTube2, Globe, CheckCircle } from 'lucide-react'; + +interface Instance { + instance_name: string; + url: string; + database: string; + username: string; + type: string; +} + +export default function Connections() { + const [instances, setInstances] = useState([]); + const [showForm, setShowForm] = useState(false); + const [loading, setLoading] = useState(false); + const [form, setForm] = useState({ + instance_name: 'default', + url: '', + database: '', + username: '', + credential: '', + is_api_key: true, + }); + + function load() { + const key = session.getKey(); + if (!key) return; + api.listCredentials(key).then((r) => setInstances(r.instances)).catch(console.error); + } + useEffect(load, []); + + async function handleAdd(e: React.FormEvent) { + e.preventDefault(); + const key = session.getKey(); + if (!key) return; + setLoading(true); + try { + await api.addCredential(key, form); + setShowForm(false); + setForm({ instance_name: 'default', url: '', database: '', username: '', credential: '', is_api_key: true }); + load(); + } catch (err) { + alert(err instanceof Error ? err.message : 'Failed to add connection'); + } finally { + setLoading(false); + } + } + + async function handleDelete(name: string) { + if (!confirm(`Remove "${name}"?`)) return; + const key = session.getKey(); + if (!key) return; + await api.deleteCredential(key, name); + load(); + } + + return ( +
+
+
+

Odoo Connections

+

Manage your Odoo ERP instances.

+
+ +
+ + {/* Existing connections */} + {instances.length === 0 && !showForm && ( +
+ +

No connections yet

+

Add your Odoo instance to get started.

+ +
+ )} + + {instances.map((inst) => ( +
+
+
+
+ +
+
+

{inst.instance_name}

+

{inst.url}

+
+
+ +
+
+

Database

{inst.database}

+

Username

{inst.username}

+

Auth Type

{inst.type}

+
+
+ ))} + + {/* Add connection form panel */} + {showForm && ( +
+

Add Odoo Connection

+
+
+ setForm({ ...form, instance_name: v })} placeholder="e.g. production" /> + setForm({ ...form, url: v })} placeholder="https://mycompany.odoo.com" /> +
+
+ setForm({ ...form, database: v })} placeholder="mydb" /> + setForm({ ...form, username: v })} placeholder="admin@company.com" /> +
+
+ + +
+ setForm({ ...form, credential: v })} + placeholder={form.is_api_key ? 'Paste your Odoo API key' : 'Odoo password'} + type="password" + /> + +
+ + +
+ +
+ )} +
+ ); +} + +function Field({ label, value, onChange, placeholder, type = 'text' }: { + label: string; value: string; onChange: (v: string) => void; placeholder?: string; type?: string; +}) { + return ( +
+ + onChange(e.target.value)} + placeholder={placeholder} required + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + /> +
+ ); +} diff --git a/src/pages/dashboard/DashboardHome.tsx b/src/pages/dashboard/DashboardHome.tsx new file mode 100644 index 0000000..fea4129 --- /dev/null +++ b/src/pages/dashboard/DashboardHome.tsx @@ -0,0 +1,192 @@ +import { useEffect, useState } from 'react'; +import { api, session, type MeResult } from '@/lib/api'; +import { Cpu, Globe, BarChart2, Copy, RotateCcw, ExternalLink, Activity } from 'lucide-react'; +import { clsx } from 'clsx'; + +const MCP_URL = `${import.meta.env.VITE_API_URL ?? 'https://odoo-mcp.thedomainnest.com'}/mcp/sse`; + +const RECENT_ACTIVITY = [ + { time: '2 mins ago', event: 'Connection sync', status: 'Success', detail: 'Odoo connection is healthy' }, + { time: '5 mins ago', event: 'MCP request', status: 'Success', detail: 'search_invoices' }, + { time: '12 mins ago', event: 'Token rotated', status: 'Success', detail: 'New token generated' }, + { time: '1 hour ago', event: 'Connection sync', status: 'Success', detail: 'Odoo connection is healthy' }, +]; + +export default function DashboardHome() { + const [me, setMe] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const key = session.getKey(); + if (!key) return; + api.me(key).then(setMe).catch(console.error).finally(() => setLoading(false)); + }, []); + + const instance = me?.odoo_instances?.[0]; + const requestsUsed = 2431; + const requestsLimit = 10000; + + return ( +
+
+

Dashboard

+

Welcome back{me ? `, ${me.email}` : ''}.

+
+ + {/* Stat cards */} +
+ 💎} + color="bg-purple-50" + /> + Active} + sub="All systems operational" + icon={} + color="bg-blue-50" + /> + } + color="bg-green-50" + /> + +
+
+
+ {Math.round((requestsUsed / requestsLimit) * 100)}% used +
+ } + icon={} + color="bg-orange-50" + /> +
+ +
+ {/* Odoo Connection Health */} +
+

Odoo Connection Health

+ {instance ? ( +
+ + + + Healthy} /> + +
+ ) : ( +

No Odoo instance connected yet.{' '} + Add one → +

+ )} +
+ + {/* Quick Actions */} +
+

Quick Actions

+
+ {[ + { label: 'Copy MCP URL', icon: Copy, action: () => navigator.clipboard.writeText(MCP_URL) }, + { label: 'Rotate Token', icon: RotateCcw, action: () => {} }, + { label: 'Open Claude Setup', icon: ExternalLink, action: () => {} }, + { label: 'Open Codex Setup', icon: ExternalLink, action: () => {} }, + { label: 'View Logs', icon: Activity, action: () => {} }, + ].map(({ label, icon: Icon, action }) => ( + + ))} +
+
+ + {/* Usage Overview */} +
+

Usage Overview

+
+ {[40, 65, 45, 80, 55, 90, 70, 85, 60, 95, 75, 100, 60, 85].map((h, i) => ( +
+ ))} +
+
+ Jun 1Jun 15Jun 29 +
+

{requestsUsed.toLocaleString()}

+

Requests this month

+
+
+ + {/* Recent Activity */} +
+
+

Recent Activity

+ View all logs → +
+ + + + + + + + + + + {RECENT_ACTIVITY.map((row, i) => ( + + + + + + + ))} + +
TimeEventStatusDetails
{row.time}{row.event} + + {row.status} + + {row.detail}
+
+
+ ); +} + +function StatCard({ label, value, sub, icon, color }: { + label: string; value: React.ReactNode; sub: React.ReactNode; icon: React.ReactNode; color: string; +}) { + return ( +
+
+

{label}

+
{icon}
+
+

{value}

+
{sub}
+
+ ); +} + +function Row({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ); +} diff --git a/src/pages/dashboard/DashboardLayout.tsx b/src/pages/dashboard/DashboardLayout.tsx new file mode 100644 index 0000000..0e5d8d1 --- /dev/null +++ b/src/pages/dashboard/DashboardLayout.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from 'react'; +import { useNavigate, useLocation, Link, Outlet } from 'react-router-dom'; +import { session } from '@/lib/api'; +import { + LayoutDashboard, Globe, Cpu, BookOpen, Shield, BarChart2, + FileText, CreditCard, Key, Users, Settings, HelpCircle, LogOut, Menu, X, +} from 'lucide-react'; +import { clsx } from 'clsx'; + +const NAV = [ + { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard }, + { label: 'Odoo Connections', href: '/dashboard/connections', icon: Globe }, + { label: 'MCP Endpoints', href: '/dashboard/endpoints', icon: Cpu }, + { label: 'AI Setup Guides', href: '/dashboard/guides', icon: BookOpen }, + { label: 'Tools & Permissions', href: '/dashboard/tools', icon: Shield }, + { label: 'Usage', href: '/dashboard/usage', icon: BarChart2 }, + { label: 'Logs', href: '/dashboard/logs', icon: FileText }, + { label: 'Billing', href: '/dashboard/billing', icon: CreditCard }, + { label: 'API Tokens', href: '/dashboard/tokens', icon: Key }, + { label: 'Team', href: '/dashboard/team', icon: Users }, + { label: 'Settings', href: '/dashboard/settings', icon: Settings }, +]; + +export default function DashboardLayout() { + const navigate = useNavigate(); + const location = useLocation(); + const pathname = location.pathname; + const [email, setEmail] = useState(''); + const [sidebarOpen, setSidebarOpen] = useState(false); + + useEffect(() => { + const key = session.getKey(); + const em = session.getEmail(); + if (!key) { navigate('/login', { replace: true }); return; } + if (em) setEmail(em); + }, [navigate]); + + function logout() { + session.clear(); + navigate('/'); + } + + const Sidebar = ( + + ); + + return ( +
+ {/* Desktop sidebar */} +
+ {Sidebar} +
+ + {/* Mobile sidebar overlay */} + {sidebarOpen && ( +
+
{Sidebar}
+
setSidebarOpen(false)} /> +
+ )} + + {/* Main */} +
+ {/* Mobile topbar */} +
+ + OdooMCP Cloud +
+
+
+
+ ); +} diff --git a/src/pages/dashboard/Endpoints.tsx b/src/pages/dashboard/Endpoints.tsx new file mode 100644 index 0000000..99ec3af --- /dev/null +++ b/src/pages/dashboard/Endpoints.tsx @@ -0,0 +1,154 @@ +import { useState, useEffect } from 'react'; +import { session } from '@/lib/api'; +import { Copy, Check, RotateCcw } from 'lucide-react'; + +const MCP_URL = `${import.meta.env.VITE_API_URL ?? 'https://odoo-mcp.thedomainnest.com'}/mcp/sse`; + +const AI_CLIENTS = [ + { name: 'Claude', icon: '🤖', setup: 'claude' }, + { name: 'Codex', icon: '⚡', setup: 'codex' }, + { name: 'Cursor', icon: '🖱️', setup: 'cursor' }, + { name: 'Windsurf', icon: '🏄', setup: 'windsurf' }, + { name: 'Custom MCP', icon: '🔧', setup: 'custom' }, +]; + +export default function Endpoints() { + const [apiKey, setApiKey] = useState(''); + const [copiedUrl, setCopiedUrl] = useState(false); + const [copiedToken, setCopiedToken] = useState(false); + const [activeSetup, setActiveSetup] = useState('claude'); + + useEffect(() => { + const k = session.getKey(); + if (k) setApiKey(k); + }, []); + + function copy(text: string, setter: (v: boolean) => void) { + navigator.clipboard.writeText(text); + setter(true); + setTimeout(() => setter(false), 2000); + } + + const claudeJson = JSON.stringify({ + mcpServers: { + 'odoo-mcp': { + type: 'sse', + url: MCP_URL, + headers: { Authorization: `Bearer ${apiKey || 'your-api-key-here'}` }, + }, + }, + }, null, 2); + + return ( +
+
+

MCP Endpoints

+

Your secure MCP server connection details.

+
+ +
+ {/* Left: URL + token */} +
+
+

Your MCP Server URL

+
+ {MCP_URL} + +
+ +

Bearer Token

+
+ + {apiKey ? `${apiKey.slice(0, 20)}${'•'.repeat(20)}` : 'Loading…'} + + + +
+
+ + {/* Connect With */} +
+

Connect With

+
+ {AI_CLIENTS.map((c) => ( + + ))} +
+ + {activeSetup === 'claude' && ( +
+

+ Add this to your Claude Code settings (~/.claude/settings.json): +

+
+
{claudeJson}
+ +
+

+ Or run: claude mcp add --transport sse odoo-mcp {MCP_URL} -H "Authorization: Bearer YOUR_KEY" +

+
+ )} + {activeSetup !== 'claude' && ( +
+ Setup guide for {AI_CLIENTS.find(c => c.setup === activeSetup)?.name} coming soon. +
+ )} +
+
+ + {/* Right: status card */} +
+
+

Endpoint Status

+
+
+ + Active +
+ + Healthy} /> + + +
+
+ +
+

Security Notice

+

Do not commit your token to GitHub. Use environment variables in production.

+
+
+
+
+ ); +} + +function Row({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ); +} diff --git a/src/pages/dashboard/Logs.tsx b/src/pages/dashboard/Logs.tsx new file mode 100644 index 0000000..7b3083f --- /dev/null +++ b/src/pages/dashboard/Logs.tsx @@ -0,0 +1,148 @@ +import { Fragment, useState, useEffect, useCallback } from 'react'; +import { api, session, LogEntry } from '@/lib/api'; +import { ChevronLeft, ChevronRight, CheckCircle2, XCircle, FileText } from 'lucide-react'; + +const PAGE_SIZE = 25; + +export default function Logs() { + const [items, setItems] = useState([]); + const [page, setPage] = useState(1); + const [total, setTotal] = useState(0); + const [toolFilter, setToolFilter] = useState(''); + const [successFilter, setSuccessFilter] = useState<'all' | 'success' | 'error'>('all'); + const [expandedId, setExpandedId] = useState(null); + const [loading, setLoading] = useState(true); + + const load = useCallback(() => { + const key = session.getKey(); + if (!key) return; + setLoading(true); + api + .listLogs(key, page, PAGE_SIZE, { + tool_name: toolFilter || undefined, + success: successFilter === 'all' ? undefined : successFilter === 'success', + }) + .then((r) => { + setItems(r.items); + setTotal(r.total); + }) + .catch(console.error) + .finally(() => setLoading(false)); + }, [page, toolFilter, successFilter]); + + useEffect(load, [load]); + + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + + return ( +
+
+
+

Logs

+

Every MCP tool call made against your account.

+
+
+ + {/* Filters */} +
+ { + setPage(1); + setToolFilter(e.target.value); + }} + placeholder="Filter by tool name…" + className="border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent w-64" + /> + +
+ +
+ {items.length === 0 && !loading ? ( +
+ +

No log entries found

+
+ ) : ( + + + + + + + + + + + + {items.map((log) => ( + + log.error_message && setExpandedId(expandedId === log.id ? null : log.id)} + > + + + + + + + {expandedId === log.id && log.error_message && ( + + + + )} + + ))} + +
StatusToolInstanceDurationTime
+ {log.success ? ( + + ) : ( + + )} + {log.tool_name}{log.instance_name}{log.duration_ms} ms{log.started_at}
+ {log.error_message} +
+ )} +
+ + {/* Pagination */} + {total > PAGE_SIZE && ( +
+ + Page {page} of {totalPages} ({total} total) + +
+ + +
+
+ )} +
+ ); +} diff --git a/src/pages/dashboard/Tokens.tsx b/src/pages/dashboard/Tokens.tsx new file mode 100644 index 0000000..0c16860 --- /dev/null +++ b/src/pages/dashboard/Tokens.tsx @@ -0,0 +1,197 @@ +import { useState, useEffect } from 'react'; +import { api, session, ApiKeyInfo } from '@/lib/api'; +import { Plus, Trash2, Key, Copy, Check, AlertTriangle } from 'lucide-react'; + +export default function Tokens() { + const [keys, setKeys] = useState([]); + const [showForm, setShowForm] = useState(false); + const [name, setName] = useState(''); + const [loading, setLoading] = useState(false); + const [newKey, setNewKey] = useState(null); + const [copied, setCopied] = useState(false); + + function load() { + const key = session.getKey(); + if (!key) return; + api.listApiKeys(key).then((r) => setKeys(r.keys)).catch(console.error); + } + useEffect(load, []); + + async function handleCreate(e: React.FormEvent) { + e.preventDefault(); + const key = session.getKey(); + if (!key) return; + setLoading(true); + try { + const result = await api.createApiKey(key, name || 'Untitled key'); + setNewKey(result.api_key); + setName(''); + setShowForm(false); + load(); + } catch (err) { + alert(err instanceof Error ? err.message : 'Failed to create key'); + } finally { + setLoading(false); + } + } + + async function handleRevoke(id: number, keyName: string) { + if (!confirm(`Revoke "${keyName}"? Any client using this key will immediately lose access.`)) return; + const key = session.getKey(); + if (!key) return; + try { + await api.revokeApiKey(key, id); + load(); + } catch (err) { + alert(err instanceof Error ? err.message : 'Failed to revoke key'); + } + } + + function copyKey() { + if (!newKey) return; + navigator.clipboard.writeText(newKey); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( +
+
+
+

API Tokens

+

+ Create and revoke API keys used to connect MCP clients. +

+
+ +
+ + {newKey && ( +
+
+ +
+

+ Save this key now — it will not be shown again +

+
+ + {newKey} + + +
+ +
+
+
+ )} + + {showForm && ( +
+

Create API Key

+
+
+ + setName(e.target.value)} + placeholder="e.g. Claude Desktop, Production Server" + className="w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent" + /> +
+
+ + +
+
+
+ )} + + {keys.length === 0 && !showForm && ( +
+ +

No API keys yet

+

Create one to connect an MCP client.

+
+ )} + + {keys.length > 0 && ( +
+ + + + + + + + + + + + + {keys.map((k) => ( + + + + + + + + + ))} + +
NameKeyCreatedLast UsedStatus
{k.name}{k.key_prefix}...{k.created_at}{k.last_used_at ?? 'Never'} + {k.is_active ? ( + + Active + + ) : ( + + Revoked + + )} + + {k.is_active && ( + + )} +
+
+ )} +
+ ); +} diff --git a/src/pages/dashboard/Usage.tsx b/src/pages/dashboard/Usage.tsx new file mode 100644 index 0000000..dac1852 --- /dev/null +++ b/src/pages/dashboard/Usage.tsx @@ -0,0 +1,210 @@ +import { useState, useEffect, useCallback } from 'react'; +import { api, session, AnalyticsSummary, ToolStat, InstanceStat, TimeseriesBucket } from '@/lib/api'; +import { + LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, +} from 'recharts'; +import { Activity, CheckCircle2, XCircle, Clock } from 'lucide-react'; + +const RANGES = [ + { label: '7 days', days: 7 }, + { label: '30 days', days: 30 }, + { label: '90 days', days: 90 }, +]; + +function isoDaysAgo(days: number): string { + const d = new Date(); + d.setUTCDate(d.getUTCDate() - days); + return d.toISOString(); +} + +export default function Usage() { + const [rangeDays, setRangeDays] = useState(30); + const [summary, setSummary] = useState(null); + const [byTool, setByTool] = useState([]); + const [byInstance, setByInstance] = useState([]); + const [timeseries, setTimeseries] = useState([]); + const [loading, setLoading] = useState(true); + + const load = useCallback(() => { + const key = session.getKey(); + if (!key) return; + setLoading(true); + const since = isoDaysAgo(rangeDays); + Promise.all([ + api.analyticsSummary(key, since), + api.analyticsByTool(key, since), + api.analyticsByInstance(key, since), + api.analyticsTimeseries(key, since, undefined, rangeDays > 30 ? 'week' : 'day'), + ]) + .then(([s, t, i, ts]) => { + setSummary(s); + setByTool(t.tools); + setByInstance(i.instances); + setTimeseries(ts.buckets); + }) + .catch(console.error) + .finally(() => setLoading(false)); + }, [rangeDays]); + + useEffect(load, [load]); + + return ( +
+
+
+

Usage

+

Tool calls, durations, and activity across your MCP connections.

+
+
+ {RANGES.map((r) => ( + + ))} +
+
+ + {/* Summary cards */} +
+ + + + +
+ + {/* Time-series chart */} +
+

Calls Over Time

+ {timeseries.length === 0 && !loading ? ( + + ) : ( + + + + + + + + + + )} +
+ + {/* Per-tool bar chart + table */} +
+
+

Calls by Tool

+ {byTool.length === 0 && !loading ? ( + + ) : ( + + + + + + + + + + )} +
+ +
+

By Instance

+ {byInstance.length === 0 ? ( + + ) : ( +
+ {byInstance.map((inst) => ( +
+ {inst.instance_name} +
+ {inst.calls} calls + {inst.error_count} errors + {inst.avg_duration_ms.toFixed(0)} ms avg +
+
+ ))} +
+ )} +
+
+ + {/* Per-tool breakdown table */} +
+
+

Tool Breakdown

+
+ {byTool.length === 0 ? ( +
No tool calls in this range yet.
+ ) : ( + + + + + + + + + + + + + + {byTool.map((t) => ( + + + + + + + + + + ))} + +
ToolCallsSuccessErrorsAvg DurationTotal DurationLast Used
{t.tool_name}{t.calls}{t.success_count}{t.error_count}{t.avg_duration_ms.toFixed(1)} ms{(t.total_duration_ms / 1000).toFixed(2)} s{t.last_used_at}
+ )} +
+
+ ); +} + +function StatCard({ icon: Icon, label, value, color }: { + icon: typeof Activity; label: string; value: string | number; color: 'blue' | 'green' | 'red' | 'purple'; +}) { + const colors = { + blue: 'bg-blue-50 text-blue-600', + green: 'bg-green-50 text-green-600', + red: 'bg-red-50 text-red-600', + purple: 'bg-purple-50 text-purple-600', + }; + return ( +
+
+ +
+

{value}

+

{label}

+
+ ); +} + +function EmptyChart() { + return ( +
+ No data in this range yet. +
+ ); +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tailwind.config.cjs b/tailwind.config.cjs new file mode 100644 index 0000000..fae3964 --- /dev/null +++ b/tailwind.config.cjs @@ -0,0 +1,23 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,mdx}'], + theme: { + extend: { + colors: { + brand: { + 50: '#f5f3ff', + 100: '#ede9fe', + 200: '#ddd6fe', + 300: '#c4b5fd', + 400: '#a78bfa', + 500: '#8b5cf6', + 600: '#7c3aed', + 700: '#6d28d9', + 800: '#5b21b6', + 900: '#4c1d95', + }, + }, + }, + }, + plugins: [], +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..76100c5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "types": ["vite/client"], + "paths": { "@/*": ["./src/*"] } + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d5d81d2 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [react(), tsconfigPaths()], + server: { port: 3000 }, +});