diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7a7aed --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Dependencies +node_modules/ +Server/node_modules/ +Server/backend/node_modules/ + +# Build outputs +dist/ +build/ +*.log + +# Environment variables +.env +.env.local +.env.*.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Cursor +.cursor/debug.log diff --git a/Server b/Server deleted file mode 160000 index 6100df3..0000000 --- a/Server +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6100df3f8340ed5df41cc21c4042320d7013a363 diff --git a/Server/appwrite.json b/Server/appwrite.json new file mode 100644 index 0000000..ebc990c --- /dev/null +++ b/Server/appwrite.json @@ -0,0 +1,3 @@ +{ + "projectId": "696b82bb0036d2e547ad" +} diff --git a/Server/backend/package.json b/Server/backend/package.json new file mode 100644 index 0000000..62630b2 --- /dev/null +++ b/Server/backend/package.json @@ -0,0 +1,15 @@ +{ + "name": "eship-backend", + "version": "0.1.0", + "type": "module", + "main": "server.js", + "scripts": { + "dev": "node server.js", + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "node-appwrite": "^14.0.0", + "dotenv": "^16.3.1" + } +} diff --git a/Server/backend/server.js b/Server/backend/server.js new file mode 100644 index 0000000..ac12809 --- /dev/null +++ b/Server/backend/server.js @@ -0,0 +1,54 @@ +import express from "express"; +import { Client, Account, Databases } from "node-appwrite"; +import dotenv from "dotenv"; + +dotenv.config(); + +const app = express(); +app.use(express.json()); + +const PORT = process.env.PORT || 3000; + +function makeUserClient(jwt) { + const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID) + .setJWT(jwt); + return client; +} + +function makeAdminClient() { + const client = new Client() + .setEndpoint(process.env.APPWRITE_ENDPOINT) + .setProject(process.env.APPWRITE_PROJECT_ID) + .setKey(process.env.APPWRITE_API_KEY); + return client; +} + +app.post("/api/action", async (req, res) => { + try { + const auth = req.headers.authorization || ""; + const jwt = auth.startsWith("Bearer ") ? auth.slice(7) : ""; + if (!jwt) return res.status(401).json({ ok: false, error: "missing token" }); + + // 1) user token validieren + const userClient = makeUserClient(jwt); + const account = new Account(userClient); + const user = await account.get(); // wirft Fehler, wenn JWT ungueltig/abgelaufen + + // 2) privilegierte Aktion nur serverseitig mit Admin Key + const adminClient = makeAdminClient(); + const db = new Databases(adminClient); + + // Beispiel: lies etwas, das nur du lesen darfst + // const data = await db.listDocuments("dbId", "collectionId"); + + return res.json({ ok: true, userId: user.$id, info: "action allowed" }); + } catch (e) { + return res.status(401).json({ ok: false, error: "unauthorized" }); + } +}); + +app.listen(PORT, () => { + console.log(`Backend server running on port ${PORT}`); +}); diff --git a/Server/index.html b/Server/index.html new file mode 100644 index 0000000..b2cd577 --- /dev/null +++ b/Server/index.html @@ -0,0 +1,13 @@ + + + + + + + EShip - Login + + +
+ + + diff --git a/Server/package-lock.json b/Server/package-lock.json new file mode 100644 index 0000000..45a4a69 --- /dev/null +++ b/Server/package-lock.json @@ -0,0 +1,5634 @@ +{ + "name": "react-starter-kit-for-appwrite", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "react-starter-kit-for-appwrite", + "version": "0.0.0", + "dependencies": { + "@appwrite.io/pink-icons": "^1.0.0", + "@gsap/react": "^2.1.2", + "@tabler/icons-react": "^3.36.1", + "@tailwindcss/vite": "^4.0.14", + "appwrite": "^21.2.1", + "clsx": "^2.1.1", + "gsap": "^3.14.2", + "motion": "^12.26.2", + "ogl": "^1.0.11", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "styled-components": "^6.3.8", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.0.14" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.19.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "prettier": "3.5.3", + "vite": "^6.1.0" + } + }, + "node_modules/@appwrite.io/pink-icons": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-1.0.0.tgz", + "integrity": "sha512-+zpksP07MvOYwhx9AZDFW0pxXQNC2juKwyOQVRAwAOkN1ACSQKPlyytkI1u2ci6CQPWjJe20CzbvBBuRNXOKjA==", + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gsap/react": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@gsap/react/-/react-2.1.2.tgz", + "integrity": "sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==", + "license": "SEE LICENSE AT https://gsap.com/standard-license", + "peerDependencies": { + "gsap": "^3.12.5", + "react": ">=17" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tabler/icons": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.1.tgz", + "integrity": "sha512-f4Jg3Fof/Vru5ioix/UO4GX+sdDsF9wQo47FbtvG+utIYYVQ/QVAC0QYgcBbAjQGfbdOh2CCf0BgiFOF9Ixtjw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.36.1.tgz", + "integrity": "sha512-/8nOXeNeMoze9xY/QyEKG65wuvRhkT3q9aytaur6Gj8bYU2A98YVJyLc9MRmc5nVvpy+bRlrrwK/Ykr8WGyUWg==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "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/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", + "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", + "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", + "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/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/appwrite": { + "version": "21.5.0", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-21.5.0.tgz", + "integrity": "sha512-643bMRZVYXMluXvSXbdaLAi9qqTJLWbVGguKH4vH6IdKHur6gGIirhCOqAEt33pV4TOFJ55VBu8c/+Ft1ke2SA==", + "license": "BSD-3-Clause" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", + "integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/framer-motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.26.2.tgz", + "integrity": "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.26.2", + "motion-utils": "^12.24.10", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gsap": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz", + "integrity": "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==", + "license": "Standard 'no charge' license: https://gsap.com/standard-license.", + "peer": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "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-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "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/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "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==", + "dev": true, + "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/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.26.2.tgz", + "integrity": "sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.26.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.26.2.tgz", + "integrity": "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.24.10" + } + }, + "node_modules/motion-utils": { + "version": "12.24.10", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz", + "integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==", + "license": "MIT" + }, + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "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-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ogl": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz", + "integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==", + "license": "Unlicense" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "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/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "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/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-components": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.8.tgz", + "integrity": "sha512-Kq/W41AKQloOqKM39zfaMdJ4BcYDw/N5CIq4/GTI0YjU6pKcZ1KKhk6b4du0a+6RA9pIfOP/eu94Ge7cu+PDCA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@emotion/unitless": "0.10.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.6", + "tslib": "2.8.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/Server/package.json b/Server/package.json new file mode 100644 index 0000000..6aab6b4 --- /dev/null +++ b/Server/package.json @@ -0,0 +1,41 @@ +{ + "name": "react-starter-kit-for-appwrite", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@appwrite.io/pink-icons": "^1.0.0", + "@gsap/react": "^2.1.2", + "@tabler/icons-react": "^3.36.1", + "@tailwindcss/vite": "^4.0.14", + "appwrite": "^21.2.1", + "clsx": "^2.1.1", + "gsap": "^3.14.2", + "motion": "^12.26.2", + "ogl": "^1.0.11", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "styled-components": "^6.3.8", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.0.14" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.19.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "prettier": "3.5.3", + "vite": "^6.1.0" + } +} diff --git a/Server/public/assets/background.png b/Server/public/assets/background.png new file mode 100644 index 0000000..8f91c65 Binary files /dev/null and b/Server/public/assets/background.png differ diff --git a/Server/public/assets/logos/logo-horizontal.png b/Server/public/assets/logos/logo-horizontal.png new file mode 100644 index 0000000..0d6261d Binary files /dev/null and b/Server/public/assets/logos/logo-horizontal.png differ diff --git a/Server/public/assets/logos/logo-square.png b/Server/public/assets/logos/logo-square.png new file mode 100644 index 0000000..e63ad7d Binary files /dev/null and b/Server/public/assets/logos/logo-square.png differ diff --git a/Server/public/assets/logos/logo-vertical.png b/Server/public/assets/logos/logo-vertical.png new file mode 100644 index 0000000..00e00af Binary files /dev/null and b/Server/public/assets/logos/logo-vertical.png differ diff --git a/Server/src/App.jsx b/Server/src/App.jsx new file mode 100644 index 0000000..dda16f8 --- /dev/null +++ b/Server/src/App.jsx @@ -0,0 +1,376 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { Sidebar, SidebarBody, SidebarLink } from "./components/sidebar"; +import { + IconArrowLeft, + IconBrandTabler, + IconSettings, + IconUserBolt, + IconShoppingBag, +} from "@tabler/icons-react"; +import { motion } from "motion/react"; +import { cn } from "./lib/utils"; +import { BackgroundRippleEffect } from "./components/layout/BackgroundRippleEffect"; +import { Dashboard } from "./components/dashboard/Dashboard"; +import { AccountsPage } from "./pages/AccountsPage"; +import LogoutButton from "./components/ui/LogoutButton"; +import { OnboardingGate } from "./components/onboarding/OnboardingGate"; +import { SidebarHeader } from "./components/sidebar/SidebarHeader"; +import { useHashRoute } from "./lib/routing"; +import { account, databases, databaseId, usersCollectionId } from "./lib/appwrite"; + +export default function App() { + const { route, navigate } = useHashRoute(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [status, setStatus] = useState({ loading: true, authed: false, error: "" }); + const [sidebarOpen, setSidebarOpen] = useState(false); + + // Gate-Management State + const [authUser, setAuthUser] = useState(null); + const [hasUserDoc, setHasUserDoc] = useState(false); + const [checkingUserDoc, setCheckingUserDoc] = useState(false); + const [onboardingLoading, setOnboardingLoading] = useState(false); + const [onboardingError, setOnboardingError] = useState(""); + + const showGate = !hasUserDoc && !checkingUserDoc && authUser !== null; + + async function checkUserDocument(userId) { + if (!databases || !databaseId || !usersCollectionId) { + return false; + } + + try { + await databases.getDocument(databaseId, usersCollectionId, userId); + return true; // Dokument existiert + } catch (e) { + // 404 bedeutet, dass das Dokument nicht existiert - das ist OK + if (e.code === 404 || e.type === 'document_not_found') { + return false; // Dokument existiert nicht + } + // Andere Fehler: Loggen, aber nicht als kritischen Fehler behandeln + console.warn('Fehler beim Prüfen des User-Dokuments:', e.message || e); + return false; // Im Zweifel als "nicht vorhanden" behandeln + } + } + + async function refreshAuth() { + setStatus((s) => ({ ...s, loading: true, error: "" })); + setCheckingUserDoc(true); + setOnboardingError(""); + + try { + const user = await account.get(); // wirft Fehler, wenn keine Session + setAuthUser(user); + setStatus({ loading: false, authed: true, error: "" }); + + // Prüfe, ob User-Dokument existiert + const userDocExists = await checkUserDocument(user.$id); + setHasUserDoc(userDocExists); + + await handoffJwtToExtension(); + } catch (e) { + setStatus({ loading: false, authed: false, error: "" }); + setAuthUser(null); + setHasUserDoc(false); + } finally { + setCheckingUserDoc(false); + } + } + + async function login(e) { + e.preventDefault(); + setStatus((s) => ({ ...s, loading: true, error: "" })); + setCheckingUserDoc(true); + setOnboardingError(""); + + try { + await account.createEmailPasswordSession(email, password); + const user = await account.get(); + setAuthUser(user); + setStatus({ loading: false, authed: true, error: "" }); + + // Prüfe, ob User-Dokument existiert + const userDocExists = await checkUserDocument(user.$id); + setHasUserDoc(userDocExists); + + await handoffJwtToExtension(); + } catch (e) { + setStatus({ loading: false, authed: false, error: "Login fehlgeschlagen" }); + setAuthUser(null); + setHasUserDoc(false); + } finally { + setCheckingUserDoc(false); + } + } + + async function handleOnboardingStart() { + if (!authUser || !databases || !databaseId || !usersCollectionId) { + setOnboardingError("Fehler: Konfiguration unvollständig"); + return; + } + + setOnboardingLoading(true); + setOnboardingError(""); + + try { + await databases.createDocument( + databaseId, + usersCollectionId, + authUser.$id, // Document-ID = Auth-User-ID + { + user_name: authUser.name || "User" + } + ); + + // Erfolg: User-Dokument erstellt + setHasUserDoc(true); + setOnboardingError(""); + } catch (e) { + // 409 Conflict bedeutet, dass das Dokument bereits existiert + // Das ist ok, da wir idempotent sein wollen + if (e.code === 409 || e.type === 'document_already_exists') { + setHasUserDoc(true); + setOnboardingError(""); + } else if (e.code === 401 || e.type === 'general_unauthorized_scope') { + // 401 Unauthorized: Permissions nicht richtig gesetzt + setOnboardingError( + "Berechtigung verweigert. Bitte prüfe in Appwrite, ob die users Collection die richtigen Permissions hat. " + + "Siehe setup/USERS_COLLECTION_SETUP.md für Details." + ); + } else { + // Andere Fehler anzeigen + setOnboardingError(e.message || "Fehler beim Erstellen des Profils. Bitte versuche es erneut."); + } + } finally { + setOnboardingLoading(false); + } + } + + async function logout() { + setStatus((s) => ({ ...s, loading: true, error: "" })); + try { + await account.deleteSession("current"); + } catch {} + setStatus({ loading: false, authed: false, error: "" }); + setAuthUser(null); + setHasUserDoc(false); + setOnboardingError(""); + + // Extension informieren: Token weg + sendToExtension({ type: "AUTH_CLEARED" }); + } + + async function handoffJwtToExtension() { + // JWT ist userbezogen und kann serverseitig validiert werden + const jwt = await account.createJWT(); + sendToExtension({ type: "AUTH_JWT", jwt: jwt.jwt }); + } + + function sendToExtension(payload) { + // Sende Nachricht über window.postMessage (keine Extension ID nötig) + // Das Content Script der Extension lauscht darauf + window.postMessage( + { + source: "eship-webapp", + ...payload, + }, + "*" + ); + } + + useEffect(() => { + refreshAuth(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const links = [ + { + label: "Dashboard", + href: "#/", + icon: ( + + ), + onClick: (e) => { + e.preventDefault(); + navigate("/"); + }, + }, + { + label: "Accounts", + href: "#/accounts", + icon: ( + + ), + onClick: (e) => { + e.preventDefault(); + navigate("/accounts"); + }, + }, + { + label: "Profile", + href: "#", + icon: ( + + ), + }, + { + label: "Settings", + href: "#", + icon: ( + + ), + }, + { + label: "Logout", + href: "#", + icon: ( + + ), + onClick: (e) => { + e.preventDefault(); + logout(); + }, + }, + ]; + + // Rendere Content basierend auf Route + const renderContent = () => { + if (route === "/accounts") { + return ; + } + // Default: Dashboard + return ; + }; + + return ( +
+ {!status.authed && ( +
+
+
Login
+
Appwrite Session erforderlich
+ +
+ setEmail(e.target.value)} + autoComplete="email" + /> + setPassword(e.target.value)} + autoComplete="current-password" + /> + + {status.error &&
{status.error}
} +
+ +
+ Nach Login wird der Sperrbildschirm entfernt und die Extension erhaelt ein JWT. +
+
+
+ )} + + {status.authed && ( + <> + {showGate && ( + + )} +
+ +
+ + +
+ +
+ {links.map((link, idx) => ( + + ))} +
+
+
+ { + e.preventDefault(); + logout(); + }} /> +
+
+
+ {renderContent()} +
+
+ + )} +
+ ); +} + +const styles = { + page: { + minHeight: "100vh", + background: "#0b0f19", + color: "#e7aaf0", + fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif", + }, + lockOverlay: { + position: "fixed", + inset: 0, + background: "rgba(0,0,0,0.70)", + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: 18, + zIndex: 9999, + }, + card: { + width: "100%", + maxWidth: 420, + background: "rgba(255,255,255,0.07)", + border: "1px solid rgba(255,255,255,0.12)", + borderRadius: 18, + padding: 20, + backdropFilter: "blur(10px)", + }, + title: { fontSize: 20, fontWeight: 800 }, + sub: { marginTop: 6, opacity: 0.8, fontSize: 13 }, + form: { marginTop: 14, display: "grid", gap: 10 }, + input: { + height: 42, + borderRadius: 12, + border: "1px solid rgba(255,255,255,0.16)", + outline: "none", + padding: "0 12px", + background: "rgba(0,0,0,0.20)", + color: "#e7aaf0", + }, + button: { + height: 42, + borderRadius: 12, + border: "0", + cursor: "pointer", + fontWeight: 800, + background: "#e7aaf0", + color: "#0b0f19", + }, + error: { marginTop: 6, color: "#ffb3b3", fontSize: 13 }, + hint: { marginTop: 12, opacity: 0.75, fontSize: 12, lineHeight: 1.4 }, +}; diff --git a/Server/src/components/dashboard/Dashboard.jsx b/Server/src/components/dashboard/Dashboard.jsx new file mode 100644 index 0000000..84ead3a --- /dev/null +++ b/Server/src/components/dashboard/Dashboard.jsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from "react"; +import { OverviewSection } from "./sections/OverviewSection"; +import { AccountsSection } from "./sections/AccountsSection"; +import { ProductsSection } from "./sections/ProductsSection"; +import { InsightsSection } from "./sections/InsightsSection"; +import { useScrollSnap } from "./hooks/useScrollSnap"; +import { getAuthUser } from "../../lib/appwrite"; +import { fetchManagedAccounts } from "../../services/accountsService"; +import { resolveActiveAccount } from "../../services/accountService"; +import { useHashRoute } from "../../lib/routing"; + +export const Dashboard = () => { + const { navigate } = useHashRoute(); + const [productFilters, setProductFilters] = useState({}); + const [activeAccountId, setActiveAccountId] = useState(null); + const [hasAccounts, setHasAccounts] = useState(false); + const [loadingAccounts, setLoadingAccounts] = useState(true); + + const sectionIds = ["s1", "s2", "s3", "s4"]; + const { scrollToSection } = useScrollSnap(sectionIds); + + // Accounts laden und aktiven Account auflösen beim Mount und nach Updates + useEffect(() => { + async function loadAccountsAndResolveActive() { + setLoadingAccounts(true); + try { + const authUser = await getAuthUser(); + if (!authUser) { + setHasAccounts(false); + setActiveAccountId(null); + return; + } + + const accounts = await fetchManagedAccounts(authUser.$id); + setHasAccounts(accounts.length > 0); + + const activeAccount = resolveActiveAccount(accounts); + if (activeAccount) { + const accountId = activeAccount.$id || activeAccount.id; + setActiveAccountId(accountId); + } else { + setActiveAccountId(null); + } + } catch (e) { + console.error("Fehler beim Laden der Accounts:", e); + setHasAccounts(false); + setActiveAccountId(null); + } finally { + setLoadingAccounts(false); + } + } + + loadAccountsAndResolveActive(); + + // Listen for accounts update events (e.g., after onboarding) + const handleAccountsUpdated = () => { + loadAccountsAndResolveActive(); + }; + + window.addEventListener('accountsUpdated', handleAccountsUpdated); + + return () => { + window.removeEventListener('accountsUpdated', handleAccountsUpdated); + }; + }, []); + + // Event-Listener für Account-Wechsel + useEffect(() => { + function handleActiveAccountChanged(event) { + const { accountId } = event.detail; + setActiveAccountId(accountId); + // Dashboard-Daten würden hier neu geladen werden (später, wenn Products-Service existiert) + } + + window.addEventListener("activeAccountChanged", handleActiveAccountChanged); + return () => { + window.removeEventListener("activeAccountChanged", handleActiveAccountChanged); + }; + }, []); + + const handleJumpToSection = (sectionId) => { + scrollToSection(sectionId); + }; + + const handleFilterProducts = (filters) => { + setProductFilters(filters); + }; + + // Empty-State wenn kein Account vorhanden + if (!loadingAccounts && !hasAccounts) { + return ( +
+
+
+

+ No account connected +

+

+ Du musst zuerst einen Account hinzufügen, um das Dashboard zu nutzen. +

+ +
+
+
+ ); + } + + return ( +
+
+
+
+ + + + +
+
+
+
+ ); +}; diff --git a/Server/src/components/dashboard/hooks/usePagination.js b/Server/src/components/dashboard/hooks/usePagination.js new file mode 100644 index 0000000..a61c6fe --- /dev/null +++ b/Server/src/components/dashboard/hooks/usePagination.js @@ -0,0 +1,39 @@ +import { useState, useMemo } from "react"; + +export const usePagination = (items, itemsPerPage = 8) => { + const [currentPage, setCurrentPage] = useState(1); + + const totalPages = Math.max(1, Math.ceil(items.length / itemsPerPage)); + const currentPageItems = useMemo(() => { + const start = (currentPage - 1) * itemsPerPage; + return items.slice(start, start + itemsPerPage); + }, [items, currentPage, itemsPerPage]); + + const nextPage = () => { + setCurrentPage(prev => Math.min(prev + 1, totalPages)); + }; + + const prevPage = () => { + setCurrentPage(prev => Math.max(prev - 1, 1)); + }; + + const goToPage = (page) => { + setCurrentPage(Math.max(1, Math.min(page, totalPages))); + }; + + // Reset to page 1 when items change + const resetPage = () => { + setCurrentPage(1); + }; + + return { + currentPage, + totalPages, + currentPageItems, + nextPage, + prevPage, + goToPage, + resetPage, + setCurrentPage + }; +}; diff --git a/Server/src/components/dashboard/hooks/useScrollSnap.js b/Server/src/components/dashboard/hooks/useScrollSnap.js new file mode 100644 index 0000000..3e0730d --- /dev/null +++ b/Server/src/components/dashboard/hooks/useScrollSnap.js @@ -0,0 +1,41 @@ +import { useState, useEffect, useRef } from "react"; + +export const useScrollSnap = (sectionIds) => { + const [activeSection, setActiveSection] = useState(sectionIds[0] || ""); + const containerRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const sections = sectionIds.map(id => document.getElementById(id)).filter(Boolean); + + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter(e => e.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]; + + if (visible) { + setActiveSection(visible.target.id); + } + }, + { threshold: [0.55, 0.7] } + ); + + sections.forEach(section => observer.observe(section)); + + return () => { + sections.forEach(section => observer.unobserve(section)); + }; + }, [sectionIds]); + + const scrollToSection = (sectionId) => { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }; + + return { activeSection, scrollToSection, containerRef }; +}; diff --git a/Server/src/components/dashboard/sections/AccountsSection.jsx b/Server/src/components/dashboard/sections/AccountsSection.jsx new file mode 100644 index 0000000..ac71506 --- /dev/null +++ b/Server/src/components/dashboard/sections/AccountsSection.jsx @@ -0,0 +1,232 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { DataTable } from "../ui/DataTable"; +import { Pagination } from "../ui/Pagination"; +import { Filters } from "../ui/Filters"; +import { usePagination } from "../hooks/usePagination"; +import { cn } from "../../../lib/utils"; +import { fetchManagedAccounts } from "../../../services/accountsService"; +import { getActiveAccountId, setActiveAccountId, getAccountDisplayName } from "../../../services/accountService"; +import { getAuthUser } from "../../../lib/appwrite"; + +export const AccountsSection = ({ onJumpToSection, activeAccountId }) => { + const [platformFilter, setPlatformFilter] = useState("all"); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [accounts, setAccounts] = useState([]); + + // Lade Accounts beim Mount + useEffect(() => { + async function loadAccounts() { + setLoading(true); + setError(null); + try { + const authUser = await getAuthUser(); + if (!authUser) { + setAccounts([]); + return; + } + + const loadedAccounts = await fetchManagedAccounts(authUser.$id); + setAccounts(loadedAccounts); + } catch (e) { + console.error("Fehler beim Laden der Accounts:", e); + setError(e.message || "Fehler beim Laden der Accounts"); + } finally { + setLoading(false); + } + } + + loadAccounts(); + }, []); + + const filteredAccounts = useMemo(() => { + return accounts.filter((a) => { + if (platformFilter !== "all" && a.account_platform !== platformFilter) return false; + return true; + }); + }, [accounts, platformFilter]); + + const { currentPage, totalPages, currentPageItems, nextPage, prevPage, resetPage } = + usePagination(filteredAccounts, 8); + + const filters = [ + { + id: "platform", + type: "select", + value: platformFilter, + options: [ + { value: "all", label: "Platform: all" }, + { value: "amazon", label: "Platform: amazon" }, + { value: "ebay", label: "Platform: ebay" }, + ], + }, + ]; + + const handleFilterChange = (id, value) => { + if (id === "platform") { + setPlatformFilter(value); + } + resetPage(); + }; + + const handleQuickSwitch = (accountId) => { + setActiveAccountId(accountId); + // Event wird automatisch durch setActiveAccountId getriggert + }; + + const currentActiveId = getActiveAccountId(); + + return ( +
+
+
+

Accounts

+ + snap page + +
+
+ + + + +
+
+ +
+
+

Accounts

+

+ Managed vs scanned accounts, with quick drill-down. +

+
+ + {loading && ( + + loading... + + )} +
+ +
+
+
+
+
+

Managed accounts

+
+
+ {loading ? ( +
Loading...
+ ) : error ? ( +
{error}
+ ) : accounts.length === 0 ? ( +
No accounts yet
+ ) : ( + <> + { + if (col === "Action") { + const accountId = row.$id || row.id; + const isActive = accountId === currentActiveId; + return ( + + ); + } + if (col === "Shop") { + return getAccountDisplayName(row); + } + if (col === "Platform") { + return row.account_platform || "-"; + } + if (col === "Market") { + return row.account_platform_market || "-"; + } + return row[col.toLowerCase()] || "-"; + }} + /> + + + )} +
+
+
+ +
+
+
+

Account management

+
+ {currentActiveId + ? `Active account: ${getAccountDisplayName(accounts.find((a) => (a.$id || a.id) === currentActiveId)) || currentActiveId}` + : "No account selected. Use 'Switch' to activate an account."} +
+
+ +
{filteredAccounts.length} accounts
+
+
+
+
+
+ ); +}; diff --git a/Server/src/components/dashboard/sections/InsightsSection.jsx b/Server/src/components/dashboard/sections/InsightsSection.jsx new file mode 100644 index 0000000..1e86e67 --- /dev/null +++ b/Server/src/components/dashboard/sections/InsightsSection.jsx @@ -0,0 +1,240 @@ +import React, { useState, useEffect } from "react"; +import { InsightCard } from "../ui/InsightCard"; +import { cn } from "../../../lib/utils"; +import { getInsights } from "../../../services/dashboardService"; + +export const InsightsSection = ({ onJumpToSection, onFilterProducts, activeAccountId }) => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [insights, setInsights] = useState(null); + + // Lade Insights wenn activeAccountId sich ändert + useEffect(() => { + if (!activeAccountId) { + setInsights(null); + setLoading(false); + setError(null); + return; + } + + setLoading(true); + setError(null); + + getInsights(activeAccountId) + .then((data) => { + setInsights(data); + }) + .catch((e) => { + console.error("Fehler beim Laden der Insights:", e); + setError(e.message || "Fehler beim Laden der Daten"); + }) + .finally(() => { + setLoading(false); + }); + }, [activeAccountId]); + + + return ( +
+
+
+

Insights

+ {loading && ( + + loading... + + )} +
+
+ + + + +
+
+ +
+
+

Winning / Intelligence

+

+ Preset insights that jump back into Products. +

+
+
+ + {error && ( +
+ Fehler: {error} +
+ )} + + {!activeAccountId && !loading && ( +
+

No account selected

+
+ )} + + {loading && ( +
+ {[1, 2].map((i) => ( +
+ ))} +
+ )} + + {activeAccountId && !loading && !error && insights && ( + <> +
+ ({ + label: item.label, + value: item.value, + }))} + actionButton={{ + element: ( + + ), + info: "View all", + }} + /> + + 0 ? `${insights.priceSpread.length} groups` : "0"} + subtitle="Price variations for similar items" + items={insights.priceSpread.slice(0, 5).map((item) => ({ + label: item.label, + value: item.value, + }))} + actionButton={{ + element: ( + + ), + info: insights.priceSpread.length > 0 ? "Top spread" : "No data", + }} + /> +
+ +
+ ({ + label: item.label, + value: item.value, + }))} + actionButton={{ + element: ( + + ), + info: "Top 5", + }} + /> + + t.product?.product_status === "active").length} + subtitle="Currently active from recent products" + items={insights.trending + .filter((t) => t.product?.product_status === "active") + .slice(0, 5) + .map((item) => ({ + label: item.label, + value: item.value, + }))} + actionButton={{ + element: ( + + ), + info: "Filter: active", + }} + /> +
+ + )} + + {activeAccountId && !loading && !error && insights && + insights.trending.length === 0 && + insights.priceSpread.length === 0 && + insights.categoryShare.length === 0 && ( +
+

No insights available yet

+
+ )} +
+ ); +}; diff --git a/Server/src/components/dashboard/sections/OverviewSection.jsx b/Server/src/components/dashboard/sections/OverviewSection.jsx new file mode 100644 index 0000000..77c5d84 --- /dev/null +++ b/Server/src/components/dashboard/sections/OverviewSection.jsx @@ -0,0 +1,211 @@ +import React, { useState, useEffect } from "react"; +import { KPICard } from "../ui/KPICard"; +import { cn } from "../../../lib/utils"; +import { getOverviewKPIs } from "../../../services/dashboardService"; + +export const OverviewSection = ({ onJumpToSection, activeAccountId }) => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [kpis, setKpis] = useState(null); + const [newestProducts, setNewestProducts] = useState([]); + + // Lade KPIs wenn activeAccountId sich ändert + useEffect(() => { + if (!activeAccountId) { + setKpis(null); + setNewestProducts([]); + setLoading(false); + setError(null); + return; + } + + setLoading(true); + setError(null); + + getOverviewKPIs(activeAccountId) + .then((data) => { + setKpis(data); + setNewestProducts(data.newestProducts || []); + }) + .catch((e) => { + console.error("Fehler beim Laden der KPIs:", e); + setError(e.message || "Fehler beim Laden der Daten"); + }) + .finally(() => { + setLoading(false); + }); + }, [activeAccountId]); + + return ( +
+
+
+

Dashboard

+ {loading && ( + + loading... + + )} +
+
+ + + +
+
+ +
+
+

Overview

+

+ System health and high-level metrics. +

+
+
+ + {error && ( +
+ Fehler: {error} +
+ )} + + {!activeAccountId && !loading && ( +
+

No account selected

+
+ )} + + {activeAccountId && loading && ( +
+ {[1, 2, 3, 4, 5, 6].map((i) => ( +
+ ))} +
+ )} + + {activeAccountId && !loading && !error && kpis && ( + <> +
+ + + + + + 0 ? "Active" : "Empty"} + sub={kpis.totalProducts > 0 ? "Account has products" : "No products yet"} + /> +
+ + )} + + {activeAccountId && !loading && !error && kpis && kpis.totalProducts === 0 && ( +
+

No products yet for this account.

+
+ )} + +
+
+
+
+

Product overview

+
+ {loading ? "Loading..." : kpis ? `${kpis.totalProducts} total products` : "No data"} +
+
+ +
+
+
+ +
+
+
+

Newest items

+ {loading ? ( +
Loading...
+ ) : newestProducts.length > 0 ? ( +
+ {newestProducts.map((p) => `- ${p.product_title || p.$id}${p.product_price ? ` EUR ${p.product_price}` : ""}`).join("\n")} +
+ ) : ( +
No products yet
+ )} +
+ +
Top 5
+
+
+
+
+
+ ); +}; diff --git a/Server/src/components/dashboard/sections/ProductsSection.jsx b/Server/src/components/dashboard/sections/ProductsSection.jsx new file mode 100644 index 0000000..e2ee43b --- /dev/null +++ b/Server/src/components/dashboard/sections/ProductsSection.jsx @@ -0,0 +1,394 @@ +import React, { useState, useEffect } from "react"; +import { DataTable } from "../ui/DataTable"; +import { Pagination } from "../ui/Pagination"; +import { Filters } from "../ui/Filters"; +import { cn } from "../../../lib/utils"; +import { getProductsPage, getProductPreview } from "../../../services/dashboardService"; +import { scanProductsForAccount } from "../../../services/productsService"; +import { AnimatePresence, motion } from "motion/react"; + +export const ProductsSection = ({ onJumpToSection, activeAccountId }) => { + const [statusFilter, setStatusFilter] = useState("all"); + const [searchQuery, setSearchQuery] = useState(""); + const [page, setPage] = useState(1); + const [pageSize] = useState(8); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [products, setProducts] = useState([]); + const [total, setTotal] = useState(0); + const [selectedProduct, setSelectedProduct] = useState(null); + const [previewLoading, setPreviewLoading] = useState(false); + const [scanning, setScanning] = useState(false); + const [scanToast, setScanToast] = useState({ show: false, message: "", type: "success" }); + + // Lade Products wenn activeAccountId oder Filter sich ändern + useEffect(() => { + loadProducts(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeAccountId, page, pageSize, statusFilter, searchQuery]); + + const totalPages = Math.ceil(total / pageSize); + const nextPage = () => { + if (page < totalPages) setPage(page + 1); + }; + const prevPage = () => { + if (page > 1) setPage(page - 1); + }; + + const filters = [ + { + id: "status", + type: "select", + value: statusFilter, + options: [ + { value: "all", label: "Status: all" }, + { value: "active", label: "Active" }, + { value: "ended", label: "Ended" }, + { value: "unknown", label: "Unknown" }, + ], + }, + { + id: "search", + type: "input", + value: searchQuery, + placeholder: "Search title", + }, + ]; + + const handleFilterChange = (id, value) => { + if (id === "status") { + setStatusFilter(value); + setPage(1); // Reset to first page + } else if (id === "search") { + setSearchQuery(value); + setPage(1); // Reset to first page + } + }; + + const handleClearFilters = () => { + setStatusFilter("all"); + setSearchQuery(""); + setPage(1); + }; + + const handleOpenProduct = async (productId) => { + setPreviewLoading(true); + setSelectedProduct(null); + try { + const preview = await getProductPreview(productId); + setSelectedProduct(preview); + } catch (e) { + console.error("Fehler beim Laden des Product Previews:", e); + setError(e.message || "Fehler beim Laden des Previews"); + } finally { + setPreviewLoading(false); + } + }; + + const loadProducts = async () => { + if (!activeAccountId) { + setProducts([]); + setTotal(0); + setLoading(false); + setError(null); + return; + } + + setLoading(true); + setError(null); + + try { + const data = await getProductsPage(activeAccountId, { + page, + pageSize, + filters: { + status: statusFilter !== "all" ? statusFilter : undefined, + search: searchQuery.trim() || undefined, + }, + }); + setProducts(data.items); + setTotal(data.total); + } catch (e) { + console.error("Fehler beim Laden der Products:", e); + setError(e.message || "Fehler beim Laden der Daten"); + } finally { + setLoading(false); + } + }; + + const handleScanProducts = async () => { + if (!activeAccountId || scanning) { + return; + } + + setScanning(true); + setError(null); + + try { + // Führe Scan aus + const result = await scanProductsForAccount(activeAccountId); + + // Refresh Products-Liste + await loadProducts(); + + // Zeige Erfolgs-Toast + const updated = result.updated ?? 0; + setScanToast({ + show: true, + message: `Scan abgeschlossen: ${result.created} neu, ${updated} aktualisiert`, + type: "success", + }); + setTimeout(() => { + setScanToast({ show: false, message: "", type: "success" }); + }, 3000); + } catch (e) { + console.error("Fehler beim Scannen der Produkte:", e); + + // Logge meta für Dev-Debugging + const meta = e.meta || {}; + if (Object.keys(meta).length > 0) { + console.log("[scan meta]", meta); + } + + // Prüfe auf no_items_found oder empty_items + const errorMsg = e.message || "Fehler beim Scannen der Produkte"; + const isNoItems = errorMsg.includes("no_items_found") || errorMsg.includes("empty_items"); + + // Zeige Toast mit spezifischer Meldung für 0 Items + const toastMessage = isNoItems + ? "0 Produkte gefunden. Bitte pruefe, ob die URL auf den Shop/Artikel-Bereich des Sellers zeigt." + : errorMsg; + + setScanToast({ + show: true, + message: toastMessage, + type: "error", + }); + setTimeout(() => { + setScanToast({ show: false, message: "", type: "success" }); + }, 3000); + } finally { + setScanning(false); + } + }; + + return ( +
+
+
+

Products

+ + explorer + +
+
+ + + + +
+
+ +
+
+

Products

+

+ Filter and page through products. No inner scrolling. +

+
+
+ + +
+
+ +
+
+
+
+
+

Product list

+
+
+ {loading ? ( +
Loading...
+ ) : error ? ( +
{error}
+ ) : total === 0 && activeAccountId ? ( +
+

+ Keine Produkte gefunden +

+

+ Scanne den Account, um Produkte zu importieren. +

+ +
+ ) : !activeAccountId ? ( +
+ No account selected +
+ ) : ( + <> + { + if (col === "Action") { + return ( + + ); + } + if (col === "Title") { + return row.product_title || row.$id; + } + if (col === "Price") { + return row.product_price ? `EUR ${row.product_price}` : "N/A"; + } + if (col === "Status") { + return row.product_status || "unknown"; + } + if (col === "Category") { + return row.product_category || "-"; + } + return row[col.toLowerCase()] || "-"; + }} + /> + + + )} +
+
+
+ +
+
+
+

Product preview

+ {previewLoading ? ( +
Loading preview...
+ ) : selectedProduct ? ( +
+
+ Title: {selectedProduct.product_title || selectedProduct.$id} +
+ {selectedProduct.product_price && ( +
+ Price: EUR {selectedProduct.product_price} +
+ )} + {selectedProduct.product_status && ( +
+ Status: {selectedProduct.product_status} +
+ )} + {selectedProduct.product_category && ( +
+ Category: {selectedProduct.product_category} +
+ )} + {selectedProduct.details && ( +
+ Details available +
+ )} +
+ ) : ( +
+ Click "Open" on a product to preview details. +
+ )} +
+ + {selectedProduct && ( +
Preview loaded
+ )} +
+
+
+
+ + {/* Toast Notification */} + + {scanToast.show && ( + + {scanToast.message} + + )} + +
+ ); +}; diff --git a/Server/src/components/dashboard/ui/DataTable.jsx b/Server/src/components/dashboard/ui/DataTable.jsx new file mode 100644 index 0000000..4e2ae5c --- /dev/null +++ b/Server/src/components/dashboard/ui/DataTable.jsx @@ -0,0 +1,36 @@ +import React from "react"; + +export const DataTable = ({ columns, data, renderCell }) => { + return ( +
+ + + + {columns.map((col, idx) => ( + + ))} + + + + {data.map((row, rowIdx) => ( + + {columns.map((col, colIdx) => ( + + ))} + + ))} + +
+ {col} +
+ {renderCell ? renderCell(col, row, rowIdx) : row[col]} +
+
+ ); +}; diff --git a/Server/src/components/dashboard/ui/Filters.jsx b/Server/src/components/dashboard/ui/Filters.jsx new file mode 100644 index 0000000..10a52c5 --- /dev/null +++ b/Server/src/components/dashboard/ui/Filters.jsx @@ -0,0 +1,39 @@ +import React from "react"; + +export const Filters = ({ filters, onChange }) => { + return ( +
+ {filters.map((filter, idx) => { + if (filter.type === "select") { + return ( + + ); + } + if (filter.type === "input") { + return ( + onChange(filter.id, e.target.value)} + className="rounded-xl border border-[var(--line)] bg-transparent px-3 py-2.5 text-xs text-[var(--text)] outline-none transition-colors hover:border-[rgba(106,166,255,0.55)]" + /> + ); + } + return null; + })} +
+ ); +}; diff --git a/Server/src/components/dashboard/ui/InsightCard.jsx b/Server/src/components/dashboard/ui/InsightCard.jsx new file mode 100644 index 0000000..5ed4100 --- /dev/null +++ b/Server/src/components/dashboard/ui/InsightCard.jsx @@ -0,0 +1,42 @@ +import React from "react"; + +export const InsightCard = ({ title, value, subtitle, items, actionButton }) => { + return ( +
+
+
+

{title}

+ {value &&
{value}
} + {subtitle &&
{subtitle}
} + {items && items.length > 0 && ( +
+ {items.map((item, idx) => ( +
+ {item.label} + {item.value && {item.value}} +
+ ))} +
+ )} + {actionButton && ( +
+
+ {actionButton.element || actionButton} +
+ {actionButton.info && ( +
{actionButton.info}
+ )} +
+ )} +
+
+ ); +}; diff --git a/Server/src/components/dashboard/ui/KPICard.jsx b/Server/src/components/dashboard/ui/KPICard.jsx new file mode 100644 index 0000000..68c0f4e --- /dev/null +++ b/Server/src/components/dashboard/ui/KPICard.jsx @@ -0,0 +1,19 @@ +import React from "react"; + +export const KPICard = ({ title, value, sub }) => { + return ( +
+
+
+

{title}

+

{value}

+ {sub &&

{sub}

} +
+
+ ); +}; diff --git a/Server/src/components/dashboard/ui/Pagination.jsx b/Server/src/components/dashboard/ui/Pagination.jsx new file mode 100644 index 0000000..44dce5d --- /dev/null +++ b/Server/src/components/dashboard/ui/Pagination.jsx @@ -0,0 +1,27 @@ +import React from "react"; + +export const Pagination = ({ currentPage, totalPages, onPrev, onNext, info }) => { + return ( +
+
+ {info && {info}} +
+
+ + +
+
+ ); +}; diff --git a/Server/src/components/layout/BackgroundImage.jsx b/Server/src/components/layout/BackgroundImage.jsx new file mode 100644 index 0000000..a0c9988 --- /dev/null +++ b/Server/src/components/layout/BackgroundImage.jsx @@ -0,0 +1,19 @@ +import React from "react"; + +export const BackgroundImage = () => { + return ( +
+ ); +}; diff --git a/Server/src/components/layout/BackgroundRippleEffect.jsx b/Server/src/components/layout/BackgroundRippleEffect.jsx new file mode 100644 index 0000000..45f96ca --- /dev/null +++ b/Server/src/components/layout/BackgroundRippleEffect.jsx @@ -0,0 +1,120 @@ +"use client"; +import React, { useMemo, useRef, useState, useEffect } from "react"; +import { cn } from "@/lib/utils"; + +export const BackgroundRippleEffect = ({ + cellSize = 56 +}) => { + const [clickedCell, setClickedCell] = useState(null); + const [rippleKey, setRippleKey] = useState(0); + const ref = useRef(null); + const [viewportSize, setViewportSize] = useState({ width: 0, height: 0 }); + + // Calculate dynamic grid size to cover full viewport + useEffect(() => { + const updateSize = () => { + setViewportSize({ width: window.innerWidth, height: window.innerHeight }); + }; + updateSize(); + window.addEventListener('resize', updateSize); + return () => window.removeEventListener('resize', updateSize); + }, []); + + const calculatedCols = viewportSize.width > 0 ? Math.ceil(viewportSize.width / cellSize) + 2 : 27; + const calculatedRows = viewportSize.height > 0 ? Math.ceil(viewportSize.height / cellSize) + 2 : 8; + + return ( +
+
+
+ { + setClickedCell({ row, col }); + setRippleKey((k) => k + 1); + }} + interactive /> +
+
+ ); +}; + +const DivGrid = ({ + className, + rows = 7, + cols = 30, + cellSize = 56, + borderColor = "#3f3f46", + fillColor = "rgba(14,165,233,0.3)", + clickedCell = null, + onCellClick = () => {}, + interactive = true +}) => { + const cells = useMemo(() => Array.from({ length: rows * cols }, (_, idx) => idx), [rows, cols]); + + const gridStyle = { + display: "grid", + gridTemplateColumns: `repeat(${cols}, ${cellSize}px)`, + gridTemplateRows: `repeat(${rows}, ${cellSize}px)`, + width: "100vw", + height: "100vh", + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, + }; + + return ( +
+ {cells.map((idx) => { + const rowIdx = Math.floor(idx / cols); + const colIdx = idx % cols; + const distance = clickedCell + ? Math.hypot(clickedCell.row - rowIdx, clickedCell.col - colIdx) + : 0; + const delay = clickedCell ? Math.max(0, distance * 55) : 0; // ms + const duration = 200 + distance * 80; // ms + + const style = clickedCell + ? { + "--delay": `${delay}ms`, + "--duration": `${duration}ms`, + } + : {}; + + return ( +
onCellClick?.(rowIdx, colIdx) : undefined + } /> + ); + })} +
+ ); +}; diff --git a/Server/src/components/layout/Logo.jsx b/Server/src/components/layout/Logo.jsx new file mode 100644 index 0000000..7ecd7c0 --- /dev/null +++ b/Server/src/components/layout/Logo.jsx @@ -0,0 +1,16 @@ +import React from "react"; +import { motion } from "motion/react"; + +export const Logo = () => { + return ( + + Logo + + ); +}; diff --git a/Server/src/components/layout/LogoSquare.jsx b/Server/src/components/layout/LogoSquare.jsx new file mode 100644 index 0000000..9813675 --- /dev/null +++ b/Server/src/components/layout/LogoSquare.jsx @@ -0,0 +1,15 @@ +import React from "react"; + +export const LogoSquare = () => { + return ( + + Logo Square + + ); +}; diff --git a/Server/src/components/layout/LogoVertical.jsx b/Server/src/components/layout/LogoVertical.jsx new file mode 100644 index 0000000..2f577cb --- /dev/null +++ b/Server/src/components/layout/LogoVertical.jsx @@ -0,0 +1,15 @@ +import React from "react"; + +export const LogoVertical = () => { + return ( + + Logo Vertical + + ); +}; diff --git a/Server/src/components/onboarding/OnboardingGate.jsx b/Server/src/components/onboarding/OnboardingGate.jsx new file mode 100644 index 0000000..c311c75 --- /dev/null +++ b/Server/src/components/onboarding/OnboardingGate.jsx @@ -0,0 +1,477 @@ +"use client"; +import React, { useState } from "react"; +import { motion, AnimatePresence } from "motion/react"; +import { BackgroundRippleEffect } from "@/components/layout/BackgroundRippleEffect"; +import Shuffle from "@/components/ui/Shuffle"; +import ColourfulText from "@/components/ui/colourful-text"; +import { PlaceholdersAndVanishInput } from "@/components/ui/placeholders-and-vanish-input"; +import { MultiStepLoader } from "@/components/ui/multi-step-loader"; +import { IPhoneNotification } from "@/components/ui/iphone-notification"; +import { parseEbayAccount } from "@/services/ebayParserService"; +import { createManagedAccount, fetchManagedAccounts } from "@/services/accountsService"; +import { account, databases, databaseId, usersCollectionId } from "@/lib/appwrite"; + +export const OnboardingGate = ({ userName, onStart, loading, error }) => { + const [phase, setPhase] = useState("shuffle"); + const [submitting, setSubmitting] = useState(false); + const [submitError, setSubmitError] = useState(""); + const [showNotification, setShowNotification] = useState(false); + const [notificationMessage, setNotificationMessage] = useState("bitte eine gueltige url verwenden!"); + const [skipHovered, setSkipHovered] = useState(false); + + const loadingStates = [ + { text: "URL wird verarbeitet..." }, + { text: "Account-Daten werden geladen..." }, + { text: "Account wird erstellt..." }, + { text: "Fast fertig..." }, + ]; + + const placeholders = [ + "Gib deine eBay-Account-URL ein...", + "z.B. https://www.ebay.de/str/username", + "Paste deine eBay Shop-URL hier...", + ]; + + const handleOverlayClick = async (e) => { + // Only handle clicks on the overlay itself or when in shuffle phase + if (phase === "shuffle") { + // Create user document first (but don't call onStart, as that would hide the gate) + // We need to create it directly to ensure it exists for account creation later + try { + const authUser = await account.get(); + if (authUser && databases && databaseId && usersCollectionId) { + try { + await databases.createDocument( + databaseId, + usersCollectionId, + authUser.$id, + { + user_name: authUser.name || "User" + } + ); + } catch (docErr) { + // 409 Conflict means document already exists - that's ok + if (docErr.code !== 409 && docErr.type !== 'document_already_exists') { + console.warn("Fehler beim Erstellen des User-Dokuments:", docErr); + } + } + } + } catch (err) { + console.error("Fehler beim Abrufen des Users oder Erstellen des Dokuments:", err); + // Continue anyway - might already exist + } + + // Add small delay to let the shuffle exit animation complete + // Exit animation is 0.5s, so we wait a bit longer to see it fully + setTimeout(() => { + setPhase("input"); + }, 600); // 600ms delay - slightly longer than exit animation (500ms) + } + }; + + const handleSkip = async () => { + // Skip onboarding - directly call onStart to proceed to dashboard + // Must await to ensure User document is created before proceeding + if (onStart) { + await onStart(); + } + }; + + // Validate eBay URL + const isValidEbayUrl = (url) => { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:45',message:'isValidEbayUrl: entry',data:{url,hasUrl:!!url,trimmedUrl:url?.trim()},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + + if (!url || !url.trim()) { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:48',message:'isValidEbayUrl: empty url',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + return false; + } + + // Pattern for hostname only (without protocol/path) + const ebayHostnamePattern = /^(www\.)?(ebay\.(de|com|co\.uk|fr|it|es|nl|at|ch|com\.au|ca)|ebay-kleinanzeigen\.de)$/i; + + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname; + const patternMatches = ebayHostnamePattern.test(hostname); + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:62',message:'isValidEbayUrl: validation result',data:{url,hostname,patternMatches,pattern:ebayHostnamePattern.toString()},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + + return patternMatches; + } catch (e) { + // Invalid URL format + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:62',message:'isValidEbayUrl: URL parse error',data:{url,error:e.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + return false; + } + }; + + const handleAccountSubmit = async (e, url) => { + e.preventDefault(); + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:59',message:'handleAccountSubmit: entry',data:{url,hasUrl:!!url,phase},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); + // #endregion + + if (!url || !url.trim()) { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:64',message:'handleAccountSubmit: empty url, showing notification',data:{},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); + // #endregion + setNotificationMessage("bitte eine gueltige url verwenden!"); + setShowNotification(true); + return; + } + + // Validate eBay URL + const isValid = isValidEbayUrl(url); + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:71',message:'handleAccountSubmit: validation result',data:{url,isValid},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + + if (!isValid) { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:76',message:'handleAccountSubmit: invalid url, showing notification',data:{url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + setNotificationMessage("bitte eine gueltige url verwenden!"); + setShowNotification(true); + return; + } + + setSubmitting(true); + setSubmitError(""); + + // Wait 2 seconds for vanish animation, then switch to loading phase + setTimeout(async () => { + // Switch to loading phase first + setPhase("loading"); + + try { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:88',message:'handleAccountSubmit: before parseEbayAccount',data:{url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); + // #endregion + + // Parse eBay account + const accountData = await parseEbayAccount(url); + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:93',message:'handleAccountSubmit: parseEbayAccount success',data:{url,hasAccountData:!!accountData,market:accountData?.market,sellerId:accountData?.sellerId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); + // #endregion + + // Validate that sellerId was extracted successfully + if (!accountData.sellerId || accountData.sellerId.trim() === "") { + throw new Error("Die eBay-URL konnte nicht verarbeitet werden. Bitte stelle sicher, dass es eine gültige eBay-Shop-URL ist."); + } + + // Get current user + const authUser = await account.get(); + + // Check for duplicate account before creating + const existingAccounts = await fetchManagedAccounts(authUser.$id); + const duplicateAccount = existingAccounts.find( + (acc) => + acc.account_platform_account_id === accountData.sellerId && + acc.account_platform_market === accountData.market + ); + + if (duplicateAccount) { + throw new Error("Dieser Account ist bereits verbunden."); + } + + // Create account in database + await createManagedAccount(authUser.$id, { + account_platform_market: accountData.market, + account_platform_account_id: accountData.sellerId, + account_shop_name: accountData.shopName || null, + account_url: url, + account_status: accountData.status || "active", + }); + + // Account erstellt erfolgreich, rufe onStart auf + onStart && onStart(); + + // Trigger event to reload Dashboard accounts + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:142',message:'handleAccountSubmit: account created, triggering reload event',data:{url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{}); + // #endregion + window.dispatchEvent(new CustomEvent('accountsUpdated')); + } catch (err) { + console.error("Fehler beim Erstellen des Accounts:", err); + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:106',message:'handleAccountSubmit: error caught',data:{url,errorMessage:err.message,errorCode:err.code,errorType:err.type,errorStack:err.stack?.substring(0,200)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); + // #endregion + + // Show notification for URL validation, duplicate, or parsing errors + if ( + err.message?.includes("URL") || + err.message?.includes("ungültig") || + err.message?.includes("invalid") || + err.message?.includes("bereits verbunden") || + err.message?.includes("already") + ) { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:205',message:'handleAccountSubmit: URL/duplicate error, showing notification',data:{errorMessage:err.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); + // #endregion + // Use the actual error message or default message + setNotificationMessage(err.message || "bitte eine gueltige url verwenden!"); + setShowNotification(true); + } else { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OnboardingGate.jsx:175',message:'handleAccountSubmit: non-URL error, setting submitError',data:{errorMessage:err.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); + // #endregion + setSubmitError(err.message || "Fehler beim Erstellen des Accounts. Bitte versuche es erneut."); + } + setPhase("input"); + setSubmitting(false); + } + }, 2000); // 2 second delay for vanish animation + }; + + return ( +
+ + + {/* Skip Button - Bottom Left */} + + + {/* iPhone-style Notification */} + setShowNotification(false)} + duration={4000} + /> + + {/* Shuffle and Input Phases - Combined AnimatePresence for smooth transition */} + + {phase === "shuffle" && ( + { + e.stopPropagation(); // Prevent double-triggering + handleOverlayClick(e); + }} + > +
+ + , + +
+
+ )} + + {phase === "input" && ( + +
+
+ Gib deine + + ein +
+ + { + setSubmitError(""); + setShowNotification(false); + setNotificationMessage("bitte eine gueltige url verwenden!"); // Reset to default + }} + onSubmit={handleAccountSubmit} + /> + + {(submitError || error) && !showNotification && ( +
+ {submitError || error} +
+ )} +
+
+ )} +
+ + {/* Loading Phase */} + {phase === "loading" && ( + + )} +
+ ); +}; + +const styles = { + overlay: { + position: "fixed", + inset: 0, + background: "rgba(0,0,0,0.75)", + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: 18, + zIndex: 9998, + overflow: "hidden", + }, + centerContent: { + position: "relative", + zIndex: 10, // Higher than BackgroundRippleEffect (z-[3] = z-index: 3) to ensure input is clickable + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + width: "100%", + maxWidth: "800px", + padding: "0 20px", + cursor: "default", + }, + shuffleContainer: { + display: "flex", + alignItems: "center", + justifyContent: "center", + flexWrap: "wrap", + gap: "0", + }, + staticComma: { + fontSize: "4rem", + fontWeight: 800, + color: "#e7aaf0", + marginLeft: "0.1em", + marginRight: "0.1em", + display: "inline-block", + }, + inputContainer: { + width: "100%", + maxWidth: "600px", + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: "24px", + }, + colourfulTextContainer: { + display: "flex", + alignItems: "center", + flexWrap: "wrap", + justifyContent: "center", + gap: "4px", + fontSize: "24px", + fontWeight: 600, + color: "#e7aaf0", + marginBottom: "8px", + textAlign: "center", + }, + textPrefix: { + color: "#e7aaf0", + }, + error: { + marginTop: 12, + color: "#ffb3b3", + fontSize: 13, + textAlign: "center", + padding: "8px 16px", + backgroundColor: "rgba(255, 179, 179, 0.1)", + borderRadius: "8px", + border: "1px solid rgba(255, 179, 179, 0.3)", + }, + skipButton: { + position: "fixed", + bottom: 20, + left: 20, + display: "flex", + alignItems: "center", + justifyContent: "flex-start", + width: "32px", + height: "32px", + border: "none", + borderRadius: "50%", + cursor: "pointer", + backgroundColor: "rgb(255, 65, 65)", + boxShadow: "2px 2px 10px rgba(0, 0, 0, 0.199)", + transition: "all 0.3s ease", + overflow: "hidden", + zIndex: 10001, + }, + skipSign: { + width: "100%", + transition: "all 0.3s ease", + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + skipText: { + position: "absolute", + right: "0%", + width: "0%", + opacity: 0, + color: "white", + fontSize: "0.9em", + fontWeight: 600, + transition: "all 0.3s ease", + whiteSpace: "nowrap", + }, +}; diff --git a/Server/src/components/sidebar/Sidebar.jsx b/Server/src/components/sidebar/Sidebar.jsx new file mode 100644 index 0000000..e5bad4b --- /dev/null +++ b/Server/src/components/sidebar/Sidebar.jsx @@ -0,0 +1,151 @@ +"use client"; +import { cn } from "../../lib/utils"; +import React, { useState, createContext, useContext } from "react"; +import { AnimatePresence, motion } from "motion/react"; +import { IconMenu2, IconX } from "@tabler/icons-react"; + +const SidebarContext = createContext(undefined); + +export const useSidebar = () => { + const context = useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider"); + } + return context; +}; + +export const SidebarProvider = ({ + children, + open: openProp, + setOpen: setOpenProp, + animate = true +}) => { + const [openState, setOpenState] = useState(false); + + const open = openProp !== undefined ? openProp : openState; + const setOpen = setOpenProp !== undefined ? setOpenProp : setOpenState; + + return ( + + {children} + + ); +}; + +export const Sidebar = ({ + children, + open, + setOpen, + animate +}) => { + return ( + + {children} + + ); +}; + +export const SidebarBody = (props) => { + return ( + <> + + + + ); +}; + +export const DesktopSidebar = ({ + className, + children, + ...props +}) => { + const { open, setOpen, animate } = useSidebar(); + return ( + <> + + + ); +}; + +export const MobileSidebar = ({ + className, + children, + ...props +}) => { + const { open, setOpen } = useSidebar(); + return ( + <> +
+
+ setOpen(!open)} /> +
+ + {open && ( + +
setOpen(!open)}> + +
+ {children} +
+ )} +
+
+ + ); +}; + +export const SidebarLink = ({ + link, + className, + ...props +}) => { + const { open, animate } = useSidebar(); + return ( + + {link.icon} + + {link.label} + + + ); +}; diff --git a/Server/src/components/sidebar/SidebarHeader.jsx b/Server/src/components/sidebar/SidebarHeader.jsx new file mode 100644 index 0000000..79c3251 --- /dev/null +++ b/Server/src/components/sidebar/SidebarHeader.jsx @@ -0,0 +1,199 @@ +"use client"; +import React, { useState, useEffect, useRef } from "react"; +import { IconChevronDown } from "@tabler/icons-react"; +import { motion, AnimatePresence } from "motion/react"; +import { cn } from "../../lib/utils"; +import { useHashRoute } from "../../lib/routing"; +import { + getActiveAccountId, + setActiveAccountId, + resolveActiveAccount, + getAccountDisplayName, +} from "../../services/accountService"; +import { fetchManagedAccounts } from "../../services/accountsService"; +import { getAuthUser } from "../../lib/appwrite"; + +export const SidebarHeader = () => { + const { navigate } = useHashRoute(); + const [accounts, setAccounts] = useState([]); + const [activeAccount, setActiveAccount] = useState(null); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [loading, setLoading] = useState(true); + const dropdownRef = useRef(null); + + // Accounts laden + useEffect(() => { + async function loadAccounts() { + setLoading(true); + try { + const authUser = await getAuthUser(); + if (!authUser) { + setAccounts([]); + setActiveAccount(null); + return; + } + + const loadedAccounts = await fetchManagedAccounts(authUser.$id); + setAccounts(loadedAccounts); + const active = resolveActiveAccount(loadedAccounts); + setActiveAccount(active); + } catch (e) { + console.error("Fehler beim Laden der Accounts:", e); + setAccounts([]); + setActiveAccount(null); + } finally { + setLoading(false); + } + } + + loadAccounts(); + }, []); + + // Event-Listener für activeAccountChanged (wenn Account in anderer Komponente gewechselt wird) + useEffect(() => { + function handleActiveAccountChanged(event) { + const { accountId } = event.detail; + // Account aus aktueller Liste finden und als aktiv setzen + const foundAccount = accounts.find( + (acc) => acc.$id === accountId || acc.id === accountId + ); + if (foundAccount) { + setActiveAccount(foundAccount); + } + } + + window.addEventListener("activeAccountChanged", handleActiveAccountChanged); + return () => { + window.removeEventListener("activeAccountChanged", handleActiveAccountChanged); + }; + }, [accounts]); + + // Click outside handler für Dropdown + useEffect(() => { + function handleClickOutside(event) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setDropdownOpen(false); + } + } + + if (dropdownOpen) { + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + } + }, [dropdownOpen]); + + const handleHeaderClick = () => { + if (accounts.length === 0) { + // Keine Accounts: direkt zu /accounts navigieren + navigate("/accounts"); + } else { + // Accounts existieren: Dropdown öffnen/schließen + setDropdownOpen(!dropdownOpen); + } + }; + + const handleAccountSelect = (account) => { + const accountId = account.$id || account.id; + setActiveAccountId(accountId); + setActiveAccount(account); + setDropdownOpen(false); + }; + + const handleManageAccounts = () => { + setDropdownOpen(false); + navigate("/accounts"); + }; + + const displayText = + accounts.length === 0 + ? "Add Account" + : activeAccount + ? getAccountDisplayName(activeAccount) + : "Add Account"; + + return ( +
+ + + + {dropdownOpen && accounts.length > 0 && ( + +
+ {accounts.map((account) => { + const accountId = account.$id || account.id; + const currentActiveId = getActiveAccountId(); + const isActive = accountId === currentActiveId; + const displayName = getAccountDisplayName(account); + + return ( + + ); + })} + +
+ + +
+ + )} + +
+ ); +}; \ No newline at end of file diff --git a/Server/src/components/sidebar/index.js b/Server/src/components/sidebar/index.js new file mode 100644 index 0000000..a5f8b0b --- /dev/null +++ b/Server/src/components/sidebar/index.js @@ -0,0 +1 @@ +export { Sidebar, SidebarBody, SidebarLink, useSidebar } from "./Sidebar.jsx"; diff --git a/Server/src/components/ui/LogoutButton.jsx b/Server/src/components/ui/LogoutButton.jsx new file mode 100644 index 0000000..a8d1c08 --- /dev/null +++ b/Server/src/components/ui/LogoutButton.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import styled from 'styled-components'; + +const LogoutButton = ({ onClick }) => { + return ( + + + + ); +} + +const StyledWrapper = styled.div` + .Btn { + display: flex; + align-items: center; + justify-content: flex-start; + width: 32px; + height: 32px; + border: none; + border-radius: 50%; + cursor: pointer; + position: relative; + overflow: hidden; + transition-duration: .3s; + box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.199); + background-color: rgb(255, 65, 65); + } + + /* plus sign */ + .sign { + width: 100%; + transition-duration: .3s; + display: flex; + align-items: center; + justify-content: center; + } + + .sign svg { + width: 12px; + } + + .sign svg path { + fill: white; + } + /* text */ + .text { + position: absolute; + right: 0%; + width: 0%; + opacity: 0; + color: white; + font-size: 0.9em; + font-weight: 600; + transition-duration: .3s; + } + /* hover effect on button width */ + .Btn:hover { + width: 90px; + border-radius: 40px; + transition-duration: .3s; + } + + .Btn:hover .sign { + width: 30%; + transition-duration: .3s; + padding-left: 15px; + } + /* hover effect button's text */ + .Btn:hover .text { + opacity: 1; + width: 70%; + transition-duration: .3s; + padding-right: 8px; + } + /* button click effect*/ + .Btn:active { + transform: translate(2px ,2px); + }`; + +export default LogoutButton; diff --git a/Server/src/components/ui/Shuffle.jsx b/Server/src/components/ui/Shuffle.jsx new file mode 100644 index 0000000..be5e0e7 --- /dev/null +++ b/Server/src/components/ui/Shuffle.jsx @@ -0,0 +1,392 @@ +"use client"; +import React, { useRef, useEffect, useState, useMemo } from 'react'; +import { gsap } from 'gsap'; +import { ScrollTrigger } from 'gsap/ScrollTrigger'; +import { SplitText as GSAPSplitText } from 'gsap/SplitText'; +import { useGSAP } from '@gsap/react'; + +gsap.registerPlugin(ScrollTrigger, GSAPSplitText, useGSAP); + +const Shuffle = ({ + text, + className = '', + style = {}, + shuffleDirection = 'right', + duration = 0.35, + maxDelay = 0, + ease = 'power3.out', + threshold = 0.1, + rootMargin = '-100px', + tag = 'p', + textAlign = 'center', + onShuffleComplete, + shuffleTimes = 1, + animationMode = 'evenodd', + loop = false, + loopDelay = 0, + stagger = 0.03, + scrambleCharset = '', + colorFrom, + colorTo, + triggerOnce = true, + respectReducedMotion = true, + triggerOnHover = true +}) => { + const ref = useRef(null); + const [fontsLoaded, setFontsLoaded] = useState(false); + const [ready, setReady] = useState(false); + + const splitRef = useRef(null); + const wrappersRef = useRef([]); + const tlRef = useRef(null); + const playingRef = useRef(false); + const hoverHandlerRef = useRef(null); + + const userHasFont = useMemo( + () => (style && style.fontFamily) || (className && /font[-[]/i.test(className)), + [style, className] + ); + + const scrollTriggerStart = useMemo(() => { + const startPct = (1 - threshold) * 100; + const mm = /^(-?\d+(?:\.\d+)?)(px|em|rem|%)?$/.exec(rootMargin || ''); + const mv = mm ? parseFloat(mm[1]) : 0; + const mu = mm ? mm[2] || 'px' : 'px'; + const sign = mv === 0 ? '' : mv < 0 ? `-=${Math.abs(mv)}${mu}` : `+=${mv}${mu}`; + return `top ${startPct}%${sign}`; + }, [threshold, rootMargin]); + + useEffect(() => { + if ('fonts' in document) { + if (document.fonts.status === 'loaded') setFontsLoaded(true); + else document.fonts.ready.then(() => setFontsLoaded(true)); + } else setFontsLoaded(true); + }, []); + + useGSAP( + () => { + if (!ref.current || !text || !fontsLoaded) return; + + if (respectReducedMotion && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + onShuffleComplete?.(); + return; + } + + const el = ref.current; + + let computedFont = ''; + if (userHasFont) { + computedFont = style.fontFamily || getComputedStyle(el).fontFamily || ''; + } else { + computedFont = `'Press Start 2P', sans-serif`; + } + + const start = scrollTriggerStart; + + const removeHover = () => { + if (hoverHandlerRef.current && ref.current) { + ref.current.removeEventListener('mouseenter', hoverHandlerRef.current); + hoverHandlerRef.current = null; + } + }; + + const teardown = () => { + if (tlRef.current) { + tlRef.current.kill(); + tlRef.current = null; + } + if (wrappersRef.current.length) { + wrappersRef.current.forEach(wrap => { + const inner = wrap.firstElementChild; + const orig = inner?.querySelector('[data-orig="1"]'); + if (orig && wrap.parentNode) wrap.parentNode.replaceChild(orig, wrap); + }); + wrappersRef.current = []; + } + try { + splitRef.current?.revert(); + } catch { + /* noop */ + } + splitRef.current = null; + playingRef.current = false; + }; + + const build = () => { + teardown(); + + splitRef.current = new GSAPSplitText(el, { + type: 'chars', + charsClass: 'shuffle-char', + wordsClass: 'shuffle-word', + linesClass: 'shuffle-line', + smartWrap: true, + reduceWhiteSpace: false + }); + + const chars = splitRef.current.chars || []; + wrappersRef.current = []; + + const rolls = Math.max(1, Math.floor(shuffleTimes)); + const rand = set => set.charAt(Math.floor(Math.random() * set.length)) || ''; + + chars.forEach(ch => { + const parent = ch.parentElement; + if (!parent) return; + + const w = ch.getBoundingClientRect().width; + const h = ch.getBoundingClientRect().height; + if (!w) return; + + const wrap = document.createElement('span'); + wrap.className = 'inline-block overflow-hidden text-left'; + Object.assign(wrap.style, { + width: w + 'px', + height: shuffleDirection === 'up' || shuffleDirection === 'down' ? h + 'px' : 'auto', + verticalAlign: 'bottom' + }); + + const inner = document.createElement('span'); + inner.className = + 'inline-block will-change-transform origin-left transform-gpu ' + + (shuffleDirection === 'up' || shuffleDirection === 'down' ? 'whitespace-normal' : 'whitespace-nowrap'); + + parent.insertBefore(wrap, ch); + wrap.appendChild(inner); + + const firstOrig = ch.cloneNode(true); + firstOrig.className = + 'text-left ' + (shuffleDirection === 'up' || shuffleDirection === 'down' ? 'block' : 'inline-block'); + Object.assign(firstOrig.style, { width: w + 'px', fontFamily: computedFont }); + + ch.setAttribute('data-orig', '1'); + ch.className = + 'text-left ' + (shuffleDirection === 'up' || shuffleDirection === 'down' ? 'block' : 'inline-block'); + Object.assign(ch.style, { width: w + 'px', fontFamily: computedFont }); + + inner.appendChild(firstOrig); + for (let k = 0; k < rolls; k++) { + const c = ch.cloneNode(true); + if (scrambleCharset) c.textContent = rand(scrambleCharset); + c.className = + 'text-left ' + (shuffleDirection === 'up' || shuffleDirection === 'down' ? 'block' : 'inline-block'); + Object.assign(c.style, { width: w + 'px', fontFamily: computedFont }); + inner.appendChild(c); + } + inner.appendChild(ch); + + const steps = rolls + 1; + + if (shuffleDirection === 'right' || shuffleDirection === 'down') { + const firstCopy = inner.firstElementChild; + const real = inner.lastElementChild; + if (real) inner.insertBefore(real, inner.firstChild); + if (firstCopy) inner.appendChild(firstCopy); + } + + let startX = 0; + let finalX = 0; + let startY = 0; + let finalY = 0; + + if (shuffleDirection === 'right') { + startX = -steps * w; + finalX = 0; + } else if (shuffleDirection === 'left') { + startX = 0; + finalX = -steps * w; + } else if (shuffleDirection === 'down') { + startY = -steps * h; + finalY = 0; + } else if (shuffleDirection === 'up') { + startY = 0; + finalY = -steps * h; + } + + if (shuffleDirection === 'left' || shuffleDirection === 'right') { + gsap.set(inner, { x: startX, y: 0, force3D: true }); + inner.setAttribute('data-start-x', String(startX)); + inner.setAttribute('data-final-x', String(finalX)); + } else { + gsap.set(inner, { x: 0, y: startY, force3D: true }); + inner.setAttribute('data-start-y', String(startY)); + inner.setAttribute('data-final-y', String(finalY)); + } + + if (colorFrom) inner.style.color = colorFrom; + wrappersRef.current.push(wrap); + }); + }; + + const inners = () => wrappersRef.current.map(w => w.firstElementChild); + + const randomizeScrambles = () => { + if (!scrambleCharset) return; + wrappersRef.current.forEach(w => { + const strip = w.firstElementChild; + if (!strip) return; + const kids = Array.from(strip.children); + for (let i = 1; i < kids.length - 1; i++) { + kids[i].textContent = scrambleCharset.charAt(Math.floor(Math.random() * scrambleCharset.length)); + } + }); + }; + + const cleanupToStill = () => { + wrappersRef.current.forEach(w => { + const strip = w.firstElementChild; + if (!strip) return; + const real = strip.querySelector('[data-orig="1"]'); + if (!real) return; + strip.replaceChildren(real); + strip.style.transform = 'none'; + strip.style.willChange = 'auto'; + }); + }; + + const play = () => { + const strips = inners(); + if (!strips.length) return; + + playingRef.current = true; + const isVertical = shuffleDirection === 'up' || shuffleDirection === 'down'; + + const tl = gsap.timeline({ + smoothChildTiming: true, + repeat: loop ? -1 : 0, + repeatDelay: loop ? loopDelay : 0, + onRepeat: () => { + if (scrambleCharset) randomizeScrambles(); + if (isVertical) { + gsap.set(strips, { y: (i, t) => parseFloat(t.getAttribute('data-start-y') || '0') }); + } else { + gsap.set(strips, { x: (i, t) => parseFloat(t.getAttribute('data-start-x') || '0') }); + } + onShuffleComplete?.(); + }, + onComplete: () => { + playingRef.current = false; + if (!loop) { + cleanupToStill(); + if (colorTo) gsap.set(strips, { color: colorTo }); + onShuffleComplete?.(); + armHover(); + } + } + }); + + const addTween = (targets, at) => { + const vars = { + duration, + ease, + force3D: true, + stagger: animationMode === 'evenodd' ? stagger : 0 + }; + if (isVertical) { + vars.y = (i, t) => parseFloat(t.getAttribute('data-final-y') || '0'); + } else { + vars.x = (i, t) => parseFloat(t.getAttribute('data-final-x') || '0'); + } + + tl.to(targets, vars, at); + + if (colorFrom && colorTo) tl.to(targets, { color: colorTo, duration, ease }, at); + }; + + if (animationMode === 'evenodd') { + const odd = strips.filter((_, i) => i % 2 === 1); + const even = strips.filter((_, i) => i % 2 === 0); + const oddTotal = duration + Math.max(0, odd.length - 1) * stagger; + const evenStart = odd.length ? oddTotal * 0.7 : 0; + if (odd.length) addTween(odd, 0); + if (even.length) addTween(even, evenStart); + } else { + strips.forEach(strip => { + const d = Math.random() * maxDelay; + const vars = { + duration, + ease, + force3D: true + }; + if (isVertical) { + vars.y = parseFloat(strip.getAttribute('data-final-y') || '0'); + } else { + vars.x = parseFloat(strip.getAttribute('data-final-x') || '0'); + } + tl.to(strip, vars, d); + if (colorFrom && colorTo) tl.fromTo(strip, { color: colorFrom }, { color: colorTo, duration, ease }, d); + }); + } + + tlRef.current = tl; + }; + + const armHover = () => { + if (!triggerOnHover || !ref.current) return; + removeHover(); + const handler = () => { + if (playingRef.current) return; + build(); + if (scrambleCharset) randomizeScrambles(); + play(); + }; + hoverHandlerRef.current = handler; + ref.current.addEventListener('mouseenter', handler); + }; + + const create = () => { + build(); + if (scrambleCharset) randomizeScrambles(); + play(); + armHover(); + setReady(true); + }; + + const st = ScrollTrigger.create({ trigger: el, start, once: triggerOnce, onEnter: create }); + + return () => { + st.kill(); + removeHover(); + teardown(); + setReady(false); + }; + }, + { + dependencies: [ + text, + duration, + maxDelay, + ease, + scrollTriggerStart, + fontsLoaded, + shuffleDirection, + shuffleTimes, + animationMode, + loop, + loopDelay, + stagger, + scrambleCharset, + colorFrom, + colorTo, + triggerOnce, + respectReducedMotion, + triggerOnHover, + onShuffleComplete, + userHasFont + ], + scope: ref + } + ); + + const baseTw = 'inline-block whitespace-normal break-words will-change-transform uppercase text-[4rem] leading-none'; + const classes = useMemo( + () => `${baseTw} ${ready ? 'visible' : 'invisible'} ${className}`.trim(), + [baseTw, ready, className] + ); + const Tag = tag || 'p'; + const commonStyle = useMemo(() => ({ textAlign, ...style }), [textAlign, style]); + + return React.createElement(Tag, { ref: ref, className: classes, style: commonStyle }, text); +}; + +export default Shuffle; diff --git a/Server/src/components/ui/colourful-text.jsx b/Server/src/components/ui/colourful-text.jsx new file mode 100644 index 0000000..16b0d88 --- /dev/null +++ b/Server/src/components/ui/colourful-text.jsx @@ -0,0 +1,52 @@ +"use client"; +import React from "react"; +import { motion } from "motion/react"; + +export default function ColourfulText({ text }) { + const colors = [ + "rgb(131, 179, 32)", + "rgb(47, 195, 106)", + "rgb(42, 169, 210)", + "rgb(4, 112, 202)", + "rgb(107, 10, 255)", + "rgb(183, 0, 218)", + "rgb(218, 0, 171)", + "rgb(230, 64, 92)", + "rgb(232, 98, 63)", + "rgb(249, 129, 47)", + ]; + + const [currentColors, setCurrentColors] = React.useState(colors); + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + const interval = setInterval(() => { + const shuffled = [...colors].sort(() => Math.random() - 0.5); + setCurrentColors(shuffled); + setCount((prev) => prev + 1); + }, 5000); + + return () => clearInterval(interval); + }, []); + + return text.split("").map((char, index) => ( + + {char} + + )); +} diff --git a/Server/src/components/ui/iphone-notification.jsx b/Server/src/components/ui/iphone-notification.jsx new file mode 100644 index 0000000..0bea74c --- /dev/null +++ b/Server/src/components/ui/iphone-notification.jsx @@ -0,0 +1,107 @@ +"use client"; +import React, { useState } from "react"; +import { motion, AnimatePresence } from "motion/react"; + +export function IPhoneNotification({ show, title, message, onClose, duration = 3000 }) { + const [isHovered, setIsHovered] = useState(false); + + React.useEffect(() => { + if (show && duration > 0) { + const timer = setTimeout(() => { + onClose && onClose(); + }, duration); + + return () => clearTimeout(timer); + } + }, [show, duration, onClose]); + + return ( + + {show && ( + setIsHovered(true)} + onHoverEnd={() => setIsHovered(false)} + whileHover={{ scale: 1.05 }} + > + +
+
+

{title}

+ now +
+

{message}

+
+
+ )} +
+ ); +} + +const styles = { + card: { + position: "fixed", + top: 20, + left: "50%", + width: "100%", + maxWidth: "290px", + height: "70px", + background: "#27272a", // zinc-800 - same grey as search bar + borderRadius: "20px", + display: "flex", + alignItems: "center", + justifyContent: "left", + backdropFilter: "blur(10px)", + transition: "0.5s ease-in-out", + zIndex: 10000, + cursor: "pointer", + boxShadow: "0 4px 12px rgba(0, 0, 0, 0.5)", + border: "1px solid rgba(231, 170, 240, 0.2)", + }, + img: { + width: "50px", + height: "50px", + marginLeft: "10px", + borderRadius: "10px", + background: "linear-gradient(#d7cfcf, #9198e5)", + transition: "0.5s ease-in-out", + }, + textBox: { + width: "calc(100% - 90px)", + marginLeft: "10px", + color: "#e7aaf0", + fontFamily: "'Poppins', sans-serif", + }, + textContent: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + }, + span: { + fontSize: "10px", + opacity: 0.7, + }, + h1: { + fontSize: "16px", + fontWeight: "bold", + margin: 0, + }, + p: { + fontSize: "12px", + fontWeight: "lighter", + margin: "4px 0 0 0", + opacity: 0.8, + }, +}; \ No newline at end of file diff --git a/Server/src/components/ui/multi-step-loader.jsx b/Server/src/components/ui/multi-step-loader.jsx new file mode 100644 index 0000000..a5b2d69 --- /dev/null +++ b/Server/src/components/ui/multi-step-loader.jsx @@ -0,0 +1,97 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { AnimatePresence, motion } from "motion/react"; + +function LoaderCore({ loadingStates, value }) { + return ( +
+ {loadingStates.map((state, index) => { + const distance = Math.abs(index - value); + const opacity = Math.max(1 - distance * 0.2, 0); + return ( + +
+ {index > value ? ( + + + + ) : ( + + + + )} +
+ + {state.text} + +
+ ); + })} +
+ ); +} + +export function MultiStepLoader({ + loadingStates, + loading = false, + duration = 2000, + loop = true, +}) { + const [currentValue, setCurrentValue] = useState(0); + + useEffect(() => { + if (!loading) { + setCurrentValue(0); + return; + } + const timer = setTimeout(() => { + setCurrentValue((prev) => + loop + ? prev === loadingStates.length - 1 + ? 0 + : prev + 1 + : Math.min(prev + 1, loadingStates.length - 1) + ); + }, duration); + + return () => clearTimeout(timer); + }, [currentValue, loading, loop, duration, loadingStates.length]); + + return ( + + {loading && ( + +
+ +
+
+ + )} + + ); +} diff --git a/Server/src/components/ui/placeholders-and-vanish-input.jsx b/Server/src/components/ui/placeholders-and-vanish-input.jsx new file mode 100644 index 0000000..474aac2 --- /dev/null +++ b/Server/src/components/ui/placeholders-and-vanish-input.jsx @@ -0,0 +1,262 @@ +"use client"; + +import { AnimatePresence, motion } from "motion/react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { cn } from "@/lib/utils"; + +export function PlaceholdersAndVanishInput({ + placeholders, + onChange, + onSubmit +}) { + const [currentPlaceholder, setCurrentPlaceholder] = useState(0); + + const intervalRef = useRef(null); + const startAnimation = () => { + intervalRef.current = setInterval(() => { + setCurrentPlaceholder((prev) => (prev + 1) % placeholders.length); + }, 3000); + }; + const handleVisibilityChange = () => { + if (document.visibilityState !== "visible" && intervalRef.current) { + clearInterval(intervalRef.current); // Clear the interval when the tab is not visible + intervalRef.current = null; + } else if (document.visibilityState === "visible") { + startAnimation(); // Restart the interval when the tab becomes visible + } + }; + + useEffect(() => { + startAnimation(); + document.addEventListener("visibilitychange", handleVisibilityChange); + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + document.removeEventListener("visibilitychange", handleVisibilityChange); + }; + }, [placeholders]); + + const canvasRef = useRef(null); + const newDataRef = useRef([]); + const inputRef = useRef(null); + const [value, setValue] = useState(""); + const [animating, setAnimating] = useState(false); + + const draw = useCallback(() => { + if (!inputRef.current) return; + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + canvas.width = 800; + canvas.height = 800; + ctx.clearRect(0, 0, 800, 800); + const computedStyles = getComputedStyle(inputRef.current); + + const fontSize = parseFloat(computedStyles.getPropertyValue("font-size")); + ctx.font = `${fontSize * 2}px ${computedStyles.fontFamily}`; + ctx.fillStyle = "#FFF"; + ctx.fillText(value, 16, 40); + + const imageData = ctx.getImageData(0, 0, 800, 800); + const pixelData = imageData.data; + const newData = []; + + for (let t = 0; t < 800; t++) { + let i = 4 * t * 800; + for (let n = 0; n < 800; n++) { + let e = i + 4 * n; + if ( + pixelData[e] !== 0 && + pixelData[e + 1] !== 0 && + pixelData[e + 2] !== 0 + ) { + newData.push({ + x: n, + y: t, + color: [ + pixelData[e], + pixelData[e + 1], + pixelData[e + 2], + pixelData[e + 3], + ], + }); + } + } + } + + newDataRef.current = newData.map(({ x, y, color }) => ({ + x, + y, + r: 1, + color: `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`, + })); + }, [value]); + + useEffect(() => { + draw(); + }, [value, draw]); + + const animate = (start) => { + const animateFrame = (pos = 0) => { + requestAnimationFrame(() => { + const newArr = []; + for (let i = 0; i < newDataRef.current.length; i++) { + const current = newDataRef.current[i]; + if (current.x < pos) { + newArr.push(current); + } else { + if (current.r <= 0) { + current.r = 0; + continue; + } + current.x += Math.random() > 0.5 ? 1 : -1; + current.y += Math.random() > 0.5 ? 1 : -1; + current.r -= 0.05 * Math.random(); + newArr.push(current); + } + } + newDataRef.current = newArr; + const ctx = canvasRef.current?.getContext("2d"); + if (ctx) { + ctx.clearRect(pos, 0, 800, 800); + newDataRef.current.forEach((t) => { + const { x: n, y: i, r: s, color: color } = t; + if (n > pos) { + ctx.beginPath(); + ctx.rect(n, i, s, s); + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.stroke(); + } + }); + } + if (newDataRef.current.length > 0) { + animateFrame(pos - 8); + } else { + setValue(""); + setAnimating(false); + } + }); + }; + animateFrame(start); + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter" && !animating) { + vanishAndSubmit(); + } + }; + + const vanishAndSubmit = () => { + setAnimating(true); + draw(); + + const value = inputRef.current?.value || ""; + if (value && inputRef.current) { + const maxX = newDataRef.current.reduce((prev, current) => (current.x > prev ? current.x : prev), 0); + animate(maxX); + } + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const url = inputRef.current?.value || ""; + vanishAndSubmit(); + onSubmit && onSubmit(e, url); + }; + return ( +
+ + { + if (!animating) { + setValue(e.target.value); + onChange && onChange(e); + } + }} + onKeyDown={handleKeyDown} + ref={inputRef} + value={value} + type="text" + className={cn( + "w-full relative text-sm sm:text-base z-50 border-none dark:text-white bg-transparent text-black h-full rounded-full focus:outline-none focus:ring-0 pl-4 sm:pl-10 pr-20", + animating && "text-transparent dark:text-transparent" + )} /> + +
+ + {!value && ( + + {placeholders[currentPlaceholder]} + + )} + +
+ + ); +} diff --git a/Server/src/index.css b/Server/src/index.css new file mode 100644 index 0000000..2d493a1 --- /dev/null +++ b/Server/src/index.css @@ -0,0 +1,43 @@ +@import "tailwindcss"; + +:root { + --bg: #0b0d12; + --panel: #121624; + --panel2: #0f1320; + --text: #e8eaf1; + --muted: #9aa3b2; + --line: #22283a; + --accent: #6aa6ff; + --good: #4ade80; + --warn: #fbbf24; + --bad: #f87171; + --shadow: 0 10px 30px rgba(0, 0, 0, 0.35); + --radius: 18px; +} + +/* Hide scrollbar but keep scrolling functionality */ +.hide-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.hide-scrollbar::-webkit-scrollbar { + display: none; /* Chrome, Safari and Opera */ +} + +@theme inline { + --animate-cell-ripple: cell-ripple var(--duration, 200ms) ease-out none 1 + var(--delay, 0ms); + + @keyframes cell-ripple { + 0% { + opacity: 0.4; + } + 50% { + opacity: 0.8; + } + 100% { + opacity: 0.4; + } + } +} diff --git a/Server/src/lib/appwrite.js b/Server/src/lib/appwrite.js new file mode 100644 index 0000000..753a482 --- /dev/null +++ b/Server/src/lib/appwrite.js @@ -0,0 +1,59 @@ +/** + * Zentrales Appwrite Client Setup + * Stellt Client, Account, Databases Instanzen und Helper-Funktionen bereit + */ + +import { Client, Account, Databases } from "appwrite"; + +// Konfiguration aus Environment-Variablen oder Defaults +const endpoint = import.meta.env.VITE_APPWRITE_ENDPOINT || "https://appwrite.webklar.com/v1"; +const projectId = import.meta.env.VITE_APPWRITE_PROJECT_ID || "696b82bb0036d2e547ad"; +const databaseId = import.meta.env.VITE_APPWRITE_DATABASE_ID || "eship-db"; +const usersCollectionId = import.meta.env.VITE_APPWRITE_USERS_COLLECTION_ID || "users"; +const accountsCollectionId = import.meta.env.VITE_APPWRITE_ACCOUNTS_COLLECTION_ID || "accounts"; + +// Client initialisieren +const client = new Client() + .setEndpoint(endpoint) + .setProject(projectId); + +// Service-Instanzen +const account = new Account(client); +const databases = new Databases(client); + +/** + * Gibt den aktuell eingeloggten User zurück + * @returns {Promise} User-Objekt oder null + */ +export async function getAuthUser() { + try { + const user = await account.get(); + return user; + } catch (e) { + // Session nicht vorhanden oder abgelaufen + if (e.code === 401 || e.type === 'general_unauthorized_scope') { + return null; + } + console.warn("Fehler beim Abrufen des Users:", e); + return null; + } +} + +/** + * Prüft ob eine gültige Session existiert und leitet ggf. zum Login um + * @param {Function} navigate - Navigations-Funktion (z.B. von useHashRoute) + * @param {string} loginRoute - Route zum Login (default: "/") + * @returns {Promise} User-Objekt oder null (wenn nicht eingeloggt) + */ +export async function ensureSessionOrRedirect(navigate, loginRoute = "/") { + const user = await getAuthUser(); + if (!user && navigate) { + // Redirect zu Login (aktuell noch kein separater Login-Route, daher "/") + // TODO: Wenn Login-Route existiert, hier verwenden + navigate(loginRoute); + } + return user; +} + +// Exports +export { client, account, databases, databaseId, usersCollectionId, accountsCollectionId }; diff --git a/Server/src/lib/routing.js b/Server/src/lib/routing.js new file mode 100644 index 0000000..ead3b90 --- /dev/null +++ b/Server/src/lib/routing.js @@ -0,0 +1,39 @@ +import { useState, useEffect } from "react"; + +/** + * Einfaches Hash-basiertes Routing System + * Verwendet URL Hash (#/path) für Navigation ohne Page Reload + */ + +export function useHashRoute() { + const [route, setRoute] = useState(() => { + // Initial route aus Hash extrahieren + const hash = window.location.hash.slice(1) || "/"; + return hash.startsWith("/") ? hash : `/${hash}`; + }); + + useEffect(() => { + const handleHashChange = () => { + const hash = window.location.hash.slice(1) || "/"; + const newRoute = hash.startsWith("/") ? hash : `/${hash}`; + setRoute(newRoute); + }; + + // Event Listener für Hash-Änderungen + window.addEventListener("hashchange", handleHashChange); + + // Initial check + handleHashChange(); + + return () => { + window.removeEventListener("hashchange", handleHashChange); + }; + }, []); + + const navigate = (path) => { + const newPath = path.startsWith("/") ? path : `/${path}`; + window.location.hash = newPath; + }; + + return { route, navigate }; +} \ No newline at end of file diff --git a/Server/src/lib/utils.js b/Server/src/lib/utils.js new file mode 100644 index 0000000..378ccef --- /dev/null +++ b/Server/src/lib/utils.js @@ -0,0 +1,6 @@ +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs) { + return twMerge(clsx(inputs)); +} diff --git a/Server/src/main.jsx b/Server/src/main.jsx new file mode 100644 index 0000000..89f91e5 --- /dev/null +++ b/Server/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/Server/src/pages/AccountsPage.jsx b/Server/src/pages/AccountsPage.jsx new file mode 100644 index 0000000..7b07de6 --- /dev/null +++ b/Server/src/pages/AccountsPage.jsx @@ -0,0 +1,656 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { IconPlus, IconChevronDown, IconX, IconRefresh } from "@tabler/icons-react"; +import { motion, AnimatePresence } from "motion/react"; +import { cn } from "../lib/utils"; +import { useHashRoute } from "../lib/routing"; +import { + setActiveAccountId, + getAccountDisplayName, +} from "../services/accountService"; +import { fetchManagedAccounts, createManagedAccount, updateManagedAccount } from "../services/accountsService"; +import { getAuthUser } from "../lib/appwrite"; +import { parseEbayAccount } from "../services/ebayParserService"; +import { DataTable } from "../components/dashboard/ui/DataTable"; + +export const AccountsPage = () => { + const { navigate } = useHashRoute(); + const [accounts, setAccounts] = useState([]); + const [loading, setLoading] = useState(true); + const [showAdvanced, setShowAdvanced] = useState(false); + const [showAddForm, setShowAddForm] = useState(false); + const [formError, setFormError] = useState(""); + const [formSuccess, setFormSuccess] = useState(""); + const [formLoading, setFormLoading] = useState(false); + + // Parse-State für Zwei-Phasen-Flow + const [parsedData, setParsedData] = useState(null); + const [parsing, setParsing] = useState(false); + const [parsingError, setParsingError] = useState(""); + + // Refresh-State pro Account + const [refreshingAccountId, setRefreshingAccountId] = useState(null); + const [refreshToast, setRefreshToast] = useState({ show: false, message: "", type: "success" }); + + // Form-Felder (nur noch URL) + const [formData, setFormData] = useState({ + account_url: "", + }); + + // Accounts laden + useEffect(() => { + loadAccounts(); + }, []); + + async function loadAccounts() { + setLoading(true); + try { + const authUser = await getAuthUser(); + if (!authUser) { + setAccounts([]); + return; + } + + const loadedAccounts = await fetchManagedAccounts(authUser.$id); + setAccounts(loadedAccounts); + } catch (e) { + console.error("Fehler beim Laden der Accounts:", e); + setAccounts([]); + } finally { + setLoading(false); + } + } + + const handleSelectAccount = (account) => { + const accountId = account.$id || account.id; + setActiveAccountId(accountId); + // Navigiere zurück zum Dashboard + navigate("/"); + }; + + const handleRefreshAccount = async (account) => { + const accountId = account.$id || account.id; + const accountUrl = account.account_url; + + if (!accountUrl) { + setRefreshToast({ show: true, message: "Account hat keine URL zum Aktualisieren.", type: "error" }); + setTimeout(() => setRefreshToast({ show: false, message: "", type: "success" }), 3000); + return; + } + + setRefreshingAccountId(accountId); + + try { + // URL erneut parsen + const parsedData = await parseEbayAccount(accountUrl); + + // Account in DB aktualisieren + // WICHTIG: Nur Felder setzen, die nicht leer sind und sich geändert haben + // Leere account_platform_account_id würde Unique-Index-Konflikte verursachen + const updatePayload = {}; + + // Nur market setzen, wenn nicht leer + if (parsedData.market && parsedData.market.trim()) { + updatePayload.account_platform_market = parsedData.market; + } + + // Nur sellerId setzen, wenn nicht leer (verhindert Unique-Index-Konflikte mit leerem String) + if (parsedData.sellerId && parsedData.sellerId.trim()) { + updatePayload.account_platform_account_id = parsedData.sellerId; + } + + // Shop-Name und account_sells können auch leer sein (optional) + updatePayload.account_shop_name = parsedData.shopName || null; + updatePayload.account_sells = parsedData.stats?.itemsSold ?? null; + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'AccountsPage.jsx:93',message:'handleRefreshAccount: update payload',data:{payload:updatePayload},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + + // account_status wird weggelassen (wie beim Erstellen) + // Grund: Schema-Konflikt - Enum-Feld akzeptiert weder String noch Array im Update + // TODO: Schema in Appwrite prüfen und korrigieren, dann account_status wieder hinzufügen + + await updateManagedAccount(accountId, updatePayload); + + // Accounts-Liste neu laden (in-place Update) + await loadAccounts(); + + // Success-Toast + setRefreshToast({ show: true, message: "Account aktualisiert", type: "success" }); + setTimeout(() => setRefreshToast({ show: false, message: "", type: "success" }), 3000); + } catch (e) { + console.error("Fehler beim Aktualisieren des Accounts:", e); + + let errorMessage = "Update fehlgeschlagen"; + if (e.message?.includes("Parsing") || e.message?.includes("URL")) { + errorMessage = "Parsing fehlgeschlagen"; + } + + setRefreshToast({ show: true, message: errorMessage, type: "error" }); + setTimeout(() => setRefreshToast({ show: false, message: "", type: "success" }), 3000); + } finally { + setRefreshingAccountId(null); + } + }; + + const handleAddAccount = () => { + setShowAddForm(true); + setFormError(""); + setFormSuccess(""); + }; + + const handleCloseForm = () => { + setShowAddForm(false); + setFormError(""); + setFormSuccess(""); + setParsedData(null); + setParsingError(""); + // Form zurücksetzen + setFormData({ + account_url: "", + }); + }; + + const handleFormChange = (field, value) => { + setFormData((prev) => ({ ...prev, [field]: value })); + // Clear errors beim Eingeben + if (formError) setFormError(""); + if (parsingError) setParsingError(""); + }; + + const handleParseUrl = async () => { + const url = formData.account_url?.trim(); + + if (!url) { + setParsingError("Bitte gib eine eBay-URL ein."); + return; + } + + setParsing(true); + setParsingError(""); + setFormError(""); + + try { + const result = await parseEbayAccount(url); + setParsedData(result); + setParsingError(""); + } catch (e) { + setParsingError(e.message || "Bitte gib eine gültige eBay-URL ein."); + setParsedData(null); + } finally { + setParsing(false); + } + }; + + const handleFormSubmit = async (e) => { + e.preventDefault(); + + // Wenn noch nicht geparst, zuerst parsen + if (!parsedData) { + await handleParseUrl(); + return; + } + + // Save-Phase: Account in DB speichern + setFormError(""); + setFormSuccess(""); + setFormLoading(true); + + try { + const authUser = await getAuthUser(); + if (!authUser) { + setFormError("Nicht eingeloggt. Bitte neu anmelden."); + return; + } + + // Payload aus parsedData zusammenstellen + const accountSellsValue = parsedData.stats?.itemsSold ?? null; + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'AccountsPage.jsx:193',message:'handleFormSubmit: parsedData before save',data:{hasStats:!!parsedData.stats,itemsSold:parsedData.stats?.itemsSold,accountSellsValue},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + const newAccount = await createManagedAccount(authUser.$id, { + account_url: formData.account_url.trim(), + account_platform_account_id: parsedData.sellerId, + account_platform_market: parsedData.market, + account_shop_name: parsedData.shopName, + account_sells: accountSellsValue, + // account_status wird nicht mehr gesendet (optional, Schema-Problem in Appwrite) + }); + + // Erfolg: Liste refreshen + await loadAccounts(); + + // Wenn dies das erste Account ist, setze es als aktiv + if (accounts.length === 0) { + setActiveAccountId(newAccount.$id); + } + + setFormSuccess("Account erfolgreich erstellt!"); + setTimeout(() => { + handleCloseForm(); + }, 1500); + } catch (e) { + // Fehlerbehandlung + const errorMessage = + e.message || "Fehler beim Erstellen des Accounts. Bitte versuche es erneut."; + setFormError(errorMessage); + } finally { + setFormLoading(false); + } + }; + + // Spalten für die Tabelle + const columns = [ + "Account Name", + "Platform", + "Platform Account ID", + "Market", + "Account URL", + "Status", + "Last Scan", + ...(showAdvanced ? ["Owner User ID"] : []), + "Action", + ]; + + const renderCell = (col, row) => { + if (col === "Action") { + const accountId = row.$id || row.id; + const isRefreshing = refreshingAccountId === accountId; + + return ( +
+ + +
+ ); + } + + if (col === "Account Name") { + return getAccountDisplayName(row) || "-"; + } + + if (col === "Platform") { + return row.account_platform || "-"; + } + + if (col === "Platform Account ID") { + return row.account_platform_account_id || "-"; + } + + if (col === "Market") { + return row.account_platform_market || "-"; + } + + if (col === "Account URL") { + const url = row.account_url; + if (!url) return "-"; + return ( + + {url.length > 40 ? `${url.substring(0, 40)}...` : url} + + ); + } + + if (col === "Status") { + return row.account_status || "-"; + } + + if (col === "Last Scan") { + const lastScan = row.account_last_scan_at; + if (!lastScan) return "-"; + try { + const date = new Date(lastScan); + return date.toLocaleString("de-DE", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + } catch (e) { + return "-"; + } + } + + if (col === "Owner User ID") { + return row.account_owner_user_id || "-"; + } + + return "-"; + }; + + return ( +
+
+ {/* Header */} +
+
+

+ Accounts +

+

+ Verwalte deine Plattform-Accounts +

+
+ +
+ + {/* Hilfe-Panel */} +
+
+
+

+ Account hinzufügen +

+
+
+ + eBay Account URL (Pflichtfeld) + + : Gib einfach die eBay-URL zum Verkäuferprofil oder Shop ein. Alle weiteren Informationen (Market, Seller ID, Shop Name) werden automatisch erkannt. +
+
+ + Market (Auto) + + : Wird automatisch aus der URL extrahiert (z.B. DE, US, UK). Du musst nichts eingeben. +
+
+ + eBay Seller ID (Auto) + + : Wird automatisch erkannt. Dies ist die eindeutige Verkäufer-Kennung von eBay und verhindert Duplikate. +
+
+ + Shop Name (Auto) + + : Öffentlich sichtbarer Name des Shops. Wird automatisch aus der URL/Seite extrahiert. +
+
+ + Status (Auto) + + : Wird automatisch auf "active" gesetzt. Du musst nichts eingeben. +
+
+
+ So funktioniert's:{" "} + Gib einfach die eBay-URL ein und klicke auf "Account hinzufügen". Das System liest alle notwendigen Informationen automatisch aus. Du musst keine technischen Felder manuell ausfüllen. +
+ + {/* Advanced Toggle */} + +
+
+ + {/* Toast Notification */} + + {refreshToast.show && ( + + {refreshToast.message} + + )} + + + {/* Tabelle */} +
+
+
+ {loading ? ( +
+ Loading accounts... +
+ ) : ( + + )} +
+
+ + {/* Add Account Form Modal */} + + {showAddForm && ( + + e.stopPropagation()} + className="relative w-full max-w-2xl rounded-2xl border border-[var(--line)] bg-white p-6 shadow-xl dark:bg-neutral-800" + > + {/* Close Button */} + + + {/* Form Header */} +

+ Add Account +

+ + {/* Error/Success Messages */} + {parsingError && ( +
+ {parsingError} +
+ )} + {formError && ( +
+ {formError} +
+ )} + {formSuccess && ( +
+ {formSuccess} +
+ )} + + {/* Form */} +
+ {/* Phase 1: URL Input */} +
+ + handleFormChange("account_url", e.target.value)} + placeholder="https://www.ebay.de/usr/..." + required + disabled={parsing || formLoading} + className="w-full rounded-lg border border-[var(--line)] bg-white px-3 py-2 text-sm text-[var(--text)] outline-none transition-colors focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-neutral-900" + /> +

+ Füge den Link zum eBay-Verkäuferprofil oder Shop ein. Wir lesen die Account-Daten automatisch aus. +

+
+ + {/* Phase 2: Preview (wenn parsedData vorhanden) */} + {parsedData && ( +
+

+ Account-Informationen (automatisch erkannt) +

+ +
+ + +

+ Automatisch erkannter Marktplatz (z.B. DE oder US). +

+
+ +
+ + +

+ Eindeutige Verkäufer-ID von eBay. Wird für Abgleich und Duplikat-Erkennung verwendet. +

+
+ +
+ + +

+ Öffentlich sichtbarer Name des Shops auf eBay. +

+
+ +
+ + +

+ Automatisch aus dem eBay-Profil gelesen. +

+
+ +
+ + +

+ Interner Status. Normalerweise "active". +

+
+
+ )} + + {/* Form Actions */} +
+ + {parsedData ? ( + + ) : ( + + )} +
+
+
+
+ )} +
+
+
+ ); +}; diff --git a/Server/src/services/accountService.js b/Server/src/services/accountService.js new file mode 100644 index 0000000..6ab1508 --- /dev/null +++ b/Server/src/services/accountService.js @@ -0,0 +1,99 @@ +/** + * Account Service + * Verwaltet den aktiven Account über localStorage + */ + +const ACTIVE_ACCOUNT_ID_KEY = "active_account_id"; + +/** + * Gibt die ID des aktiven Accounts aus localStorage zurück + * @returns {string|null} Account ID oder null + */ +export function getActiveAccountId() { + try { + return localStorage.getItem(ACTIVE_ACCOUNT_ID_KEY); + } catch (e) { + console.warn("Fehler beim Lesen von localStorage:", e); + return null; + } +} + +/** + * Speichert die ID des aktiven Accounts in localStorage + * Dispatch ein CustomEvent für sofortigen Sidebar-Refresh + * @param {string} accountId - Account ID + */ +export function setActiveAccountId(accountId) { + try { + if (accountId) { + localStorage.setItem(ACTIVE_ACCOUNT_ID_KEY, accountId); + } else { + localStorage.removeItem(ACTIVE_ACCOUNT_ID_KEY); + } + + // CustomEvent dispatch für Sidebar-Refresh + window.dispatchEvent( + new CustomEvent("activeAccountChanged", { detail: { accountId } }) + ); + } catch (e) { + console.warn("Fehler beim Schreiben in localStorage:", e); + } +} + +/** + * Bestimmt den aktiven Account aus der Liste + * Wenn eine ID in localStorage gespeichert ist und gültig, wird dieser verwendet + * Ansonsten wird der erste Account als Default genommen + * @param {Array} accounts - Liste der Accounts + * @returns {Object|null} Aktiver Account oder null + */ +export function resolveActiveAccount(accounts) { + if (!accounts || accounts.length === 0) { + return null; + } + + const storedId = getActiveAccountId(); + + if (storedId) { + const foundAccount = accounts.find((acc) => acc.$id === storedId || acc.id === storedId); + if (foundAccount) { + return foundAccount; + } + } + + // Fallback: Erster Account als Default + const firstAccount = accounts[0]; + if (firstAccount) { + const accountId = firstAccount.$id || firstAccount.id; + if (accountId) { + setActiveAccountId(accountId); + } + } + + return firstAccount || null; +} + +/** + * Gibt den Display-Namen eines Accounts zurück + * Priorität: account_shop_name > account_platform_account_id + * @param {Object} account - Account Objekt + * @returns {string} Display-Name + */ +export function getAccountDisplayName(account) { + if (!account) return ""; + + if (account.account_shop_name) { + return account.account_shop_name; + } + + if (account.account_platform_account_id) { + return account.account_platform_account_id; + } + + // Fallback für alte Datenstruktur + if (account.shop) { + return account.shop; + } + + return account.id || account.$id || "Unknown Account"; +} \ No newline at end of file diff --git a/Server/src/services/accountsService.js b/Server/src/services/accountsService.js new file mode 100644 index 0000000..be1b1a0 --- /dev/null +++ b/Server/src/services/accountsService.js @@ -0,0 +1,339 @@ +/** + * Accounts Service + * CRUD-Operationen für Accounts aus Appwrite + * Verwaltet nur "Managed Accounts" (account_owner_user_id == authUserId) + */ + +import { databases, databaseId, accountsCollectionId } from "../lib/appwrite"; +import { ID, Query } from "appwrite"; + +/** + * Lädt ein einzelnes Account nach ID + * @param {string} accountId - ID des Accounts + * @returns {Promise} Account-Dokument oder null + */ +export async function getAccountById(accountId) { + if (!accountId) { + console.warn("getAccountById: accountId fehlt"); + return null; + } + + try { + const document = await databases.getDocument( + databaseId, + accountsCollectionId, + accountId + ); + return document; + } catch (e) { + // 404 bedeutet, dass das Dokument nicht existiert + if (e.code === 404 || e.type === 'document_not_found') { + return null; + } + console.error("Fehler beim Laden des Accounts:", e); + throw e; + } +} + +/** + * Leitet den Market-Code aus einem Account ab + * @param {Object} account - Account-Dokument + * @returns {string} Market-Code (z.B. "DE", "US", "UNKNOWN") + */ +export function deriveMarketFromAccount(account) { + if (!account) { + return "UNKNOWN"; + } + + // Priority 1: account_platform_market Feld vorhanden + if (account.account_platform_market && account.account_platform_market.trim()) { + return account.account_platform_market.trim().toUpperCase(); + } + + // Priority 2: Ableitung aus account_url Hostname + if (account.account_url) { + try { + const url = new URL(account.account_url); + const hostname = url.hostname.toLowerCase(); + + // Mapping von Hostname zu Market-Code + const hostnameToMarket = { + "ebay.de": "DE", + "www.ebay.de": "DE", + "ebay.com": "US", + "www.ebay.com": "US", + "ebay.co.uk": "UK", + "www.ebay.co.uk": "UK", + "ebay.fr": "FR", + "www.ebay.fr": "FR", + "ebay.it": "IT", + "www.ebay.it": "IT", + "ebay.es": "ES", + "www.ebay.es": "ES", + "ebay.ca": "CA", + "www.ebay.ca": "CA", + "ebay.com.au": "AU", + "www.ebay.com.au": "AU", + }; + + if (hostnameToMarket[hostname]) { + return hostnameToMarket[hostname]; + } + + // Fallback: Prüfe ob hostname ein bekanntes Pattern enthält + if (hostname.includes("ebay.de")) return "DE"; + if (hostname.includes("ebay.com") && !hostname.includes("ebay.com.au")) return "US"; + if (hostname.includes("ebay.co.uk")) return "UK"; + if (hostname.includes("ebay.fr")) return "FR"; + if (hostname.includes("ebay.it")) return "IT"; + if (hostname.includes("ebay.es")) return "ES"; + if (hostname.includes("ebay.ca")) return "CA"; + if (hostname.includes("ebay.com.au")) return "AU"; + } catch (e) { + // URL parsing fehlgeschlagen + console.debug("Fehler beim Parsen der Account-URL:", e); + } + } + + return "UNKNOWN"; +} + +/** + * Leitet die Währung aus einem Market-Code ab + * @param {string} market - Market-Code (z.B. "DE", "US", "UK") + * @returns {string} Währungscode (z.B. "EUR", "USD", "GBP") + */ +export function deriveCurrencyFromMarket(market) { + if (!market || market === "UNKNOWN") { + return "EUR"; // Fallback + } + + const marketUpper = market.toUpperCase(); + + // Mapping Market -> Currency + const marketToCurrency = { + // EUR Länder + "DE": "EUR", + "FR": "EUR", + "IT": "EUR", + "ES": "EUR", + "NL": "EUR", + "AT": "EUR", + "BE": "EUR", + "IE": "EUR", + // Andere Währungen + "US": "USD", + "UK": "GBP", + "CA": "CAD", + "AU": "AUD", + }; + + return marketToCurrency[marketUpper] || "EUR"; // Fallback zu EUR +} + +/** + * Lädt alle Managed Accounts für einen User + * @param {string} authUserId - ID des eingeloggten Users + * @returns {Promise} Array von Account-Dokumenten + */ +export async function fetchManagedAccounts(authUserId) { + if (!authUserId) { + console.warn("fetchManagedAccounts: authUserId fehlt"); + return []; + } + + try { + const response = await databases.listDocuments( + databaseId, + accountsCollectionId, + [ + Query.equal("account_owner_user_id", authUserId), + Query.orderDesc("$createdAt"), + ] + ); + + return response.documents; + } catch (e) { + console.error("Fehler beim Laden der Accounts:", e); + + // Wenn Collection nicht existiert oder Berechtigungen fehlen + if (e.code === 404 || e.code === 401) { + return []; + } + + throw e; + } +} + +/** + * Erstellt ein neues Managed Account + * @param {string} authUserId - ID des eingeloggten Users + * @param {Object} accountData - Account-Daten + * @param {string} accountData.account_platform_market - Required: Market (z.B. "DE", "US") + * @param {string} accountData.account_platform_account_id - Required: Platform Account ID + * @param {string} [accountData.account_shop_name] - Optional: Shop-Name + * @param {string} [accountData.account_url] - Optional: Account URL + * @param {string} [accountData.account_status] - Optional: Status (z.B. "active", "unknown", "disabled") + * @returns {Promise} Erstelltes Account-Dokument + */ +export async function createManagedAccount(authUserId, accountData) { + if (!authUserId) { + throw new Error("authUserId ist erforderlich"); + } + + // Validierung der Required-Felder + const { account_platform_market, account_platform_account_id } = accountData; + + // account_platform wird IMMER "ebay" gesetzt (eBay-only Tool) + if (!account_platform_market) { + throw new Error("account_platform_market ist erforderlich"); + } + if (!account_platform_account_id) { + throw new Error("account_platform_account_id ist erforderlich"); + } + + // Payload zusammenstellen + const payload = { + account_owner_user_id: authUserId, + account_platform: "ebay", // IMMER "ebay" für über UI erstellte Accounts + account_platform_market, + account_platform_account_id, + account_shop_name: accountData.account_shop_name || null, + account_url: accountData.account_url || null, + account_sells: accountData.account_sells ?? null, + account_managed: true, // Immer true für über die UI erstellte Accounts + }; + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:72',message:'createManagedAccount: payload before Appwrite',data:{account_sells:payload.account_sells,accountData_account_sells:accountData.account_sells},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{}); + // #endregion + + // account_status ist optional - aufgrund Schema-Konflikt vorerst weglassen + // TODO: Schema in Appwrite prüfen und korrigieren (Enum-Feld sollte String akzeptieren, nicht Array) + // Das Feld wird erst wieder hinzugefügt, wenn das Schema korrekt konfiguriert ist + + // Document-Level Permissions für den User setzen + const permissions = [ + `read("user:${authUserId}")`, + `update("user:${authUserId}")`, + `delete("user:${authUserId}")`, + ]; + + try { + const document = await databases.createDocument( + databaseId, + accountsCollectionId, + ID.unique(), + payload, + permissions + ); + + return document; + } catch (e) { + // Duplicate-Conflict behandeln (falls Unique-Index auf platform+market+platform_account_id existiert) + if (e.code === 409 || e.message?.includes("duplicate") || e.message?.includes("unique")) { + throw new Error( + "Dieser Account ist bereits verbunden." + ); + } + + console.error("Fehler beim Erstellen des Accounts:", e); + throw e; + } +} + +/** + * Aktualisiert ein Managed Account (optional für v1) + * @param {string} accountId - ID des Accounts + * @param {Object} accountData - Zu aktualisierende Account-Daten + * @returns {Promise} Aktualisiertes Account-Dokument + */ +export async function updateManagedAccount(accountId, accountData) { + if (!accountId) { + throw new Error("accountId ist erforderlich"); + } + + try { + // Entferne undefined/null Werte aus accountData (behalte nur geänderte Felder) + const payload = {}; + Object.keys(accountData).forEach((key) => { + if (accountData[key] !== undefined && accountData[key] !== null) { + payload[key] = accountData[key]; + } + }); + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:133',message:'updateManagedAccount: before updateDocument',data:{accountId,payload,payloadKeys:Object.keys(payload)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + + const document = await databases.updateDocument( + databaseId, + accountsCollectionId, + accountId, + payload + ); + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:147',message:'updateManagedAccount: success',data:{accountId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{}); + // #endregion + return document; + } catch (e) { + console.error("Fehler beim Aktualisieren des Accounts:", e); + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'accountsService.js:149',message:'updateManagedAccount: error',data:{accountId,errorCode:e.code,errorMessage:e.message,errorType:e.type},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{}); + // #endregion + throw e; + } +} + +/** + * Aktualisiert das account_last_scan_at Feld eines Accounts + * @param {string} accountId - ID des Accounts + * @param {string} isoString - ISO 8601 Datum-String (z.B. "2026-01-18T12:34:56.000Z") + * @returns {Promise} Aktualisiertes Account-Dokument + */ +export async function updateAccountLastScanAt(accountId, isoString) { + if (!accountId) { + throw new Error("accountId ist erforderlich"); + } + if (!isoString) { + throw new Error("isoString ist erforderlich"); + } + + try { + const document = await databases.updateDocument( + databaseId, + accountsCollectionId, + accountId, + { + account_last_scan_at: isoString, + } + ); + + return document; + } catch (e) { + console.error("Fehler beim Aktualisieren des account_last_scan_at:", e); + throw e; + } +} + +/** + * Löscht ein Managed Account (optional für v1) + * @param {string} accountId - ID des Accounts + * @returns {Promise} + */ +export async function deleteManagedAccount(accountId) { + if (!accountId) { + throw new Error("accountId ist erforderlich"); + } + + try { + await databases.deleteDocument( + databaseId, + accountsCollectionId, + accountId + ); + } catch (e) { + console.error("Fehler beim Löschen des Accounts:", e); + throw e; + } +} diff --git a/Server/src/services/dashboardService.js b/Server/src/services/dashboardService.js new file mode 100644 index 0000000..10eecd3 --- /dev/null +++ b/Server/src/services/dashboardService.js @@ -0,0 +1,301 @@ +/** + * Dashboard Service + * Stellt aggregierte Dashboard-Daten bereit, gescoped nach activeAccountId + * Nutzt productsService und accountsService für Datenabfragen + */ + +import { listProductsByAccount, getProductById } from "./productsService"; +import { databases, databaseId } from "../lib/appwrite"; +import { Query } from "appwrite"; + +const productDetailsCollectionId = + import.meta.env.VITE_APPWRITE_PRODUCT_DETAILS_COLLECTION_ID || "product_details"; + +/** + * Lädt Overview KPIs für einen aktiven Account + * @param {string} activeAccountId - ID des aktiven Accounts + * @returns {Promise<{ totalProducts: number, activeProducts: number, endedProducts: number, avgPrice: number | null, newestProducts: Array }>} + */ +export async function getOverviewKPIs(activeAccountId) { + if (!activeAccountId) { + return { + totalProducts: 0, + activeProducts: 0, + endedProducts: 0, + avgPrice: null, + newestProducts: [], + }; + } + + try { + // Lade bis zu 200 Products für KPI-Berechnung + const products = await listProductsByAccount(activeAccountId, { + limit: 200, + offset: 0, + orderBy: "$createdAt", + orderType: "desc", + }); + + const totalProducts = products.length; + + // Filtere nach Status + const activeProducts = products.filter( + (p) => p.product_status === "active" || p.product_status === "Active" + ); + const endedProducts = products.filter( + (p) => p.product_status === "ended" || p.product_status === "Ended" + ); + + // Berechne Durchschnittspreis für aktive Products + const activeWithPrice = activeProducts.filter( + (p) => p.product_price != null && !isNaN(parseFloat(p.product_price)) + ); + const avgPrice = + activeWithPrice.length > 0 + ? activeWithPrice.reduce((sum, p) => sum + parseFloat(p.product_price), 0) / + activeWithPrice.length + : null; + + // Neueste 5 Products (bereits nach $createdAt desc sortiert) + const newestProducts = products.slice(0, 5); + + return { + totalProducts, + activeProducts: activeProducts.length, + endedProducts: endedProducts.length, + avgPrice: avgPrice ? Math.round(avgPrice * 100) / 100 : null, + newestProducts, + }; + } catch (e) { + console.error("Fehler beim Laden der Overview KPIs:", e); + throw e; + } +} + +/** + * Lädt eine paginierte Seite von Products für einen aktiven Account + * @param {string} activeAccountId - ID des aktiven Accounts + * @param {Object} options - Pagination und Filter-Optionen + * @param {number} options.page - Seitennummer (1-basiert) + * @param {number} options.pageSize - Anzahl Items pro Seite + * @param {Object} [options.filters] - Filter-Optionen + * @param {string} [options.filters.status] - Status-Filter ("active", "ended", etc.) + * @param {string} [options.filters.search] - Suchbegriff für Titel (client-side) + * @returns {Promise<{ items: Array, total: number, page: number, pageSize: number }>} + */ +export async function getProductsPage(activeAccountId, options = {}) { + if (!activeAccountId) { + return { + items: [], + total: 0, + page: 1, + pageSize: options.pageSize || 25, + }; + } + + const { page = 1, pageSize = 25, filters = {} } = options; + const offset = (page - 1) * pageSize; + + try { + // Lade Products mit Pagination + // Für MVP: Lade mehr als nötig (z.B. pageSize * 3) um client-side Filter zu unterstützen + const fetchLimit = Math.max(pageSize * 3, 100); // Mindestens 100, aber mehr wenn pageSize größer + let products = await listProductsByAccount(activeAccountId, { + limit: fetchLimit, + offset: 0, + orderBy: "$createdAt", + orderType: "desc", + }); + + // Client-side Filter anwenden (falls Appwrite Query-Filter nicht unterstützt) + if (filters.status && filters.status !== "all") { + products = products.filter( + (p) => + p.product_status?.toLowerCase() === filters.status.toLowerCase() + ); + } + + if (filters.search && filters.search.trim()) { + const searchLower = filters.search.trim().toLowerCase(); + products = products.filter((p) => + p.product_title?.toLowerCase().includes(searchLower) + ); + } + + const total = products.length; + + // Pagination auf gefilterten Ergebnissen anwenden + const startIndex = offset; + const endIndex = startIndex + pageSize; + const items = products.slice(startIndex, endIndex); + + return { + items, + total, + page, + pageSize, + }; + } catch (e) { + console.error("Fehler beim Laden der Products Page:", e); + throw e; + } +} + +/** + * Lädt ein Product mit optionalen Details aus product_details Collection + * @param {string} productId - ID des Products + * @returns {Promise} Product-Objekt mit optionalen Details + */ +export async function getProductPreview(productId) { + if (!productId) { + throw new Error("productId ist erforderlich"); + } + + try { + // Lade Product + const product = await getProductById(productId); + if (!product) { + return null; + } + + // Versuche product_details zu laden (optional, falls vorhanden) + let details = null; + try { + const detailsResponse = await databases.listDocuments( + databaseId, + productDetailsCollectionId, + [Query.equal("product_detail_product_id", productId), Query.limit(1)] + ); + + if (detailsResponse.documents && detailsResponse.documents.length > 0) { + details = detailsResponse.documents[0]; + } + } catch (detailsError) { + // product_details Collection existiert möglicherweise nicht oder hat keine Permissions + // Das ist OK, Details sind optional + console.debug("product_details konnten nicht geladen werden:", detailsError); + } + + // Kombiniere Product mit Details + return { + ...product, + details, + }; + } catch (e) { + console.error("Fehler beim Laden des Product Previews:", e); + throw e; + } +} + +/** + * Lädt Insights für einen aktiven Account (regelbasiert, MVP) + * @param {string} activeAccountId - ID des aktiven Accounts + * @returns {Promise<{ trending: Array, priceSpread: Array, categoryShare: Array }>} + */ +export async function getInsights(activeAccountId) { + if (!activeAccountId) { + return { + trending: [], + priceSpread: [], + categoryShare: [], + }; + } + + try { + // Lade bis zu 500 Products für Insights-Berechnung + const products = await listProductsByAccount(activeAccountId, { + limit: 500, + offset: 0, + orderBy: "$createdAt", + orderType: "desc", + }); + + // 1. Trending: Neueste 20 Products als "recent" + const trending = products.slice(0, 20).map((p) => ({ + product: p, + label: p.product_title || p.$id, + value: p.product_price ? `EUR ${p.product_price}` : "N/A", + })); + + // 2. Price Spread: Gruppiere nach normalisiertem Titel (lowercase, remove digits) + // Berechne max-min Spread pro Gruppe, Top 5 + const titleGroups = new Map(); + + products.forEach((p) => { + if (!p.product_title || !p.product_price) return; + + // Normalisiere Titel: lowercase, entferne Zahlen + const normalizedTitle = p.product_title + .toLowerCase() + .replace(/\d+/g, "") + .trim(); + + if (!normalizedTitle) return; + + const price = parseFloat(p.product_price); + if (isNaN(price)) return; + + if (!titleGroups.has(normalizedTitle)) { + titleGroups.set(normalizedTitle, { + title: normalizedTitle, + prices: [], + originalTitles: new Set(), + }); + } + + const group = titleGroups.get(normalizedTitle); + group.prices.push(price); + group.originalTitles.add(p.product_title); + }); + + // Berechne Spread für jede Gruppe + const spreads = Array.from(titleGroups.values()) + .map((group) => { + if (group.prices.length < 2) return null; // Mindestens 2 Preise für Spread + + const minPrice = Math.min(...group.prices); + const maxPrice = Math.max(...group.prices); + const spread = maxPrice - minPrice; + + return { + title: Array.from(group.originalTitles)[0], // Erstes Original-Titel als Label + minPrice, + maxPrice, + spread, + count: group.prices.length, + label: `${Array.from(group.originalTitles)[0]} (${group.prices.length}x)`, + value: `EUR ${minPrice.toFixed(2)} - ${maxPrice.toFixed(2)} (Spread: ${spread.toFixed(2)})`, + }; + }) + .filter((s) => s !== null) + .sort((a, b) => b.spread - a.spread) // Sortiere nach Spread (höchster zuerst) + .slice(0, 5); // Top 5 + + // 3. Category Share: Zähle nach product_category, Top 5 + const categoryCounts = new Map(); + + products.forEach((p) => { + const category = p.product_category || "Uncategorized"; + categoryCounts.set(category, (categoryCounts.get(category) || 0) + 1); + }); + + const categoryShare = Array.from(categoryCounts.entries()) + .map(([category, count]) => ({ + category, + count, + label: category, + value: `${count} items`, + })) + .sort((a, b) => b.count - a.count) // Sortiere nach Count (höchster zuerst) + .slice(0, 5); // Top 5 + + return { + trending, + priceSpread: spreads, + categoryShare, + }; + } catch (e) { + console.error("Fehler beim Laden der Insights:", e); + throw e; + } +} \ No newline at end of file diff --git a/Server/src/services/ebayParserService.js b/Server/src/services/ebayParserService.js new file mode 100644 index 0000000..33e9bd1 --- /dev/null +++ b/Server/src/services/ebayParserService.js @@ -0,0 +1,515 @@ +/** + * eBay URL Parser Service (Stub) + * Extrahiert Account-Daten aus eBay-URLs + * + * WICHTIG: Dies ist eine Stub-Implementierung ohne echte Network-Calls. + * Später wird diese Funktion durch einen Call zur Browser-Extension ersetzt. + * + * Regeln: + * - Deterministisch: Gleiche URL → gleiche Daten + * - Keine Network-Calls (kein fetch, kein iframe, kein HTML-Parsing) + * - Ersetzbar durch Extension-Call + */ + +/** + * Einfacher stabiler Hash für deterministische IDs + * @param {string} str - Input string + * @returns {string} - Hash string + */ +function stableHash(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return Math.abs(hash).toString(36).padStart(10, '0'); +} + +/** + * Extrahiert Marktplatz aus eBay-URL (Domain) + * @param {string} url - eBay-URL + * @returns {string} - Market Code (z.B. "DE", "US", "UK") + */ +function extractMarketFromUrl(url) { + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname.toLowerCase(); + + // eBay Domain-Patterns + if (hostname.includes('.de') || hostname.includes('ebay.de')) { + return 'DE'; + } + if (hostname.includes('.com') || hostname.includes('ebay.com')) { + // Prüfe auf US-spezifische Patterns + if (hostname.includes('ebay.com') && !hostname.includes('.uk')) { + return 'US'; + } + } + if (hostname.includes('.uk') || hostname.includes('ebay.co.uk')) { + return 'UK'; + } + if (hostname.includes('.fr') || hostname.includes('ebay.fr')) { + return 'FR'; + } + if (hostname.includes('.it') || hostname.includes('ebay.it')) { + return 'IT'; + } + if (hostname.includes('.es') || hostname.includes('ebay.es')) { + return 'ES'; + } + if (hostname.includes('.nl') || hostname.includes('ebay.nl')) { + return 'NL'; + } + if (hostname.includes('.at') || hostname.includes('ebay.at')) { + return 'AT'; + } + if (hostname.includes('.ch') || hostname.includes('ebay.ch')) { + return 'CH'; + } + + // Fallback: erster Teil der Domain nach "ebay." + const match = hostname.match(/ebay\.([a-z]{2,3})/); + if (match && match[1]) { + return match[1].toUpperCase(); + } + + // Default: US + return 'US'; + } catch (e) { + return 'US'; // Fallback + } +} + +// Extension-ID Cache (gesetzt via postMessage vom Content Script) +let cachedExtensionId = null; + +// Listener für Extension-ID vom Content Script +if (typeof window !== 'undefined') { + window.addEventListener('message', (event) => { + if (event.data?.source === 'eship-extension' && event.data?.type === 'EXTENSION_ID' && event.data?.extensionId) { + cachedExtensionId = event.data.extensionId; + } + }); +} + +/** + * Prüft ob die Browser-Extension verfügbar ist + * @returns {boolean} - true wenn Extension verfügbar + */ +export function isExtensionAvailable() { + if (typeof window === 'undefined') { + return false; + } + + // Prüfe ob Extension-ID gecacht ist (via postMessage empfangen) + if (cachedExtensionId) { + return true; + } + + // Window flag (set by content script) - das ist das Haupt-Kriterium + // Der Content Script setzt window.__EBAY_EXTENSION__ = true wenn er läuft + const flagValue = window.__EBAY_EXTENSION__; + if (flagValue === true) { + return true; + } + + // chrome.runtime.sendMessage allein bedeutet NICHT, dass die Extension verfügbar ist + // (Chrome macht chrome.runtime auch ohne Extension verfügbar, aber ohne Extension-ID können wir sie nicht nutzen) + // Daher: Nur window.__EBAY_EXTENSION__ ist der zuverlässige Indikator + + return false; +} + +/** + * Holt Extension-ID (für externally_connectable messaging) + * @returns {Promise} - Extension-ID oder null + */ +async function getExtensionId() { + try { + // Methode 1: Verwende gecachte Extension-ID (via postMessage vom Content Script empfangen) + if (cachedExtensionId) { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:135',message:'getExtensionId: found via cache',data:{cachedExtensionId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + return cachedExtensionId; + } + + // Retry-Mechanismus: Warte auf postMessage vom Content Script + for (let attempt = 0; attempt < 10; attempt++) { + if (cachedExtensionId) { + return cachedExtensionId; + } + + // Warte kurz vor dem nächsten Versuch (außer beim letzten Versuch) + if (attempt < 9) { + await new Promise(resolve => setTimeout(resolve, 200)); // 200ms delay + } + } + + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:150',message:'getExtensionId: not found after retries',data:{hasWindow:typeof window!=='undefined'},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + return null; // Nicht verfügbar + } catch (e) { + return null; + } +} + +/** + * Parst eine eBay-URL über die Browser-Extension + * @param {string} url - eBay-Verkäuferprofil oder Shop-URL + * @returns {Promise<{sellerId: string, shopName: string, market: string, status: "active" | "unknown", stats?: object}>} + * @throws {Error} - "Extension not available" oder andere Fehler + */ +async function parseViaExtension(url) { + // Validierung + if (!url || typeof url !== 'string') { + throw new Error("Invalid URL"); + } + + // Methode 1: chrome.runtime.sendMessage (externally_connectable) + // Versuche zuerst mit Extension-ID, dann ohne (Chrome findet Extension automatisch bei externally_connectable) + if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.sendMessage) { + try { + const extensionId = await getExtensionId(); + + // Versuche chrome.runtime.sendMessage (mit oder ohne Extension-ID) + return new Promise((resolve, reject) => { + const message = { + action: "PARSE_URL", + url: url + }; + + // SendMessage-Callback + const sendMessageCallback = (response) => { + // Check for Chrome runtime errors + if (chrome.runtime.lastError) { + const errorMsg = chrome.runtime.lastError.message || "Extension communication error"; + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:158',message:'parseViaExtension: chrome.runtime.sendMessage error',data:{error:errorMsg,extensionId},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + reject(new Error(errorMsg)); + return; + } + + if (response && response.ok && response.data) { + // Ensure stats object is included (even if empty) + const data = { + sellerId: response.data.sellerId || "", + shopName: response.data.shopName || "", + market: response.data.market || "US", + status: response.data.status || "unknown", + stats: response.data.stats || {} + }; + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:160',message:'parseViaExtension: response data from extension',data:{hasStats:!!response.data.stats,itemsSold:response.data.stats?.itemsSold,stats:response.data.stats},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + resolve(data); + } else { + reject(new Error(response?.error || "Extension parsing failed")); + } + }; + + // Benötigt Extension-ID (sendMessage von Webseiten aus erfordert immer Extension-ID) + if (!extensionId) { + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:175',message:'parseViaExtension: no extension ID',data:{hasWindowExtensionId:!!(typeof window!=='undefined'&&window.__EBAY_EXTENSION_ID__)},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + reject(new Error("Extension ID not available")); + return; + } + + chrome.runtime.sendMessage(extensionId, message, sendMessageCallback); + + // Timeout nach 20s (Extension hat intern 15s, hier etwas mehr Puffer) + setTimeout(() => { + reject(new Error("Extension timeout")); + }, 20000); + }); + } catch (error) { + // Chrome API Fehler: Fallback zu window.postMessage wenn möglich + if (error.message && !error.message.includes("Extension")) { + throw error; + } + // Weiter zu Methode 2 + } + } + + // Methode 2: Window flag + postMessage (falls Content Script Relay vorhanden) + if (window.__EBAY_EXTENSION__ === true) { + return new Promise((resolve, reject) => { + const messageId = `parse_${Date.now()}_${Math.random()}`; + + // Listener für Antwort + const responseHandler = (event) => { + if (event.data?.source !== 'eship-extension' || event.data?.messageId !== messageId) { + return; + } + + window.removeEventListener('message', responseHandler); + + if (event.data?.ok && event.data?.data) { + const data = { + sellerId: event.data.data.sellerId || "", + shopName: event.data.data.shopName || "", + market: event.data.data.market || "US", + status: event.data.data.status || "unknown", + stats: event.data.data.stats || {} + }; + resolve(data); + } else { + reject(new Error(event.data?.error || "Extension parsing failed")); + } + }; + + window.addEventListener('message', responseHandler); + + // Sende Request via postMessage + window.postMessage({ + source: 'eship-webapp', + action: 'PARSE_URL', + url: url, + messageId: messageId + }, '*'); + + // Timeout + setTimeout(() => { + window.removeEventListener('message', responseHandler); + reject(new Error("Extension timeout")); + }, 20000); + }); + } + + // Keine Extension verfügbar + throw new Error("Extension not available"); +} + +/** + * Parst eine eBay-URL über die Browser-Extension für Produkt-Scan + * @param {string} url - eBay-Verkäuferprofil oder Shop-URL + * @param {string} accountId - Account-ID (wird an Extension übergeben) + * @returns {Promise} - Array von Produkt-Items + * @throws {Error} - "Extension not available" oder andere Fehler + */ +export async function parseViaExtensionScanProducts(url, accountId) { + // Validierung + if (!url || typeof url !== 'string') { + throw new Error("Invalid URL"); + } + + if (!accountId || typeof accountId !== 'string') { + throw new Error("Invalid accountId"); + } + + // Methode 1: chrome.runtime.sendMessage (externally_connectable) + if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.sendMessage) { + try { + const extensionId = await getExtensionId(); + + // Versuche chrome.runtime.sendMessage (mit oder ohne Extension-ID) + return new Promise((resolve, reject) => { + const message = { + action: "SCAN_PRODUCTS", + url: url, + accountId: accountId + }; + + // SendMessage-Callback + const sendMessageCallback = (response) => { + // Check for Chrome runtime errors + if (chrome.runtime.lastError) { + const errorMsg = chrome.runtime.lastError.message || "Extension communication error"; + reject(new Error(errorMsg)); + return; + } + + if (response && response.ok && response.data) { + const items = response.data.items || []; + const meta = response.data.meta || response.meta || {}; + + // Wenn items leer oder ok:false, werfe Fehler mit meta + if (!Array.isArray(items) || items.length === 0) { + const errorMsg = response?.error || "no_items_found"; + const error = new Error(`SCAN_PRODUCTS failed: ${errorMsg} (${meta.pageType || "unknown"})`); + error.meta = meta; + reject(error); + return; + } + + // Erfolg: gib items zurück + resolve(items); + } else { + // Fehler: sende error + meta + const meta = response?.meta || {}; + const errorMsg = response?.error || "Extension scanning failed"; + const error = new Error(`SCAN_PRODUCTS failed: ${errorMsg} (${meta.pageType || "unknown"})`); + error.meta = meta; + reject(error); + } + }; + + // Benötigt Extension-ID (sendMessage von Webseiten aus erfordert immer Extension-ID) + if (!extensionId) { + reject(new Error("Extension ID not available")); + return; + } + + chrome.runtime.sendMessage(extensionId, message, sendMessageCallback); + + // Timeout nach 20s (Extension hat intern 20s) + setTimeout(() => { + reject(new Error("Extension timeout")); + }, 20000); + }); + } catch (error) { + // Chrome API Fehler: weiter zu Methode 2 + if (error.message && !error.message.includes("Extension")) { + throw error; + } + // Weiter zu Methode 2 + } + } + + // Methode 2: Window flag + postMessage (falls Content Script Relay vorhanden) + if (typeof window !== 'undefined' && window.__EBAY_EXTENSION__ === true) { + return new Promise((resolve, reject) => { + const messageId = `scan_${Date.now()}_${Math.random()}`; + + // Listener für Antwort + const responseHandler = (event) => { + if (event.data?.source !== 'eship-extension' || event.data?.messageId !== messageId) { + return; + } + + window.removeEventListener('message', responseHandler); + + if (event.data?.ok && event.data?.data) { + const items = event.data.data.items || event.data.items || []; + const meta = event.data.data.meta || event.data.meta || {}; + + // Wenn items leer oder ok:false, werfe Fehler mit meta + if (!Array.isArray(items) || items.length === 0) { + const errorMsg = event.data?.error || "no_items_found"; + const error = new Error(`SCAN_PRODUCTS failed: ${errorMsg} (${meta.pageType || "unknown"})`); + error.meta = meta; + reject(error); + return; + } + + // Erfolg: gib items zurück + resolve(items); + } else { + // Fehler: sende error + meta + const meta = event.data?.meta || event.data?.data?.meta || {}; + const errorMsg = event.data?.error || "Extension scanning failed"; + const error = new Error(`SCAN_PRODUCTS failed: ${errorMsg} (${meta.pageType || "unknown"})`); + error.meta = meta; + reject(error); + } + }; + + window.addEventListener('message', responseHandler); + + // Sende Request via postMessage + window.postMessage({ + source: 'eship-webapp', + action: 'SCAN_PRODUCTS', + url: url, + accountId: accountId, + messageId: messageId + }, '*'); + + // Timeout + setTimeout(() => { + window.removeEventListener('message', responseHandler); + reject(new Error("Extension timeout")); + }, 20000); + }); + } + + // Keine Extension verfügbar - kein Stub für Produkt-Scan (User will echte Daten) + throw new Error("Extension not available"); +} + +/** + * Parst eine eBay-URL mit Stub-Logik (deterministisch, keine Network-Calls) + * @param {string} url - eBay-Verkäuferprofil oder Shop-URL + * @returns {Promise<{sellerId: string, shopName: string, market: string, status: "active" | "unknown"}>} + * @throws {Error} - Wenn URL ungültig ist oder keine eBay-URL + */ +async function parseViaStub(url) { + // Validierung: Muss gültige URL sein + let urlObj; + try { + urlObj = new URL(url); + } catch (e) { + throw new Error("Bitte gib eine gültige URL ein."); + } + + // Validierung: Muss eBay-URL sein + const hostname = urlObj.hostname.toLowerCase(); + if (!hostname.includes('ebay.')) { + throw new Error("Bitte gib eine gültige eBay-URL ein."); + } + + // Stub-Implementierung: Deterministische Daten aus URL generieren + const hash = stableHash(url); + const market = extractMarketFromUrl(url); + + // Seller ID: Deterministic aus URL-Hash + // Format: "ebay_" + hash (first 10 chars) + const sellerId = `ebay_${hash.slice(0, 10)}`; + + // Shop Name: Generiert aus Hash (last 4 chars als Suffix) + const shopNameSuffix = hash.slice(-4); + const shopName = `eBay Seller ${shopNameSuffix}`; + + // Status: Immer "active" für Stub + const status = "active"; + + return { + sellerId, + shopName, + market, + status, + stats: { + itemsSold: null, // Stub liefert keine echten Daten + }, + }; +} + +/** + * Parst eine eBay-URL und extrahiert Account-Daten (Facade) + * Versucht zuerst Extension-Pfad, fällt zurück auf Stub-Implementierung + * @param {string} url - eBay-Verkäuferprofil oder Shop-URL + * @returns {Promise<{sellerId: string, shopName: string, market: string, status: "active" | "unknown", stats?: object}>} + * @throws {Error} - Wenn URL ungültig ist oder keine eBay-URL + */ +export async function parseEbayAccount(url) { + // Versuche IMMER Extension-Pfad zuerst (auch wenn Flag nicht gesetzt) + // parseViaExtension prüft selbst, ob Extension verfügbar ist + // #region agent log + const extAvailable = isExtensionAvailable(); + const hasChromeRuntime = typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.sendMessage; + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:292',message:'parseEbayAccount: route decision',data:{extAvailable,hasChromeRuntime,url},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + + try { + // Versuche Extension zu nutzen (auch wenn Flag nicht gesetzt - parseViaExtension prüft selbst) + return await parseViaExtension(url); + } catch (e) { + // Extension-Fehler: Fallback zu Stub + console.warn("Extension parsing failed, falling back to stub:", e.message); + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:299',message:'parseEbayAccount: extension error, using stub',data:{error:e.message},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + } + + // Fallback: Stub-Implementierung + const stubResult = await parseViaStub(url); + // #region agent log + fetch('http://127.0.0.1:7242/ingest/246fe132-ecc5-435f-bd9c-fe5e8e26089d',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'ebayParserService.js:304',message:'parseEbayAccount: stub result',data:{itemsSold:stubResult.stats?.itemsSold},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{}); + // #endregion + return stubResult; +} \ No newline at end of file diff --git a/Server/src/services/productsService.js b/Server/src/services/productsService.js new file mode 100644 index 0000000..053e908 --- /dev/null +++ b/Server/src/services/productsService.js @@ -0,0 +1,258 @@ +/** + * Products Service + * CRUD-Operationen für Products aus Appwrite + * Filtert nach product_account_id für Account-Scoping + */ + +import { databases, databaseId } from "../lib/appwrite"; +import { Query, ID } from "appwrite"; +import { getAccountById, deriveMarketFromAccount, deriveCurrencyFromMarket, updateAccountLastScanAt } from "./accountsService"; +import { parseViaExtensionScanProducts } from "./ebayParserService"; + +const productsCollectionId = import.meta.env.VITE_APPWRITE_PRODUCTS_COLLECTION_ID || "products"; + +/** + * Lädt alle Products für einen bestimmten Account + * @param {string} accountId - ID des Accounts (product_account_id) + * @param {Object} [options] - Optionale Parameter + * @param {number} [options.limit] - Maximale Anzahl Ergebnisse (default: 25) + * @param {number} [options.offset] - Offset für Pagination (default: 0) + * @param {string} [options.orderBy] - Feld zum Sortieren (default: "$createdAt") + * @param {string} [options.orderType] - Sortier-Richtung: "asc" oder "desc" (default: "desc") + * @returns {Promise} Array von Product-Dokumenten + */ +export async function listProductsByAccount(accountId, options = {}) { + if (!accountId) { + console.warn("listProductsByAccount: accountId fehlt"); + return []; + } + + const { + limit = 25, + offset = 0, + orderBy = "$createdAt", + orderType = "desc", + } = options; + + try { + const queries = [ + Query.equal("product_account_id", accountId), + Query.orderDesc(orderBy), // orderType wird über Desc/Asc gehandhabt + Query.limit(limit), + Query.offset(offset), + ]; + + // Wenn orderType "asc" ist, verwende orderAsc statt orderDesc + if (orderType === "asc") { + queries[1] = Query.orderAsc(orderBy); + } + + const response = await databases.listDocuments( + databaseId, + productsCollectionId, + queries + ); + + return response.documents; + } catch (e) { + console.error("Fehler beim Laden der Products:", e); + + // Wenn Collection nicht existiert oder Berechtigungen fehlen + if (e.code === 404 || e.code === 401 || e.type === 'collection_not_found') { + // Placeholder: Collection existiert noch nicht + console.warn("Products-Collection existiert noch nicht oder ist nicht verfügbar. Gebe leeres Array zurück."); + return []; + } + + throw e; + } +} + +/** + * Lädt ein einzelnes Product nach ID + * @param {string} productId - ID des Products + * @returns {Promise} Product-Dokument oder null + */ +export async function getProductById(productId) { + if (!productId) { + console.warn("getProductById: productId fehlt"); + return null; + } + + try { + const document = await databases.getDocument( + databaseId, + productsCollectionId, + productId + ); + return document; + } catch (e) { + // 404 bedeutet, dass das Dokument nicht existiert + if (e.code === 404 || e.type === 'document_not_found') { + return null; + } + console.error("Fehler beim Laden des Products:", e); + throw e; + } +} + +/** + * Scannt Produkte für einen Account über die Extension (echte eBay-Daten) + * @param {string} accountId - ID des Accounts + * @returns {Promise<{ created: number, updated: number }>} Anzahl erstellter/aktualisierter Produkte + */ +export async function scanProductsForAccount(accountId) { + if (!accountId) { + throw new Error("accountId ist erforderlich"); + } + + try { + // 1. Lade Account, um URL und andere Daten abzuleiten + const account = await getAccountById(accountId); + if (!account) { + throw new Error("Account nicht gefunden"); + } + + if (!account.account_url) { + throw new Error("Account-URL fehlt. Bitte Account refreshen."); + } + + // 2. Extension aufrufen: scanne Produkte + const items = await parseViaExtensionScanProducts(account.account_url, accountId); + + // 3. Response-Items zu Product-Schema mappen + const mappedProducts = items.map(item => { + // Validiere, dass platformProductId vorhanden ist + if (!item.platformProductId) { + console.warn("Item ohne platformProductId übersprungen:", item); + return null; + } + + return { + product_platform_product_id: item.platformProductId, + product_title: item.title || "", + product_price: item.price ?? undefined, // undefined statt null für Appwrite + product_currency: item.currency ?? undefined, // auto-fill from market if undefined + product_url: item.url || "", + product_status: item.status ?? "unknown", + product_category: item.category ?? "unknown", + product_condition: item.condition ?? "unknown" + }; + }).filter(Boolean); // Entferne null-Einträge + + // 4. upsertProductsForAccount aufrufen + const result = await upsertProductsForAccount(accountId, mappedProducts); + + // 5. account_last_scan_at aktualisieren + await updateAccountLastScanAt(accountId, new Date().toISOString()); + + // 6. Return { created, updated } + return result; + } catch (e) { + console.error("Fehler beim Scannen der Produkte:", e); + throw e; + } +} + +/** + * Erstellt oder aktualisiert Produkte für einen Account (vorbereitet für Extension-Scans) + * @param {string} accountId - ID des Accounts + * @param {Array} products - Array von Produkt-Daten (kann partiell sein) + * @returns {Promise<{ created: number, updated: number }>} Anzahl erstellter/aktualisierter Produkte + */ +export async function upsertProductsForAccount(accountId, products) { + if (!accountId) { + throw new Error("accountId ist erforderlich"); + } + if (!Array.isArray(products) || products.length === 0) { + return { created: 0, updated: 0 }; + } + + try { + // Lade Account, um Market und andere Daten abzuleiten + const account = await getAccountById(accountId); + if (!account) { + throw new Error("Account nicht gefunden"); + } + + // Leite Market und Currency aus Account ab + const market = deriveMarketFromAccount(account); + if (market === "UNKNOWN") { + throw new Error( + "Market konnte nicht erkannt werden. Bitte Account refreshen." + ); + } + + const currency = deriveCurrencyFromMarket(market); + const platform = "ebay"; // Enum-Werte sind lowercase gemäß Fehlermeldung: [amazon], [ebay] + + // Lade bestehende Produkte für Duplikat-Prüfung + const existingProducts = await listProductsByAccount(accountId, { + limit: 1000, + offset: 0, + }); + + // Erstelle Map von platform_product_id -> bestehendes Produkt + const existingProductsMap = new Map(); + existingProducts.forEach((p) => { + if (p.product_platform_product_id) { + existingProductsMap.set(p.product_platform_product_id, p); + } + }); + + let created = 0; + let updated = 0; + + // Verarbeite jedes Produkt + for (const productData of products) { + // Validiere, dass platform_product_id vorhanden ist + if (!productData.product_platform_product_id) { + console.warn("Produkt ohne product_platform_product_id übersprungen:", productData); + continue; + } + + const platformProductId = productData.product_platform_product_id; + const existingProduct = existingProductsMap.get(platformProductId); + + // Ergänze fehlende erforderliche Felder aus Account + const fullProductData = { + product_account_id: accountId, + product_platform: platform, + product_platform_market: market, + product_currency: currency, + ...productData, // Produkt-Daten überschreiben Account-Daten wo vorhanden + }; + + try { + if (existingProduct) { + // Aktualisiere bestehendes Produkt + await databases.updateDocument( + databaseId, + productsCollectionId, + existingProduct.$id, + fullProductData + ); + updated++; + } else { + // Erstelle neues Produkt + await databases.createDocument( + databaseId, + productsCollectionId, + ID.unique(), + fullProductData + ); + created++; + } + } catch (e) { + console.error(`Fehler beim Upsert des Produkts "${platformProductId}":`, e); + // Weiter mit nächstem Produkt + continue; + } + } + + return { created, updated }; + } catch (e) { + console.error("Fehler beim Upsert der Produkte:", e); + throw e; + } +} diff --git a/Server/vite.config.js b/Server/vite.config.js new file mode 100644 index 0000000..0cca23e --- /dev/null +++ b/Server/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +})