diff --git a/README.md b/README.md index 51f10c3..f96e4cd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ A collaborative platform for developers to manage projects, tasks, discussions and team communication + ``` Developer-Collaboration-Platform ├─ client @@ -17,6 +18,10 @@ Developer-Collaboration-Platform │ │ ├─ App.css │ │ ├─ App.jsx │ │ ├─ assets +│ │ │ ├─ eyeOff.svg +│ │ │ ├─ eyeOn.svg +│ │ │ ├─ github.svg +│ │ │ ├─ google.svg │ │ │ ├─ hero.png │ │ │ ├─ react.svg │ │ │ └─ vite.svg @@ -28,26 +33,47 @@ Developer-Collaboration-Platform │ │ ├─ index.css │ │ ├─ main.jsx │ │ ├─ pages +│ │ │ ├─ dashboard.jsx +│ │ │ ├─ forgotpassword.jsx +│ │ │ ├─ login.jsx +│ │ │ └─ signup.jsx │ │ ├─ services +│ │ │ └─ googleAuthService.js │ │ ├─ styles │ │ └─ utils │ └─ vite.config.js -├─ docs ├─ LICENSE +├─ package-lock.json +├─ package.json ├─ README.md └─ server ├─ package-lock.json ├─ package.json ├─ prisma + │ ├─ migrations + │ │ ├─ 20260708161847_init + │ │ │ └─ migration.sql + │ │ ├─ 20260713130332_add_google_oauth + │ │ │ └─ migration.sql + │ │ ├─ 20260713132054_google_oauth + │ │ │ └─ migration.sql + │ │ ├─ 20260716153106_add_profile_completed + │ │ │ └─ migration.sql + │ │ └─ migration_lock.toml │ └─ schema.prisma - ├─ prisma.config.ts + ├─ prisma.config.js └─ src ├─ app.js ├─ config + │ └─ prisma.js ├─ controllers + │ └─ auth.controller.js ├─ middleware - ├─ models ├─ server.js ├─ services - └─ utils -``` + │ └─ auth.service.js + ├─ utils + └─ validators + └─ auth.validator.js + +``` \ No newline at end of file diff --git a/client/.gitignore b/client/.gitignore index a547bf3..d823d0d 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + + +.env \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 93eacf3..381182d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,11 +8,17 @@ "name": "client", "version": "0.0.0", "dependencies": { + "@marsidev/react-turnstile": "^1.5.3", + "@react-oauth/google": "^0.13.5", + "@tailwindcss/typography": "^0.5.20", "@tailwindcss/vite": "^4.3.2", + "axios": "^1.18.1", "lucide-react": "^1.23.0", "react": "^19.2.7", "react-dom": "^19.2.7", + "react-markdown": "^10.1.0", "react-router-dom": "^7.18.1", + "recharts": "^3.10.1", "tailwindcss": "^4.3.2" }, "devDependencies": { @@ -537,6 +543,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@marsidev/react-turnstile": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-1.5.3.tgz", + "integrity": "sha512-8Dij2jiNGNczq1U4EKpO4do2XepcTPxSMc2ZzvHndO+gcp68tvMULm27z2P99rGkdB89hc3452NZeu2Rti4g6A==", + "license": "MIT", + "peerDependencies": { + "react": "^17.0.2 || ^18.0.0 || ^19.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -564,6 +580,42 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@react-oauth/google": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.13.5.tgz", + "integrity": "sha512-xQWri2s/3nNekZJ4uuov2aAfQYu83bN3864KcFqw2pK1nNbFurQIjPFDXhWaKH3IjYJ2r/9yyIIpsn5lMqrheQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", @@ -812,6 +864,18 @@ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", @@ -1055,6 +1119,18 @@ "node": ">= 20" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz", + "integrity": "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" + } + }, "node_modules/@tailwindcss/vite": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", @@ -1079,6 +1155,78 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -1090,9 +1238,26 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1100,11 +1265,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1120,6 +1299,24 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", @@ -1169,6 +1366,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -1186,6 +1395,34 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1256,6 +1493,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001799", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", @@ -1277,6 +1527,87 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1312,18 +1643,149 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1337,6 +1799,25 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1344,15 +1825,60 @@ "dev": true, "license": "MIT" }, - "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==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "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.381", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz", @@ -1373,6 +1899,61 @@ "node": ">=10.13.0" } }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "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==", + "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-toolkit": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", + "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1571,6 +2152,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1581,6 +2172,18 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1670,6 +2273,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1684,6 +2323,15 @@ "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1694,6 +2342,43 @@ "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==", + "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==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1720,12 +2405,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "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==", + "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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -1743,6 +2519,29 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -1753,6 +2552,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "11.1.15", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.15.tgz", + "integrity": "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1763,6 +2572,55 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1786,6 +2644,28 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2145,6 +3025,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2173,50 +3063,674 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.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==", + "license": "MIT", "engines": { - "node": "18 || 20 || >=22" + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "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/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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" @@ -2281,6 +3795,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2347,6 +3886,19 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2357,6 +3909,25 @@ "node": ">= 0.8.0" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2388,6 +3959,63 @@ "react": "^19.2.7" } }, + "node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT", + "peer": true + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.18.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", @@ -2426,6 +4054,90 @@ "react-dom": ">=18" } }, + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, "node_modules/rolldown": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", @@ -2513,6 +4225,48 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/tailwindcss": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", @@ -2532,6 +4286,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -2548,6 +4308,26 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2568,6 +4348,93 @@ "node": ">= 0.8.0" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2609,6 +4476,71 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", @@ -2754,6 +4686,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/client/package.json b/client/package.json index 7569686..227e4b9 100644 --- a/client/package.json +++ b/client/package.json @@ -10,11 +10,17 @@ "preview": "vite preview" }, "dependencies": { + "@marsidev/react-turnstile": "^1.5.3", + "@react-oauth/google": "^0.13.5", + "@tailwindcss/typography": "^0.5.20", "@tailwindcss/vite": "^4.3.2", + "axios": "^1.18.1", "lucide-react": "^1.23.0", "react": "^19.2.7", "react-dom": "^19.2.7", + "react-markdown": "^10.1.0", "react-router-dom": "^7.18.1", + "recharts": "^3.10.1", "tailwindcss": "^4.3.2" }, "devDependencies": { diff --git a/client/src/App.jsx b/client/src/App.jsx index f081195..5f7780e 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,19 +1,265 @@ -import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; -// import Login from "./pages/Login"; +import { + BrowserRouter, + Navigate, + Route, + Routes, + useLocation, +} from "react-router-dom"; +import Login from "./pages/login"; import Signup from "./pages/signup"; +import Forgotpassword from "./pages/forgotpassword"; +import WorkspaceDashboard from "./pages/WorkspaceDashboard"; +import CompleteProfile from "./pages/completeProfile"; +import ProtectedRoute from "./components/common/ProtectedRoute"; +import ProtectedAdminRoute from "./components/ProtectedAdminRoute"; +import AdminDashboard from "./pages/AdminDashboard"; +import OrganizationDashboard from "./pages/OrganizationDashboard"; +import OrganizationMembers from "./pages/OrganizationMembers"; +import Workspaces from "./pages/Workspaces"; +import CreateWorkspaceWizard from "./pages/CreateWorkspaceWizard"; +import Wiki from "./pages/Wiki"; +import Commits from "./pages/Commits"; + +import Invitations from "./pages/Invitations"; +import WorkspaceSettings from "./pages/WorkspaceSettings"; +import WorkspaceMembers from "./pages/WorkspaceMembers"; +import PageLoader from "./components/common/PageLoader"; +import { + NavigationLoadingProvider, + useNavigationLoading, +} from "./context/NavigationLoadingContext"; import "./index.css"; +import GithubCallback from "./pages/githubCallback"; +import PullRequests from "./pages/PullRequests"; +import Projects from "./pages/Projects"; +import ProjectDetails from "./pages/ProjectDetails"; +import Sprints from "./pages/Sprints"; +import TasksKanban from "./pages/TasksKanban"; +import UserProfile from "./pages/UserProfile"; + +function AppRoutes() { + const location = useLocation(); + const { goTo } = useNavigationLoading(); + + const handleLinkClick = (event) => { + const link = event.target.closest("a"); + + if ( + !link || + event.defaultPrevented || + event.button !== 0 || + event.metaKey || + event.ctrlKey || + event.shiftKey || + event.altKey || + link.target === "_blank" || + link.hasAttribute("download") + ) { + return; + } + + const url = new URL(link.href, window.location.origin); + + // Do not intercept external links, such as normal GitHub links. + if (url.origin !== window.location.origin) { + return; + } + + const destination = `${url.pathname}${url.search}${url.hash}`; + const currentPage = `${location.pathname}${location.search}${location.hash}`; + + // Do not show a loader for a link to the page already open. + if (destination === currentPage) { + return; + } + + event.preventDefault(); + + goTo(destination); + }; -function App() { return ( - +
+ + - {/* } /> */} + } /> } /> - {/* Redirect root to login for now */} - } /> + } /> + } /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + {/* Organization Routes Protected by Auth */} + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + }> + } /> + + + } /> +
+ ); +} + +function App() { + return ( + + + + ); } -export default App; +export default App; \ No newline at end of file diff --git a/client/src/assets/eyeOff.svg b/client/src/assets/eyeOff.svg new file mode 100644 index 0000000..5a0b98a --- /dev/null +++ b/client/src/assets/eyeOff.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/eyeOn.svg b/client/src/assets/eyeOn.svg new file mode 100644 index 0000000..4d27ebc --- /dev/null +++ b/client/src/assets/eyeOn.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/github.svg b/client/src/assets/github.svg new file mode 100644 index 0000000..84e4d2f --- /dev/null +++ b/client/src/assets/github.svg @@ -0,0 +1,19 @@ + + + + + github [#142] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/google.svg b/client/src/assets/google.svg new file mode 100644 index 0000000..3ffa2aa --- /dev/null +++ b/client/src/assets/google.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/client/src/components/InvitationCard.jsx b/client/src/components/InvitationCard.jsx new file mode 100644 index 0000000..ac3e52b --- /dev/null +++ b/client/src/components/InvitationCard.jsx @@ -0,0 +1,71 @@ +import React from "react"; +import { Building2, Calendar, User, Check, X, Loader2 } from "lucide-react"; + +const InvitationCard = ({ invitation, onAccept, onReject, isProcessing }) => { + return ( +
+ {/* Left Side: Invitation Details */} +
+
+
+ +
+

+ {invitation.organization?.name} +

+
+ +
+
+ + + Invited by{" "} + + {invitation.invitedBy?.username} + + +
+
+ + ROLE: {invitation.role} + +
+
+ + {new Date(invitation.createdAt).toLocaleDateString()} +
+
+
+ + {/* Right Side: Actions */} +
+ + +
+
+ ); +}; + +export default InvitationCard; diff --git a/client/src/components/ProtectedAdminRoute.jsx b/client/src/components/ProtectedAdminRoute.jsx new file mode 100644 index 0000000..22bd8ea --- /dev/null +++ b/client/src/components/ProtectedAdminRoute.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; + +const ProtectedAdminRoute = () => { + const userStr = localStorage.getItem("user"); + + if (!userStr) { + return ; + } + + const user = JSON.parse(userStr); + + if (user.platformRole !== 'PLATFORM_ADMIN') { + return ; + } + + return ; +}; + +export default ProtectedAdminRoute; diff --git a/client/src/components/admin/AdminAuditLogs.jsx b/client/src/components/admin/AdminAuditLogs.jsx new file mode 100644 index 0000000..f2426e3 --- /dev/null +++ b/client/src/components/admin/AdminAuditLogs.jsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { ClipboardList, Search, ChevronDown, Wrench, Calendar as CalendarIcon, Filter } from 'lucide-react'; + +const AdminAuditLogs = () => { + return ( +
+
+

+ Audit Logs +

+

Track platform-wide actions, configuration changes, and security events.

+
+ +
+ {/* Toolbar */} +
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+
+ + {/* Table Content (Empty State) */} +
+ + + + + + + + + + + {[...Array(6)].map((_, i) => ( + + + + + + + ))} + +
ActorAction & TargetIP AddressTimestamp
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+

Pending Implementation

+

+ The Audit Logs viewer is designed and ready, but the platform does not yet have an active audit logging backend service tracking these events. +

+ +
+
+
+
+
+ ); +}; + +export default AdminAuditLogs; diff --git a/client/src/components/admin/AdminOrganizations.jsx b/client/src/components/admin/AdminOrganizations.jsx new file mode 100644 index 0000000..b716d55 --- /dev/null +++ b/client/src/components/admin/AdminOrganizations.jsx @@ -0,0 +1,385 @@ +import React, { useState, useEffect, useRef } from 'react'; +import axios from 'axios'; +import { + Building2, Search, ChevronDown, ChevronLeft, ChevronRight, + MoreVertical, Eye, AlertTriangle, Trash2, ArrowLeft, + Users, Layers, Calendar +} from 'lucide-react'; + +const AdminOrganizations = () => { + const [orgs, setOrgs] = useState([]); + const [orgsLoading, setOrgsLoading] = useState(false); + const [orgsError, setOrgsError] = useState(null); + + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [search, setSearch] = useState(""); + const [statusFilter, setStatusFilter] = useState("ALL"); + + const [selectedOrg, setSelectedOrg] = useState(null); + const [orgDetails, setOrgDetails] = useState(null); + const [detailsLoading, setDetailsLoading] = useState(false); + const [openDropdownId, setOpenDropdownId] = useState(null); + const dropdownRef = useRef(null); + + // Close dropdown on click outside + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setOpenDropdownId(null); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const fetchOrgs = async () => { + setOrgsLoading(true); + setOrgsError(null); + try { + const response = await axios.get( + `http://localhost:5000/api/admin/organizations?page=${page}&limit=10&search=${search}`, + { withCredentials: true } + ); + setOrgs(response.data.data.organizations); + setTotalPages(response.data.data.pagination.totalPages); + } catch (err) { + setOrgsError(err.response?.data?.message || "Failed to fetch organizations"); + } finally { + setOrgsLoading(false); + } + }; + + const fetchOrgDetails = async (orgId) => { + setDetailsLoading(true); + try { + const response = await axios.get(`http://localhost:5000/api/admin/organizations/${orgId}`, { withCredentials: true }); + setOrgDetails(response.data.data); + } catch (err) { + console.error(err); + } finally { + setDetailsLoading(false); + } + }; + + const handleSelectOrg = (org) => { + setSelectedOrg(org); + fetchOrgDetails(org.id); + }; + + const handleDeleteOrg = async (orgId) => { + if (!window.confirm("Are you sure you want to permanently delete this organization? All its workspaces and data will be destroyed.")) return; + try { + await axios.delete(`http://localhost:5000/api/admin/organizations/${orgId}`, { withCredentials: true }); + if (selectedOrg?.id === orgId) setSelectedOrg(null); + fetchOrgs(); + } catch (err) { + alert(err.response?.data?.message || "Failed to delete organization"); + } + }; + + useEffect(() => { + const delay = setTimeout(fetchOrgs, 500); + return () => clearTimeout(delay); + }, [page, search]); + + // Client-side filter for now as backend doesn't support status filter out of the box yet + const filteredOrgs = statusFilter === "ALL" + ? orgs + : orgs.filter(o => o.status === statusFilter); + + const handleActionClick = (e, orgId) => { + e.stopPropagation(); + setOpenDropdownId(openDropdownId === orgId ? null : orgId); + }; + + if (selectedOrg) { + return ( +
+ + +
+ {/* Org Profile Card */} +
+
+
+ +
+ +
+ +

{selectedOrg.name}

+
+ {selectedOrg.description || "No description provided."} +
+ +
+
Active Status +
+ +
+
+
Total Members
+
+ {selectedOrg._count?.members || 0} +
+
+
+
Created Date
+
+ {new Date(selectedOrg.createdAt).toLocaleDateString()} +
+
+
+
+ + {/* Actions Card */} +
+

Organization Actions

+
+ +
+
+
+ + {/* Details Column */} +
+
+
+

+ Workspaces +

+ {orgDetails && {orgDetails._count?.workspaces || 0} Workspaces} +
+
+ {detailsLoading ? ( +
Loading workspaces...
+ ) : orgDetails?.workspaces?.length > 0 ? ( +
+ {orgDetails.workspaces.map(workspace => ( +
+
+
+
{workspace.name}
+
+ + {workspace.status} + +
+ ))} +
+ ) : ( +
+ +

This organization has no workspaces.

+
+ )} +
+
+ +
+
+

+ Members +

+ {orgDetails && {orgDetails._count?.members || 0} Members} +
+
+ {detailsLoading ? ( +
Loading members...
+ ) : orgDetails?.members?.length > 0 ? ( +
+ {orgDetails.members.map(member => ( +
+
+
+ {member.user.avatar ? avatar : (member.user.username?.charAt(0) || "U").toUpperCase()} +
+
+
+ {member.user.username} + {member.user.platformRole === "PLATFORM_ADMIN" && ( + Admin + )} +
+
{member.user.email}
+
+
+ + {member.role} + +
+ ))} +
+ ) : ( +
+ +

No members found.

+
+ )} +
+
+
+
+
+ ); + } + + return ( +
+
+

+ Organizations +

+

Manage platform tenants, billing statuses, and organization settings.

+
+ +
+ {/* Toolbar */} +
+
+ + { setSearch(e.target.value); setPage(1); }} + className="w-full bg-[#0f111a] border border-slate-800 text-sm rounded-lg pl-9 pr-4 py-2 text-white focus:outline-none focus:border-blue-500 transition-colors placeholder:text-slate-600" + /> +
+
+
+ + +
+
+
+ + {/* Table Content */} +
+ {orgsLoading ? ( +
Loading organizations...
+ ) : orgsError ? ( +
{orgsError}
+ ) : filteredOrgs.length === 0 ? ( +
+ +

No organizations found matching your search.

+
+ ) : ( + + + + + + + + + + + {filteredOrgs.map((org) => ( + handleSelectOrg(org)} className="hover:bg-slate-800/30 transition-colors cursor-pointer group"> + + + + + + ))} + +
OrganizationMembersCreatedActions
+
+
+ {org.name.charAt(0).toUpperCase()} +
+
+
+ {org.name} +
+
+
+
+
+ + {org._count?.members || 0} +
+
+ {new Date(org.createdAt).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })} + +
+ + + {openDropdownId === org.id && ( +
+ +
+ +
+ )} +
+
+ )} +
+ + {/* Pagination */} + {!orgsLoading && !orgsError && filteredOrgs.length > 0 && ( +
+
+ Showing {filteredOrgs.length} organizations on page {page} +
+
+ + {page} + +
+
+ )} +
+
+ ); +}; + +export default AdminOrganizations; diff --git a/client/src/components/admin/AdminOverview.jsx b/client/src/components/admin/AdminOverview.jsx new file mode 100644 index 0000000..f490706 --- /dev/null +++ b/client/src/components/admin/AdminOverview.jsx @@ -0,0 +1,306 @@ +import React from 'react'; +import { + Users, Building2, Layers, Mail, + UserPlus, ShieldAlert, Globe, Database, GitBranch, Settings, ClipboardList +} from 'lucide-react'; + +const AdminOverview = ({ user }) => { + return ( +
+
+

Dashboard

+

Welcome back, {user?.username || "SuperAdmin"}

+
+ + {/* Summary Cards */} +
+
+
+
+ +
+
+
8
+
+
+
+
Total Users
+
↑ 2 this week
+
+
+ +
+
+
+ +
+
+
4
+
+
+
+
Organizations
+
↑ 1 this week
+
+
+ +
+
+
+ +
+
+
17
+
+
+
+
Workspaces
+
↑ 3 this week
+
+
+ +
+
+
+ +
+
+
3
+
+
+
+
Pending Invitations
+
View all →
+
+
+
+ +
+ {/* Recent Activity */} +
+
+

Recent Activity

+ +
+
+
+ +
+
+ +
+
+
+
Test_101 registered
+
New user joined the platform
+
+
+ User + 2m ago +
+
+
+ +
+
+ +
+
+
+
Test4_Org organization created
+
New organization was created
+
+
+ Organization + 8m ago +
+
+
+ +
+
+ +
+
+
+
Ankur changed workspace role
+
Changed role of Test_106 in Dev Team
+
+
+ Workspace + 15m ago +
+
+
+ +
+
+ +
+
+
+
Invitation sent to user
+
Invitation sent to test.user@gmail.com
+
+
+ Invitation + 33m ago +
+
+
+ +
+
+ +
+
+
+
Platform settings updated
+
Public signups setting changed
+
+
+ Settings + 1h ago +
+
+
+ +
+
+
+ + {/* System Status */} +
+
+

System Status

+ +
+
+ +
+
+ +
+
+
+
+ API +
+ Operational +
+
All systems operational
+
+
+ +
+
+ +
+
+
+
+ Database +
+ Operational +
+
All systems operational
+
+
+ +
+
+ +
+
+
+
+ GitHub Integration +
+ Operational +
+
All systems operational
+
+
+ +
+
+ +
+
+
+
+ Email Service +
+ Operational +
+
All systems operational
+
+
+ +
+
+
+ + {/* Footer Info Strip */} +
+
+
Platform Overview
+
All times shown in your local timezone.
+
+
+
+ +
+
8
+
Total Users
+
+
+
+ +
+
1
+
Platform Admin
+
+
+
+ +
+
4
+
Organizations
+
+
+
+ +
+
17
+
Workspaces
+
+
+
+ +
+
31
+
Total Members
+
+
+
+ +
+
3
+
Pending Invitations
+
+
+
+ +
+
128
+
Audit Logs
+
+
+
+
+
+ ); +}; + +export default AdminOverview; diff --git a/client/src/components/admin/AdminSecurity.jsx b/client/src/components/admin/AdminSecurity.jsx new file mode 100644 index 0000000..c03077d --- /dev/null +++ b/client/src/components/admin/AdminSecurity.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { ShieldCheck, MonitorSmartphone, KeyRound, Lock, Wrench } from 'lucide-react'; + +const AdminSecurity = () => { + return ( +
+
+

+ Security Overview +

+

Monitor platform security and manage authentication policies.

+
+ +
+ + {/* Pending Banner */} +
+
+ +
+
+

UI Structure Ready - Pending Backend Integration

+

+ This security dashboard is structured according to the redesign specifications. Features like active session management, + MFA enforcement, and detailed login history require corresponding backend endpoints to be fully operational. +

+
+
+ +
+ + {/* Active Sessions */} +
+
+
+ +

Active Sessions

+
+ Mock Data +
+
+
+
+
Admin (SuperAdmin)
+
Current
+
+
Windows • Chrome 116
+
192.168.1.1 • Signed in 2h ago
+
+ +
+
+
Test_101
+
+
Mac OS • Safari
+
10.0.0.45 • Signed in 1d ago
+
+
+
+ + {/* Security Policies */} +
+
+
+ +

Security Policies

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

+ Accounts will be temporarily locked for 15 minutes after exceeding this threshold. +

+
+ +
+
+
+ +
+
+ ); +}; + +export default AdminSecurity; diff --git a/client/src/components/admin/AdminSettings.jsx b/client/src/components/admin/AdminSettings.jsx new file mode 100644 index 0000000..f27709f --- /dev/null +++ b/client/src/components/admin/AdminSettings.jsx @@ -0,0 +1,184 @@ +import React, { useState } from 'react'; +import { Settings, Globe, Shield, Activity, AlertTriangle, Key } from 'lucide-react'; + +const AdminSettings = () => { + // Static state for now + const [allowSignups, setAllowSignups] = useState(true); + const [requireEmailVer, setRequireEmailVer] = useState(false); + const [allowGoogleLogin, setAllowGoogleLogin] = useState(true); + const [allowGithubLogin, setAllowGithubLogin] = useState(true); + const [maintenanceMode, setMaintenanceMode] = useState(false); + + const [platformName, setPlatformName] = useState("Dev Collaboration Platform"); + + const handleSave = () => { + alert("Settings saved successfully!"); + }; + + return ( +
+
+
+

+ Platform Settings +

+

Configure global application behavior and maintenance.

+
+ +
+ +
+ {/* General Settings */} +
+
+ +

General Information

+
+
+
+ + setPlatformName(e.target.value)} + className="w-full max-w-md bg-[#0f111a] border border-slate-700 text-sm rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-purple-500 transition-colors" + /> +
+
+ + +
+
+ + +
+
+
+ + {/* User & Registration */} +
+
+ +

Authentication & Registration

+
+
+
+
+

Allow Public Signups

+

When disabled, new users can only join via invitation.

+
+ +
+ +
+
+

Require Email Verification

+

Force new accounts to verify their email before logging in.

+
+ +
+ +
+
+

Allow Google Login

+

Enable Google OAuth2 for authentication.

+
+ +
+ +
+
+

Allow GitHub Login

+

Enable GitHub OAuth2 for authentication.

+
+ +
+
+
+ + {/* Maintenance */} +
+
+ +

System Maintenance

+
+
+
+
+

+ Maintenance Mode +

+

+ When enabled, all non-admin users will be logged out and presented with a maintenance screen. + Use this only during critical upgrades or platform migrations. +

+ {maintenanceMode && ( +
+ + +
+ )} +
+ +
+
+
+ + {/* Danger Zone */} +
+
+ +

Danger Zone

+
+
+
+
+

Purge Deleted Data

+

Permanently remove all soft-deleted organizations, workspaces, and users.

+
+ +
+
+
+
+
+ ); +}; + +export default AdminSettings; diff --git a/client/src/components/admin/AdminSidebar.jsx b/client/src/components/admin/AdminSidebar.jsx new file mode 100644 index 0000000..10031f4 --- /dev/null +++ b/client/src/components/admin/AdminSidebar.jsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { + LayoutDashboard, Users, Building2, Layers, + ClipboardList, Activity, Settings, ShieldCheck, + Shield, ChevronRight +} from 'lucide-react'; + +const AdminSidebar = ({ activeTab, setActiveTab, user }) => { + const menuGroups = [ + { + title: "MAIN", + items: [ + { id: "dashboard", label: "Dashboard", icon: LayoutDashboard } + ] + }, + { + title: "MANAGEMENT", + items: [ + { id: "users", label: "Users", icon: Users }, + { id: "orgs", label: "Organizations", icon: Building2 }, + { id: "workspaces", label: "Workspaces", icon: Layers } + ] + }, + { + title: "MONITORING", + items: [ + { id: "audit", label: "Audit Logs", icon: ClipboardList }, + { id: "activity", label: "System Activity", icon: Activity } + ] + }, + { + title: "SYSTEM", + items: [ + { id: "settings", label: "Platform Settings", icon: Settings }, + { id: "security", label: "Security", icon: ShieldCheck } + ] + } + ]; + + return ( +
+ {/* Header */} +
+
window.location.href = "/organization"}> +
+ +
+

Platform Admin

+
+

+ Manage users, organizations, workspaces and system settings. +

+
+ + {/* Navigation */} +
+ {menuGroups.map((group, idx) => ( +
+

+ {group.title} +

+
+ {group.items.map(item => { + const Icon = item.icon; + const isActive = activeTab === item.id; + return ( + + ); + })} +
+
+ ))} +
+ + {/* Footer User Profile */} +
+
window.location.href = "/profile"} + className="flex items-center gap-3 p-2 rounded-xl hover:bg-[#1c1f2e] transition-colors cursor-pointer" + > +
+ {user?.avatar ? ( + avatar + ) : ( + (user?.username?.charAt(0) || "A").toUpperCase() + )} +
+
+
{user?.username || "Admin"}
+
Platform Admin
+
+ +
+
+
+ ); +}; + +export default AdminSidebar; diff --git a/client/src/components/admin/AdminUsers.jsx b/client/src/components/admin/AdminUsers.jsx new file mode 100644 index 0000000..b6ca548 --- /dev/null +++ b/client/src/components/admin/AdminUsers.jsx @@ -0,0 +1,417 @@ +import React, { useState, useEffect, useRef } from 'react'; +import axios from 'axios'; +import { + Users, Search, ChevronDown, ChevronLeft, ChevronRight, + MoreVertical, User, ShieldAlert, ArrowLeft, Mail, Calendar, Key, AlertTriangle, Trash2, Building2, Layers +} from 'lucide-react'; + +const AdminUsers = () => { + const [users, setUsers] = useState([]); + const [usersLoading, setUsersLoading] = useState(false); + const [usersError, setUsersError] = useState(null); + + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [search, setSearch] = useState(""); + const [roleFilter, setRoleFilter] = useState("ALL"); + + const [selectedUser, setSelectedUser] = useState(null); + const [userDetails, setUserDetails] = useState(null); + const [detailsLoading, setDetailsLoading] = useState(false); + const [openDropdownId, setOpenDropdownId] = useState(null); + const dropdownRef = useRef(null); + + // Close dropdown on click outside + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setOpenDropdownId(null); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const fetchUsers = async () => { + setUsersLoading(true); + setUsersError(null); + try { + const response = await axios.get( + `http://localhost:5000/api/admin/users?page=${page}&limit=10&search=${search}`, + { withCredentials: true } + ); + setUsers(response.data.data.users); + setTotalPages(response.data.data.pagination.totalPages); + } catch (err) { + setUsersError(err.response?.data?.message || "Failed to fetch users"); + } finally { + setUsersLoading(false); + } + }; + + const fetchUserDetails = async (userId) => { + setDetailsLoading(true); + try { + const response = await axios.get(`http://localhost:5000/api/admin/users/${userId}`, { withCredentials: true }); + setUserDetails(response.data.data); + } catch (err) { + console.error(err); + } finally { + setDetailsLoading(false); + } + }; + + const handleSelectUser = (user) => { + setSelectedUser(user); + fetchUserDetails(user.id); + }; + + const handleChangeRole = async (userId, currentRole) => { + const newRole = currentRole === "PLATFORM_ADMIN" ? "USER" : "PLATFORM_ADMIN"; + if (!window.confirm(`Are you sure you want to change this user's role to ${newRole}?`)) return; + try { + await axios.patch(`http://localhost:5000/api/admin/users/${userId}/role`, { role: newRole }, { withCredentials: true }); + fetchUsers(); + if (selectedUser?.id === userId) fetchUserDetails(userId); + } catch (err) { + alert(err.response?.data?.message || "Failed to change role"); + } + }; + + const handleDeleteUser = async (userId) => { + if (!window.confirm("Are you sure you want to permanently delete this user? This cannot be undone.")) return; + try { + await axios.delete(`http://localhost:5000/api/admin/users/${userId}`, { withCredentials: true }); + if (selectedUser?.id === userId) setSelectedUser(null); + fetchUsers(); + } catch (err) { + alert(err.response?.data?.message || "Failed to delete user"); + } + }; + + useEffect(() => { + const delay = setTimeout(fetchUsers, 500); + return () => clearTimeout(delay); + }, [page, search]); + + // Filter users client-side if a specific role is selected + // (Since the backend search only searches username/email right now) + const filteredUsers = roleFilter === "ALL" + ? users + : users.filter(u => u.platformRole === roleFilter); + + const handleActionClick = (e, userId) => { + e.stopPropagation(); + setOpenDropdownId(openDropdownId === userId ? null : userId); + }; + + if (selectedUser) { + return ( +
+ + +
+ {/* User Profile Card */} +
+
+
+
+ {selectedUser.avatar ? ( + avatar + ) : ( + (selectedUser.username?.charAt(0) || "U").toUpperCase() + )} +
+

{selectedUser.username}

+
{selectedUser.email}
+ +
+ + {selectedUser.platformRole} + +
+ +
+
Active Account +
+ +
+
+
Joined Date
+
{new Date(selectedUser.createdAt).toLocaleDateString()}
+
+
+
ID
+
USR-{selectedUser.id.toString().padStart(4, '0')}
+
+
+
+ + {/* Actions Card */} +
+

Admin Actions

+
+ + +
+
+
+ + {/* Details Column */} +
+
+
+

+ Organizations +

+ {userDetails && {userDetails.organizationMemberships?.length || 0} Orgs} +
+
+ {detailsLoading ? ( +
Loading organizations...
+ ) : userDetails?.organizationMemberships?.length > 0 ? ( +
+ {userDetails.organizationMemberships.map(membership => ( +
+
+
+
{membership.organization.name}
+
+ {membership.role} +
+ ))} +
+ ) : ( +
+ +

User does not belong to any organizations.

+
+ )} +
+
+ +
+
+

+ Workspaces +

+ {userDetails && {userDetails.workspaceMemberships?.length || 0} Workspaces} +
+
+ {detailsLoading ? ( +
Loading workspaces...
+ ) : userDetails?.workspaceMemberships?.length > 0 ? ( +
+ {userDetails.workspaceMemberships.map(membership => ( +
+
+
+
+
{membership.workspace.name}
+
in {membership.workspace.organization.name}
+
+
+ {membership.role} +
+ ))} +
+ ) : ( +
+ +

User does not belong to any workspaces.

+
+ )} +
+
+
+
+
+ ); + } + + return ( +
+
+

+ Users +

+

Manage platform accounts, roles, and access.

+
+ +
+ {/* Toolbar */} +
+
+ + { setSearch(e.target.value); setPage(1); }} + className="w-full bg-[#0f111a] border border-slate-800 text-sm rounded-lg pl-9 pr-4 py-2 text-white focus:outline-none focus:border-purple-500 transition-colors placeholder:text-slate-600" + /> +
+
+
+ + +
+
+
+ + {/* Table Content */} +
+ {usersLoading ? ( +
Loading users...
+ ) : usersError ? ( +
{usersError}
+ ) : filteredUsers.length === 0 ? ( +
+ +

No users found matching your search.

+
+ ) : ( + + + + + + + + + + + + {filteredUsers.map((user) => ( + handleSelectUser(user)} className="hover:bg-slate-800/30 transition-colors cursor-pointer group"> + + + + + + + ))} + +
UserPlatform RoleStatusJoinedActions
+
+
+ {user.avatar ? ( + avatar + ) : ( + (user.username?.charAt(0) || "U").toUpperCase() + )} +
+
+
+ {user.username} +
+
{user.email}
+
+
+
+ + {user.platformRole} + + +
+
Active +
+
+ {new Date(user.createdAt).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })} + +
+ + + {openDropdownId === user.id && ( +
+ + +
+ +
+ )} +
+
+ )} +
+ + {/* Pagination */} + {!usersLoading && !usersError && filteredUsers.length > 0 && ( +
+
+ Showing {filteredUsers.length} users on page {page} +
+
+ + {page} + +
+
+ )} +
+
+ ); +}; + +export default AdminUsers; diff --git a/client/src/components/admin/AdminWorkspaces.jsx b/client/src/components/admin/AdminWorkspaces.jsx new file mode 100644 index 0000000..f3c919e --- /dev/null +++ b/client/src/components/admin/AdminWorkspaces.jsx @@ -0,0 +1,127 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { Layers, Search, ChevronDown, Wrench, Building2, User } from 'lucide-react'; + +const AdminWorkspaces = () => { + const [workspaces, setWorkspaces] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + + const fetchWorkspaces = async () => { + setLoading(true); + setError(null); + try { + const response = await axios.get( + `http://localhost:5000/api/admin/workspaces?page=${page}&limit=10&search=${search}`, + { withCredentials: true } + ); + setWorkspaces(response.data.data.workspaces); + setTotalPages(response.data.data.pagination.totalPages); + } catch (err) { + setError(err.response?.data?.message || "Failed to fetch workspaces"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + const delay = setTimeout(fetchWorkspaces, 500); + return () => clearTimeout(delay); + }, [page, search]); + + return ( +
+
+

+ Workspaces +

+

Global overview and management of all platform workspaces.

+
+ +
+ {/* Toolbar */} +
+
+ + { setSearch(e.target.value); setPage(1); }} + className="w-full bg-[#0f111a] border border-slate-800 text-sm rounded-lg pl-9 pr-4 py-2 text-white focus:outline-none focus:border-emerald-500 transition-colors placeholder:text-slate-600" + /> +
+
+ + {/* Table Content */} +
+ {loading ? ( +
Loading workspaces...
+ ) : error ? ( +
{error}
+ ) : workspaces.length === 0 ? ( +
+ +

No workspaces found matching your search.

+
+ ) : ( + + + + + + + + + + + + {workspaces.map((workspace) => ( + + + + + + + + ))} + +
WorkspaceOrganizationMembersStatusCreated
+
+
+ +
+
+
+ {workspace.name} +
+
+ {workspace.createdBy.username} +
+
+
+
+
+ + {workspace.organization.name} +
+
+ {workspace._count?.members || 0} + + + {workspace.status} + + + {new Date(workspace.createdAt).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })} +
+ )} +
+
+
+ ); +}; + +export default AdminWorkspaces; diff --git a/client/src/components/common/Card.jsx b/client/src/components/common/Card.jsx new file mode 100644 index 0000000..0d544ee --- /dev/null +++ b/client/src/components/common/Card.jsx @@ -0,0 +1,13 @@ +import React from "react"; + +const Card = ({ children, className = "" }) => { + return ( +
+ {children} +
+ ); +}; + +export default Card; diff --git a/client/src/components/common/PageLoader.jsx b/client/src/components/common/PageLoader.jsx new file mode 100644 index 0000000..d6150f1 --- /dev/null +++ b/client/src/components/common/PageLoader.jsx @@ -0,0 +1,23 @@ +// client/src/components/common/PageLoader.jsx + +import { LoaderCircle } from "lucide-react"; +import { useNavigationLoading } from "../../context/NavigationLoadingContext.jsx"; + +function PageLoader() { + const { isNavigating } = useNavigationLoading(); + + if (!isNavigating) { + return null; + } + + return ( +
+
+ + Loading... +
+
+ ); +} + +export default PageLoader; \ No newline at end of file diff --git a/client/src/components/common/ProtectedRoute.jsx b/client/src/components/common/ProtectedRoute.jsx new file mode 100644 index 0000000..7e69458 --- /dev/null +++ b/client/src/components/common/ProtectedRoute.jsx @@ -0,0 +1,13 @@ +import {Navigate} from "react-router-dom"; + +function ProtectedRoute({children}){ + const user = localStorage.getItem("user"); + + if(!user){ + return ; + } + + return children; +} + +export default ProtectedRoute; \ No newline at end of file diff --git a/client/src/components/layout/KPIs/StatsCard.jsx b/client/src/components/layout/KPIs/StatsCard.jsx new file mode 100644 index 0000000..6fe5aae --- /dev/null +++ b/client/src/components/layout/KPIs/StatsCard.jsx @@ -0,0 +1,19 @@ +import React from "react"; + +const StatsCard = ({ title, value, subtitle, icon: Icon, borderColor, bgColor, iconColor, hoverBorderColor, bgIcon}) => { + return ( +
+
+ {title} +
+ +
+
+

{value}

+

{subtitle}

+
+ ); +}; + +export default StatsCard; \ No newline at end of file diff --git a/client/src/components/layout/KPIs/StatsData.js b/client/src/components/layout/KPIs/StatsData.js new file mode 100644 index 0000000..cd9c9ba --- /dev/null +++ b/client/src/components/layout/KPIs/StatsData.js @@ -0,0 +1,73 @@ +import React from "react"; +import { Folder, GitPullRequestArrow, Code, CircleAlert, CloudUpload, CloudUploadIcon, CheckCircle, CheckCircle2Icon} from "lucide-react"; + +const StatsData = [ + { + title: "Active Projects", + value: 6, + subtitle: "2 from last week", + icon: Folder, + borderColor: "border-violet-500/30", + bgColor: "bg-gradient-to-br from-violet-950/60 to-slate-900", + iconColor: "text-violet-400", + bgIcon: "bg-violet-500/20", + hoverBorderColor: "hover:border-violet-400", + }, + { + title: "Pull Requests", + value: 9, + subtitle: "4 Awaiting Review", + icon: GitPullRequestArrow, + borderColor: "border-sky-500/30", + bgColor: "bg-gradient-to-br from-cyan-950/60 to-slate-900", + iconColor: "text-sky-400", + bgIcon: "bg-sky-500/20", + hoverBorderColor: "hover:border-sky-400", + }, + { + title: "Commits", + value: 156, + subtitle: "18% from last week", + icon: Code, + borderColor: "border-blue-500/30", + bgColor: "bg-gradient-to-br from-blue-950/60 to-slate-900", + iconColor: "text-blue-400", + bgIcon: "bg-blue-500/20", + hoverBorderColor: "hover:border-blue-400", + }, + { + title: "Open Issues", + value: 18, + subtitle: "2 Critical", + icon: CircleAlert, + borderColor: "border-green-500/30", + bgColor: "bg-gradient-to-br from-green-950/60 to-slate-900", + iconColor: "text-green-400", + bgIcon: "bg-green-500/20", + hoverBorderColor: "hover:border-green-400", + }, + { + title: "Deployments", + value: 4, + subtitle: "1 from last week", + icon: CloudUploadIcon, + borderColor: "border-orange-500/30", + bgColor: "bg-gradient-to-br from-orange-950/60 to-slate-900", + iconColor: "text-orange-400", + bgIcon: "bg-orange-500/20", + hoverBorderColor: "hover:border-orange-400", + }, + { + title: "Tasks Done", + value: 32, + subtitle: "68% Completed", + icon: CheckCircle2Icon, + borderColor: "border-purple-500/30", + bgColor: "bg-gradient-to-br from-purple-950/60 to-slate-900", + iconColor: "text-purple-400", + bgIcon: "bg-purple-500/20", + hoverBorderColor: "hover:border-purple-400", + }, +]; + +export default StatsData; diff --git a/client/src/components/layout/KPIs/StatsGrid.jsx b/client/src/components/layout/KPIs/StatsGrid.jsx new file mode 100644 index 0000000..e8f5a68 --- /dev/null +++ b/client/src/components/layout/KPIs/StatsGrid.jsx @@ -0,0 +1,102 @@ +import React from "react"; +import StatsCard from "./StatsCard.jsx"; +import { Folder, Play, CheckCircle2Icon, CircleAlert, CheckCircle, Code } from "lucide-react"; + +const getStatsData = (kpis) => { + return [ + { + title: "Active Projects", + value: kpis?.totalProjects || 0, + subtitle: "In this workspace", + icon: Folder, + borderColor: "border-violet-500/30", + bgColor: "bg-gradient-to-br from-violet-950/60 to-slate-900", + iconColor: "text-violet-400", + bgIcon: "bg-violet-500/20", + hoverBorderColor: "hover:border-violet-400", + }, + { + title: "Active Sprints", + value: kpis?.activeSprints || 0, + subtitle: "Currently running", + icon: Play, + borderColor: "border-sky-500/30", + bgColor: "bg-gradient-to-br from-cyan-950/60 to-slate-900", + iconColor: "text-sky-400", + bgIcon: "bg-sky-500/20", + hoverBorderColor: "hover:border-sky-400", + }, + { + title: "Tasks Completed", + value: kpis?.tasksCompleted || 0, + subtitle: "Across all projects", + icon: CheckCircle2Icon, + borderColor: "border-blue-500/30", + bgColor: "bg-gradient-to-br from-blue-950/60 to-slate-900", + iconColor: "text-blue-400", + bgIcon: "bg-blue-500/20", + hoverBorderColor: "hover:border-blue-400", + }, + { + title: "Open Bugs", + value: kpis?.openBugs || 0, + subtitle: "Requires attention", + icon: CircleAlert, + borderColor: "border-red-500/30", + bgColor: "bg-gradient-to-br from-red-950/60 to-slate-900", + iconColor: "text-red-400", + bgIcon: "bg-red-500/20", + hoverBorderColor: "hover:border-red-400", + }, + { + title: "Total Sprints", + value: kpis?.totalSprints || 0, + subtitle: "Historical data", + icon: Code, + borderColor: "border-orange-500/30", + bgColor: "bg-gradient-to-br from-orange-950/60 to-slate-900", + iconColor: "text-orange-400", + bgIcon: "bg-orange-500/20", + hoverBorderColor: "hover:border-orange-400", + }, + { + title: "Total Tasks", + value: kpis?.totalTasks || 0, + subtitle: "Total recorded", + icon: CheckCircle, + borderColor: "border-purple-500/30", + bgColor: "bg-gradient-to-br from-purple-950/60 to-slate-900", + iconColor: "text-purple-400", + bgIcon: "bg-purple-500/20", + hoverBorderColor: "hover:border-purple-400", + }, + ]; +}; + +const StatsGrid = ({ kpis }) => { + const stats = getStatsData(kpis); + + return ( +
+
+ {stats.map((stat) => ( + + ))} +
+
+ ); +}; + +export default StatsGrid; \ No newline at end of file diff --git a/client/src/components/layout/LowerMiddleSection/LowerMiddleSection.jsx b/client/src/components/layout/LowerMiddleSection/LowerMiddleSection.jsx new file mode 100644 index 0000000..9a4cd98 --- /dev/null +++ b/client/src/components/layout/LowerMiddleSection/LowerMiddleSection.jsx @@ -0,0 +1,20 @@ +import React from 'react' +import { RecentActivity } from './RecentActivity'; +import { PullRequests } from './PullRequests'; +import { OpenIssues } from './OpenIssues'; + +const LowerMiddleSection = ({ recentActivity, orgId, workspaceId }) => { + return ( +
+
+ + +
+ +
+
+
+ ); +} + +export default LowerMiddleSection; \ No newline at end of file diff --git a/client/src/components/layout/LowerMiddleSection/OpenIssues.jsx b/client/src/components/layout/LowerMiddleSection/OpenIssues.jsx new file mode 100644 index 0000000..4d4adad --- /dev/null +++ b/client/src/components/layout/LowerMiddleSection/OpenIssues.jsx @@ -0,0 +1,135 @@ +import { useEffect, useState } from "react"; +import { CircleAlert, LoaderCircle } from "lucide-react"; +import Card from "../../common/Card"; +import axios from "axios"; + +const getPriorityStyles = (priority) => { + switch (priority) { + case "Critical": + return "bg-red-500/20 text-red-400"; + + case "High": + return "bg-orange-500/20 text-orange-400"; + + case "Medium": + return "bg-yellow-500/20 text-yellow-400"; + + case "Low": + return "bg-blue-500/20 text-blue-400"; + + default: + return "bg-slate-500/20 text-slate-400"; + } +}; + +const getIconStyles = (priority) => { + switch (priority) { + case "Critical": + return "text-red-400"; + + case "High": + return "text-orange-400"; + + case "Medium": + return "text-yellow-400"; + + case "Low": + return "text-blue-400"; + + default: + return "text-slate-400"; + } +}; + +export const OpenIssues = ({ orgId, workspaceId }) => { + const [issues, setIssues] = useState([]); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const loadIssues = async () => { + try { + const result = await axios.get(`http://localhost:5000/api/organizations/${orgId}/workspaces/${workspaceId}/github/issues`, { withCredentials: true }); + setIssues(result.data.data); + } catch (err) { + setError( + err.response?.data?.message || "Could not load open issues." + ); + } finally { + setIsLoading(false); + } + }; + + loadIssues(); + }, [orgId, workspaceId]); + + return ( + +
+

Open Issues

+
+ + {isLoading && ( +
+ +
+ )} + + {!isLoading && error && ( +

{error}

+ )} + + {!isLoading && !error && issues.length === 0 && ( +

+ No open GitHub issues. +

+ )} + + {!isLoading && !error && issues.length > 0 && ( + + )} +
+ ); +}; + +export default OpenIssues; \ No newline at end of file diff --git a/client/src/components/layout/LowerMiddleSection/PullRequests.jsx b/client/src/components/layout/LowerMiddleSection/PullRequests.jsx new file mode 100644 index 0000000..baf708e --- /dev/null +++ b/client/src/components/layout/LowerMiddleSection/PullRequests.jsx @@ -0,0 +1,126 @@ +import { useEffect, useState } from "react"; +import { + GitPullRequest, + LoaderCircle, + MessageSquare, +} from "lucide-react"; +import Card from "../../common/Card"; +import axios from "axios"; + +const getStatusStyles = (status) => { + switch (status) { + case "Draft": + return "bg-slate-500/20 text-slate-300"; + + case "Review": + return "bg-purple-500/20 text-purple-400"; + + case "Open": + return "bg-cyan-500/20 text-cyan-400"; + + default: + return "bg-slate-500/20 text-slate-400"; + } +}; + +export const PullRequests = ({ orgId, workspaceId }) => { + const [pullRequests, setPullRequests] = useState([]); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const loadPullRequests = async () => { + try { + const result = await axios.get(`http://localhost:5000/api/organizations/${orgId}/workspaces/${workspaceId}/github/pull-requests`, { withCredentials: true }); + setPullRequests(result.data.data); + } catch (err) { + setError( + err.response?.data?.message || + "Could not load pull requests." + ); + } finally { + setIsLoading(false); + } + }; + + loadPullRequests(); + }, [orgId, workspaceId]); + + return ( + +
+

+ Pull Requests +

+
+ + {isLoading && ( +
+ +
+ )} + + {!isLoading && error && ( +

{error}

+ )} + + {!isLoading && !error && pullRequests.length === 0 && ( +

+ No open GitHub pull requests. +

+ )} + + {!isLoading && !error && pullRequests.length > 0 && ( + + )} +
+ ); +}; + +export default PullRequests; \ No newline at end of file diff --git a/client/src/components/layout/LowerMiddleSection/RecentActivity.jsx b/client/src/components/layout/LowerMiddleSection/RecentActivity.jsx new file mode 100644 index 0000000..c27eaaa --- /dev/null +++ b/client/src/components/layout/LowerMiddleSection/RecentActivity.jsx @@ -0,0 +1,82 @@ +import React from "react"; +import Card from "../../common/Card"; +import { MessageSquare, GitMerge, CircleDot, GitCommit, CheckCircle, PlusCircle, Play, CheckSquare } from "lucide-react"; + +const timeAgo = (dateStr) => { + const diff = (new Date() - new Date(dateStr)) / 1000; + if (diff < 60) return "just now"; + if (diff < 3600) return Math.floor(diff / 60) + "m ago"; + if (diff < 86400) return Math.floor(diff / 3600) + "h ago"; + return Math.floor(diff / 86400) + "d ago"; +}; + +const getActivityConfig = (type) => { + switch(type) { + case 'PROJECT_CREATED': + return { icon: PlusCircle, bg: 'bg-emerald-500/20', color: 'text-emerald-400' }; + case 'TASK_CREATED': + return { icon: CircleDot, bg: 'bg-blue-500/20', color: 'text-blue-400' }; + case 'TASK_UPDATED': + return { icon: MessageSquare, bg: 'bg-orange-500/20', color: 'text-orange-400' }; + case 'TASK_COMPLETED': + return { icon: CheckSquare, bg: 'bg-emerald-500/20', color: 'text-emerald-400' }; + case 'SPRINT_CREATED': + return { icon: PlusCircle, bg: 'bg-purple-500/20', color: 'text-purple-400' }; + case 'SPRINT_STARTED': + return { icon: Play, bg: 'bg-cyan-500/20', color: 'text-cyan-400' }; + case 'SPRINT_COMPLETED': + return { icon: CheckCircle, bg: 'bg-green-500/20', color: 'text-green-400' }; + default: + return { icon: CircleDot, bg: 'bg-slate-500/20', color: 'text-slate-400' }; + } +}; + +export const RecentActivity = ({ activities }) => { + return ( + +
+

+ Recent Activity +

+
+ +
+ {!activities || activities.length === 0 ? ( +

No recent activity.

+ ) : ( + activities.map((activity) => { + const config = getActivityConfig(activity.type); + const Icon = config.icon; + + return ( +
+
+ +
+ {activity.user?.avatar ? ( + Avatar + ) : ( +
+ {activity.user?.username?.charAt(0) || '?'} +
+ )} +
+

+ {activity.user?.username}{" "} + {activity.message.replace(`User ${activity.user?.username} `, "")} +

+

{timeAgo(activity.createdAt)}

+
+
+ ); + }) + )} +
+
+ ); +}; + +export default RecentActivity; diff --git a/client/src/components/layout/MiddleSection/CICDStatus.jsx b/client/src/components/layout/MiddleSection/CICDStatus.jsx new file mode 100644 index 0000000..e25c6e6 --- /dev/null +++ b/client/src/components/layout/MiddleSection/CICDStatus.jsx @@ -0,0 +1,70 @@ +import React from "react"; +import Card from "../../common/Card"; +import { ArrowRight } from "lucide-react"; + +const pipelineSteps = [ + { + name: "Build", + status: "Passed", + time: "2 min ago", + icon: "✅", + color: "text-green-400", + }, + { + name: "Tests", + status: "143 Passed", + time: "2 min ago", + icon: "✅", + color: "text-green-400", + }, + { + name: "Lint", + status: "Passed", + time: "2 min ago", + icon: "✅", + color: "text-green-400", + }, + { + name: "Deployment", + status: "Deployed", + time: "5 min ago", + icon: "🚀", + color: "text-orange-400", + }, +]; + +export const CICDStatus = () => { + return ( + +

CI/CD Status

+ +
+ {pipelineSteps.map((step) => ( +
+
{step.name}
+ +
+ {step.icon} + {step.status} +
+ +
{step.time}
+
+ ))} +
+ +
+ +
+
+ ); +}; + +export default CICDStatus; diff --git a/client/src/components/layout/MiddleSection/CurrentSprint.jsx b/client/src/components/layout/MiddleSection/CurrentSprint.jsx new file mode 100644 index 0000000..d188850 --- /dev/null +++ b/client/src/components/layout/MiddleSection/CurrentSprint.jsx @@ -0,0 +1,86 @@ +import { LoaderCircle } from "lucide-react"; +import Card from "../../common/Card"; + +const StatItem = ({ label, value }) => { + return ( +
+

{label}

+

+ {value} +

+
+ ); +}; + +const formatDate = (date) => { + if (!date) return "No due date"; + return new Intl.DateTimeFormat("en-IN", { + day: "numeric", + month: "short", + year: "numeric", + }).format(new Date(date)); +}; + +export const CurrentSprint = ({ sprint }) => { + if (!sprint) { + return ( + +

+ Current Sprint +

+

No active sprint. Start a sprint from the Sprints page.

+
+ ); + } + + // Calculate days left + const daysLeft = sprint.endDate + ? Math.max(0, Math.ceil((new Date(sprint.endDate) - new Date()) / (1000 * 60 * 60 * 24))) + : null; + + return ( + +
+

+ Current Sprint +

+
+ {sprint.name} +
+
+ +
+

+ {sprint.goal || "No goal specified"} +

+

+ Due {formatDate(sprint.endDate)} + {daysLeft !== null && ` (${daysLeft} days left)`} +

+
+ +
+
+
+
+ + {sprint.progress}% + +
+ +
+
+ + + + +
+
+ + ); +}; + +export default CurrentSprint; \ No newline at end of file diff --git a/client/src/components/layout/MiddleSection/MiddleSection.jsx b/client/src/components/layout/MiddleSection/MiddleSection.jsx new file mode 100644 index 0000000..79c05b2 --- /dev/null +++ b/client/src/components/layout/MiddleSection/MiddleSection.jsx @@ -0,0 +1,20 @@ +import React from 'react' +import { CurrentSprint } from './CurrentSprint'; +import { ProjectOverview } from './ProjectOverview'; +import { CICDStatus } from './CICDStatus'; + +const MiddleSection = ({ currentSprint, projectOverview }) => { + return ( +
+
+ + +
+ +
+
+
+ ); +} + +export default MiddleSection; \ No newline at end of file diff --git a/client/src/components/layout/MiddleSection/ProjectOverview.jsx b/client/src/components/layout/MiddleSection/ProjectOverview.jsx new file mode 100644 index 0000000..c21c5f1 --- /dev/null +++ b/client/src/components/layout/MiddleSection/ProjectOverview.jsx @@ -0,0 +1,57 @@ +import React from "react"; +import Card from "../../common/Card"; +import { Link, useParams } from "react-router-dom"; + +export const ProjectOverview = ({ projects }) => { + const { orgId, workspaceId } = useParams(); + + if (!projects || projects.length === 0) { + return ( + +

Project Overview

+

No active projects yet.

+ + Create Project + +
+ ); + } + + return ( + +
+

+ Project Overview +

+ View All +
+ +
+ {projects.map(project => ( +
+
+
+ {project.name.charAt(0)} +
+
+

{project.name}

+
+
+
+
+ {project.progress}% +
+
+
+
+ ))} +
+ + ); +}; diff --git a/client/src/components/layout/Navbar.jsx b/client/src/components/layout/Navbar.jsx new file mode 100644 index 0000000..bd1e106 --- /dev/null +++ b/client/src/components/layout/Navbar.jsx @@ -0,0 +1,108 @@ +import { Search, Menu, MessageCircle, LogOut } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { useNavigationLoading } from "../../context/NavigationLoadingContext.jsx"; +import githubIcon from "/src/assets/github.svg"; +import { logout } from "../../services/googleAuthService.js"; +import NotificationDropdown from "./NotificationDropdown.jsx"; + +const Navbar = ({ toggleSidebar, githubData }) => { + const { goTo } = useNavigationLoading(); + const dropdownRef = useRef(null); + + const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false); + const [imageFailed, setImageFailed] = useState(false); + + const storedUser = localStorage.getItem("user"); + let user = null; + try { + user = storedUser ? JSON.parse(storedUser) : null; + } catch (e) { + console.error("Failed to parse user from local storage"); + } + + const username = user?.username || "User"; + const avatar = user?.avatar; + const userInitial = username.charAt(0).toUpperCase(); + + const handleLogout = async () => { + try { + await logout(); + } catch (error) { + console.error("Logout request failed:", error); + } finally { + localStorage.removeItem("user"); + goTo("/login", { replace: true }); + } + }; + + useEffect(() => { + const closeDropdown = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsProfileMenuOpen(false); + } + }; + document.addEventListener("mousedown", closeDropdown); + return () => document.removeEventListener("mousedown", closeDropdown); + }, []); + + return ( +
+
+ + +
+

Dashboard

+

+ Welcome back, {username}! Here's what's happening with your projects. +

+
+
+ +
+ + + + +
+ +
+ + + + alert("Coming Soon!")} + /> + + {!githubData && ( +
window.location.href = "http://localhost:5000/api/auth/github?action=connect"} className="flex h-5 w-5 cursor-pointer items-center justify-center font-medium transition-transform duration-300 hover:scale-110" title="Connect your GitHub account"> + GitHub +
+ )} + +
+ + + {isProfileMenuOpen && ( +
+
+

{username}

+

{user?.email}

+
+ +
+ )} +
+
+
+ ); +}; + +export default Navbar; \ No newline at end of file diff --git a/client/src/components/layout/NotificationDropdown.jsx b/client/src/components/layout/NotificationDropdown.jsx new file mode 100644 index 0000000..740e9e6 --- /dev/null +++ b/client/src/components/layout/NotificationDropdown.jsx @@ -0,0 +1,216 @@ +import { useState, useEffect, useRef } from "react"; +import { Bell, Check, CircleAlert, MailOpen } from "lucide-react"; +import { + getNotifications, + getUnreadCount, + markAsRead, + markAllAsRead +} from "../../services/notificationService"; +import { useNavigate } from "react-router-dom"; + +const NotificationDropdown = () => { + const [isOpen, setIsOpen] = useState(false); + const [notifications, setNotifications] = useState([]); + const [unreadCount, setUnreadCount] = useState(0); + const dropdownRef = useRef(null); + const navigate = useNavigate(); + + const fetchUnreadCount = async () => { + try { + const res = await getUnreadCount(); + if (res.success) setUnreadCount(res.count); + } catch (err) { + console.error("Failed to fetch unread count", err); + } + }; + + const fetchNotifications = async () => { + try { + const res = await getNotifications(); + if (res.success) setNotifications(res.data); + } catch (err) { + console.error("Failed to fetch notifications", err); + } + }; + + useEffect(() => { + fetchUnreadCount(); + // Poll every 30 seconds + const interval = setInterval(fetchUnreadCount, 30000); + return () => clearInterval(interval); + }, []); + + useEffect(() => { + if (isOpen) { + fetchNotifications(); + } + }, [isOpen]); + + useEffect(() => { + const closeDropdown = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + const handleKeyDown = (event) => { + if (event.key === "Escape") setIsOpen(false); + }; + document.addEventListener("mousedown", closeDropdown); + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("mousedown", closeDropdown); + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const handleMarkAsRead = async (id, e) => { + if (e) e.stopPropagation(); + try { + await markAsRead(id); + setNotifications(notifications.map(n => n.id === id ? { ...n, isRead: true } : n)); + setUnreadCount(prev => Math.max(0, prev - 1)); + } catch (err) { + console.error("Failed to mark as read", err); + } + }; + + const handleMarkAllAsRead = async () => { + try { + await markAllAsRead(); + setNotifications(notifications.map(n => ({ ...n, isRead: true }))); + setUnreadCount(0); + } catch (err) { + console.error("Failed to mark all as read", err); + } + }; + + const handleNotificationClick = (notification) => { + if (!notification.isRead) { + handleMarkAsRead(notification.id); + } + + setIsOpen(false); + + // Navigate based on metadata + if (notification.type === "INVITATION") { + navigate("/invitations"); + } else if (notification.type === "SYSTEM" && notification.title === "Invitation Accepted") { + // If someone accepted, maybe take the inviter to the org members page + if (notification.metadata?.organizationId) { + navigate(`/organizations/${notification.metadata.organizationId}/members`); + } + } + }; + + const formatTimeAgo = (dateString) => { + const date = new Date(dateString); + const now = new Date(); + const seconds = Math.floor((now - date) / 1000); + if (seconds < 0) return 'Just now'; + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; + }; + + return ( +
+ + + {isOpen && ( +
+
+

Notifications

+ {unreadCount > 0 && ( + + )} +
+ +
+ {notifications.length === 0 ? ( +
+
+ +
+

You're all caught up!

+

No new notifications right now.

+
+ ) : ( + notifications.map((notif) => ( +
handleNotificationClick(notif)} + role="menuitem" + tabIndex={0} + onKeyDown={(e) => { if (e.key === 'Enter') handleNotificationClick(notif); }} + className={`flex items-start gap-3 border-b border-slate-700/50 p-4 transition-colors hover:bg-slate-700 focus:bg-slate-700 focus:outline-none cursor-pointer ${notif.isRead ? 'opacity-70 bg-slate-800' : 'bg-slate-800/80'}`} + > +
+ +
+
+

+ {notif.title} +

+

+ {notif.message} +

+

+ {formatTimeAgo(notif.createdAt)} +

+
+ {!notif.isRead && ( +
+ )} +
+ )) + )} +
+
+ View all notifications +
+
+ )} +
+ ); +}; + +export default NotificationDropdown; diff --git a/client/src/components/layout/RepositoryHealthSection/RepositoryHealth.jsx b/client/src/components/layout/RepositoryHealthSection/RepositoryHealth.jsx new file mode 100644 index 0000000..5fa38ee --- /dev/null +++ b/client/src/components/layout/RepositoryHealthSection/RepositoryHealth.jsx @@ -0,0 +1,111 @@ +import React from "react"; +import { ResponsiveContainer, LineChart, Line } from "recharts"; + +const repositoryHealthData = [ + { + title: "Code Coverage", + value: "92%", + type: "progress", + progress: 92, + color: "bg-green-500", + }, + { + title: "Test Passing Rate", + value: "98%", + type: "progress", + progress: 98, + color: "bg-green-500", + }, + { + title: "Open Issues", + value: "18", + type: "chart", + chartColor: "#ef4444", + }, + { + title: "Technical Debt", + value: "4.2 hrs", + type: "chart", + chartColor: "#f97316", + }, + { + title: "Code Smells", + value: "12", + type: "chart", + chartColor: "#a855f7", + }, + { + title: "Duplications", + value: "3.1%", + type: "chart", + chartColor: "#3b82f6", + }, +]; + +const chartData = [ + { value: 20 }, + { value: 35 }, + { value: 28 }, + { value: 45 }, + { value: 30 }, + { value: 55 }, + { value: 48 }, +]; + +const HealthMetric = ({ title, value, type, progress, color, chartColor }) => { + return ( +
+ {/* Title */} +

{title}

+ + {/* Value */} +

{value}

+ + {/* Progress Bars */} + {type === "progress" ? ( +
+
+
+ ) : ( +
+ + + + + +
+ )} +
+ ); +}; + +const RepositoryHealth = ({ orgId, workspaceId }) => { + return ( +
+ {/* Header */} +
+

+ Repository Health +

+
+ + {/* Metrics Grid */} +
+ {repositoryHealthData.map((item) => ( + + ))} +
+
+ ); +}; + +export default RepositoryHealth; diff --git a/client/src/components/layout/RightSideSection/RightSideBar.jsx b/client/src/components/layout/RightSideSection/RightSideBar.jsx new file mode 100644 index 0000000..c0ddc95 --- /dev/null +++ b/client/src/components/layout/RightSideSection/RightSideBar.jsx @@ -0,0 +1,18 @@ +import React from 'react' +import TeamMember from './TeamMember'; +import UpcomingEvents from './UpcomingEvents'; +import TeamChat from './TeamChat'; + +export const RightSideBar = () => { + return ( +
+
+ + + +
+
+ ); +} + +export default RightSideBar; \ No newline at end of file diff --git a/client/src/components/layout/RightSideSection/TeamChat.jsx b/client/src/components/layout/RightSideSection/TeamChat.jsx new file mode 100644 index 0000000..5276aab --- /dev/null +++ b/client/src/components/layout/RightSideSection/TeamChat.jsx @@ -0,0 +1,66 @@ +import React from "react"; + +const recentChats = [ + { + id: 1, + name: "Soham A.", + time: "10:30 AM", + message: "OAuth flow is ready for review.", + avatar: "https://i.pravatar.cc/150?u=4", + }, + { + id: 2, + name: "Ankur Y.", + time: "10:28 AM", + message: "Pushed the latest backend changes.", + avatar: "https://i.pravatar.cc/150?u=3", + }, + { + id: 3, + name: "Sumit S.", + time: "10:25 AM", + message: "Updated the UI for the dashboard.", + avatar: "https://i.pravatar.cc/150?u=2", + }, +]; + +export default function TeamChat() { + return ( +
+
+

+ Team Chat (Recent) +

+ + View all + +
+ +
+ {recentChats.map((chat) => ( +
+ {chat.name} +
+
+

+ {chat.name} +

+

+ {chat.time} +

+
+

{chat.message}

+
+
+ ))} +
+
+ ); +} diff --git a/client/src/components/layout/RightSideSection/TeamMember.jsx b/client/src/components/layout/RightSideSection/TeamMember.jsx new file mode 100644 index 0000000..a9ffb7b --- /dev/null +++ b/client/src/components/layout/RightSideSection/TeamMember.jsx @@ -0,0 +1,108 @@ +import React from "react"; + +export const teamMembers = [ + { + id: 1, + name: "Ankur Y. (You)", + role: "Admin", + status: "Online", + statusColor: "bg-emerald-500", + avatar: "https://i.pravatar.cc/150?u=1", + }, + { + id: 2, + name: "Sarah J.", + role: "Frontend Developer", + status: "Online", + statusColor: "bg-emerald-500", + avatar: "https://i.pravatar.cc/150?u=2", + }, + { + id: 3, + name: "Soham A.", + role: "Backend Developer", + status: "Online", + statusColor: "bg-emerald-500", + avatar: "https://i.pravatar.cc/150?u=3", + }, + { + id: 4, + name: "Anas S.", + role: "DevOps Engineer", + status: "Away", + statusColor: "bg-yellow-500", + avatar: "https://i.pravatar.cc/150?u=4", + }, + { + id: 5, + name: "Sumit S.", + role: "QA Engineer", + status: "Offline", + statusColor: "bg-gray-500", + avatar: "https://i.pravatar.cc/150?u=5", + }, + { + id: 6, + name: "Maria K.", + role: "UI/UX Designer", + status: "Online", + statusColor: "bg-emerald-500", + avatar: "https://i.pravatar.cc/150?u=6", + }, +]; + +export default function TeamMember() { + return ( +
+
+

Team Members

+ + View all + +
+ +
+ {teamMembers.map((member) => ( +
+
+ {member.name} +
+

+ {member.name} +

+

{member.jobTitle}

+
+
+ +
+ + + {member.status} + +
+
+ ))} +
+
+ ); +} diff --git a/client/src/components/layout/RightSideSection/UpcomingEvents.jsx b/client/src/components/layout/RightSideSection/UpcomingEvents.jsx new file mode 100644 index 0000000..de364e7 --- /dev/null +++ b/client/src/components/layout/RightSideSection/UpcomingEvents.jsx @@ -0,0 +1,63 @@ +import React from "react"; +import { Calendar } from "lucide-react"; + +const upcomingEvents = [ + { + id: 1, + title: "Sprint Planning", + date: "May 14, 2025 • 10:00 AM", + color: "bg-purple-500/20", + iconColor: "text-purple-500", + }, + { + id: 2, + title: "Code Review Sync", + date: "May 15, 2025 • 04:00 PM", + color: "bg-indigo-500/20", + iconColor: "text-indigo-500", + }, + { + id: 3, + title: "Demo Day", + date: "May 16, 2025 • 11:00 AM", + color: "bg-blue-500/20", + iconColor: "text-blue-500", + }, +]; + +export default function UpcomingEvents() { + return ( +
+
+

Upcoming Events

+ + View calendar + +
+ +
+ {upcomingEvents.map((event) => ( +
+
+ +
+
+

+ {event.title} +

+

{event.date}

+
+
+ ))} +
+
+ ); +} diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx new file mode 100644 index 0000000..f458a3f --- /dev/null +++ b/client/src/components/layout/Sidebar.jsx @@ -0,0 +1,304 @@ +import { Book, BookAIcon, BookCheck, BookCopy, BookIcon, BookOpenText, BubblesIcon, Calendar, Calendar1Icon, ChartBar, CircleArrowDown, CircleCheck, Folder, GitBranchMinusIcon, GitCommit, GitCommitHorizontalIcon, GitCompare, GitMergeConflict, GitPullRequest, GitPullRequestArrow, HomeIcon, LucideGitCommit, LucideMirrorRectangular, MessageCircle, Settings, Ticket, User2, User2Icon, UserCircleIcon, UserIcon, UserRound, PanelLeftOpen, PanelLeftClose, LayoutDashboard, CircleUserRound, ShieldCheck} from "lucide-react"; + +import { useOrganization } from "../../context/OrganizationContext"; +import {useState, useEffect} from 'react'; +import axios from 'axios'; +import { useParams, useNavigate } from 'react-router-dom'; + +function Sidebar({ isOpen, setIsOpen }) { + const [isCollapsed, setIsCollapsed] = useState(false); + const { orgId, workspaceId } = useParams(); + const navigate = useNavigate(); + const { organizations, currentOrg, switchOrganization, loading } = useOrganization(); + + const [workspaceRole, setWorkspaceRole] = useState(null); + + useEffect(() => { + if (orgId && workspaceId) { + axios.get(`http://localhost:5000/api/organizations/${orgId}/workspaces/${workspaceId}`, { withCredentials: true }) + .then(res => setWorkspaceRole(res.data.myRole)) + .catch(console.error); + } + }, [orgId, workspaceId]); + + // Removed canAccessSprints as sprints should be visible to all members + + const userStr = localStorage.getItem("user"); + const user = userStr ? JSON.parse(userStr) : null; + + return ( + <> + {isOpen && ( +
setIsOpen(false)} + /> + )} +
+
+ {!isCollapsed && Devhub} + +
+ {/* Organization Switcher */} + {!loading && organizations.length > 0 && ( +
+ +
+ )} + {workspaceId && ( +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/dashboard`, + ) + } + className="flex items-center h-10 px-3 gap-2 hover:bg-[#1c1f2e] hover:text-white rounded-lg font-medium cursor-pointer transition-colors" + > + + + Dashboard + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/projects`, + ) + } + className="flex items-center h-10 px-3 gap-2 hover:bg-[#1c1f2e] hover:text-white rounded-lg cursor-pointer transition-colors" + > + + + Projects + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/tasks`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Issues / Tasks + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/pull-requests`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Pull Requests + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/commits`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Commits + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/sprints`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Sprints + +
+
alert("Coming Soon!")} + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + CI/CD + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/wiki`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Wiki + +
+
alert("Coming Soon!")} + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Chat + +
+
alert("Coming Soon!")} + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Calender + +
+
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/members`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Team + +
+ {workspaceRole !== "VIEWER" && ( +
+ navigate( + `/organizations/${orgId}/workspaces/${workspaceId}/settings`, + ) + } + className="flex items-center h-10 px-3 gap-2 rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer transition-colors" + > + + + Settings + +
+ )} +
+ )} + {!workspaceId &&
} +
+
(window.location.href = "/organization")} + className={`p-2 flex items-center rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer ${isCollapsed ? "justify-center" : "gap-2"}`} + > + + + Workspace + +
+
navigate("/profile")} + className={`p-2 flex items-center rounded-lg hover:bg-[#1c1f2e] hover:text-white cursor-pointer ${isCollapsed ? "justify-center" : "gap-2"}`} + > + + + Profile + +
+ {user?.platformRole === "PLATFORM_ADMIN" && ( +
(window.location.href = "/admin")} + > + + + Admin Panel + +
+ )} +
+
+ + ); +} + +export default Sidebar; + diff --git a/client/src/context/NavigationLoadingContext.jsx b/client/src/context/NavigationLoadingContext.jsx new file mode 100644 index 0000000..565f7b2 --- /dev/null +++ b/client/src/context/NavigationLoadingContext.jsx @@ -0,0 +1,45 @@ +// client/src/context/NavigationLoadingContext.jsx + +import { createContext, useCallback, useContext, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +const NavigationLoadingContext = createContext(null); + +export function NavigationLoadingProvider({ children }) { + const navigate = useNavigate(); + const [isNavigating, setIsNavigating] = useState(false); + + const goTo = useCallback( + (to, options = {}) => { + setIsNavigating(true); + + window.setTimeout(() => { + navigate(to, options); + + // Keep the loader visible briefly after the next page renders. + window.setTimeout(() => { + setIsNavigating(false); + }, 150); + }, 1000); + }, + [navigate] + ); + + return ( + + {children} + + ); +} + +export function useNavigationLoading() { + const context = useContext(NavigationLoadingContext); + + if (!context) { + throw new Error( + "useNavigationLoading must be used inside NavigationLoadingProvider." + ); + } + + return context; +} \ No newline at end of file diff --git a/client/src/context/OrganizationContext.jsx b/client/src/context/OrganizationContext.jsx new file mode 100644 index 0000000..92303da --- /dev/null +++ b/client/src/context/OrganizationContext.jsx @@ -0,0 +1,73 @@ +import React, { createContext, useState, useEffect, useContext } from "react"; +import axios from "axios"; + +const OrganizationContext = createContext(); + +export const OrganizationProvider = ({ children }) => { + const [organizations, setOrganizations] = useState([]); + const [currentOrg, setCurrentOrg] = useState(null); + const [loading, setLoading] = useState(true); + + // Fetch all organizations the logged-in user belongs to + const fetchOrganizations = async () => { + try { + const res = await axios.get( + "http://localhost:5000/api/organizations/mine", + { withCredentials: true }, + ); + if (res.data.success) { + setOrganizations(res.data.data); + + // Remember the last organization they were looking at + const storedOrgId = localStorage.getItem("currentOrgId"); + if (res.data.data.length > 0) { + const orgToSelect = + res.data.data.find((o) => o.id === parseInt(storedOrgId)) || + res.data.data[0]; + setCurrentOrg(orgToSelect); + localStorage.setItem("currentOrgId", orgToSelect.id); + } else { + setCurrentOrg(null); + localStorage.removeItem("currentOrgId"); + } + } + } catch (error) { + console.error("Failed to fetch organizations", error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + // Only fetch if a user is currently logged in + if (localStorage.getItem("user")) { + fetchOrganizations(); + } else { + setLoading(false); + } + }, []); + + const switchOrganization = (orgId) => { + const org = organizations.find((o) => o.id === parseInt(orgId)); + if (org) { + setCurrentOrg(org); + localStorage.setItem("currentOrgId", org.id); + } + }; + + return ( + + {children} + + ); +}; + +export const useOrganization = () => useContext(OrganizationContext); diff --git a/client/src/index.css b/client/src/index.css index 3d552a6..503f7da 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1,2 +1,3 @@ @import "tailwindcss"; +@plugin "@tailwindcss/typography"; diff --git a/client/src/main.jsx b/client/src/main.jsx index b9a1a6d..103ac7c 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,10 +1,19 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.jsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { GoogleOAuthProvider } from "@react-oauth/google"; +import axios from "axios"; +import { OrganizationProvider } from "./context/OrganizationContext.jsx"; +import "./index.css"; +import App from "./App.jsx"; -createRoot(document.getElementById('root')).render( +axios.defaults.withCredentials = true; + +createRoot(document.getElementById("root")).render( - + + + + + , -) +); \ No newline at end of file diff --git a/client/src/pages/AdminDashboard.jsx b/client/src/pages/AdminDashboard.jsx new file mode 100644 index 0000000..6dc8e40 --- /dev/null +++ b/client/src/pages/AdminDashboard.jsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from "react"; +import Navbar from "../components/layout/Navbar"; +import AdminSidebar from "../components/admin/AdminSidebar"; +import AdminOverview from "../components/admin/AdminOverview"; +import AdminUsers from "../components/admin/AdminUsers"; +import AdminOrganizations from "../components/admin/AdminOrganizations"; +import AdminWorkspaces from "../components/admin/AdminWorkspaces"; +import AdminAuditLogs from "../components/admin/AdminAuditLogs"; +import AdminSettings from "../components/admin/AdminSettings"; +import AdminSecurity from "../components/admin/AdminSecurity"; + +const AdminDashboard = () => { + const [activeTab, setActiveTab] = useState("dashboard"); + const [user, setUser] = useState(null); + + useEffect(() => { + const userStr = localStorage.getItem("user"); + if (userStr) { + try { + setUser(JSON.parse(userStr)); + } catch (e) { + console.error("Failed to parse user from local storage"); + } + } + }, []); + + const renderContent = () => { + switch (activeTab) { + case "dashboard": + return ; + case "users": + return ; + case "orgs": + return ; + case "workspaces": + return ; + case "audit": + return ; + case "settings": + return ; + case "security": + return ; + default: + return ; + } + }; + + return ( +
+ {/* + We use the new dedicated AdminSidebar instead of the global application Sidebar + to provide a focused, full-height administration experience as per the redesign. + */} + + +
+ {}} /> {/* Empty function since Admin sidebar isn't collapsible in the same way yet */} + +
+ {renderContent()} +
+
+
+ ); +}; + +export default AdminDashboard; diff --git a/client/src/pages/Commits.jsx b/client/src/pages/Commits.jsx new file mode 100644 index 0000000..4d637f3 --- /dev/null +++ b/client/src/pages/Commits.jsx @@ -0,0 +1,239 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { useParams } from "react-router-dom"; +// Fixed the imports here! Removed Github and UserCircle2 +import { + GitCommit, + ExternalLink, + RefreshCw, + Clock, + GitBranch, + User, +} from "lucide-react"; +import Sidebar from "../components/layout/Sidebar"; +import Navbar from "../components/layout/Navbar"; + +const Commits = () => { + const { orgId, workspaceId } = useParams(); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + const [commits, setCommits] = useState([]); + const [branches, setBranches] = useState([]); + const [selectedBranch, setSelectedBranch] = useState(""); + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + + const fetchBranches = async () => { + try { + const res = await axios.get( + `http://localhost:5000/api/organizations/${orgId}/workspaces/${workspaceId}/github/branches`, + { withCredentials: true }, + ); + const { branches, defaultBranch } = res.data.data; + setBranches(branches); + if (branches.length > 0) setSelectedBranch(defaultBranch || branches[0]); + } catch (err) { + console.error("Could not load branches", err); + } + }; + + const fetchCommits = async () => { + if (!selectedBranch) return; + try { + setLoading(true); + const res = await axios.get( + `http://localhost:5000/api/organizations/${orgId}/workspaces/${workspaceId}/github/commits?branch=${selectedBranch}`, + { withCredentials: true }, + ); + setCommits(res.data.data); + } catch (err) { + console.error(err); + setError("Failed to load commits."); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchBranches(); + }, [workspaceId]); + useEffect(() => { + fetchCommits(); + }, [selectedBranch]); + + return ( +
+
+ + + +
+ setIsSidebarOpen(!isSidebarOpen)} /> + +
+
+
+
+ {/* Changed to GitBranch */} + +
+
+

+ Commit History +

+

+ Track your team's latest code changes. +

+
+
+ +
+
+
+ +
+ +
+ + + +
+
+ + +
+
+ +
+ {loading ? ( +
+ + + Syncing with GitHub... + +
+ ) : error ? ( +
+
+ +
+ {error} +
+ ) : commits.length === 0 ? ( +
+ + + No commits found on '{selectedBranch}' + +
+ ) : ( +
+ {commits.map((commit, index) => ( +
+
+ {commit.author} +
+ +
+
+
+ + {commit.message} + + +
+ + {/* Changed to User */} + + {commit.author} + + + + {new Date(commit.date).toLocaleString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + })} + +
+
+ + + + {commit.shortSha} + + +
+
+
+ ))} +
+ )} +
+
+
+
+ ); +}; + +export default Commits; diff --git a/client/src/pages/CreateWorkspaceWizard.jsx b/client/src/pages/CreateWorkspaceWizard.jsx new file mode 100644 index 0000000..864b6d4 --- /dev/null +++ b/client/src/pages/CreateWorkspaceWizard.jsx @@ -0,0 +1,332 @@ +import React, { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import { useOrganization } from "../context/OrganizationContext"; +import { Check, ChevronRight } from "lucide-react"; +import Sidebar from "../components/layout/Sidebar"; +import Navbar from "../components/layout/Navbar"; + +const CreateWorkspaceWizard = () => { + const { currentOrg } = useOrganization(); + const navigate = useNavigate(); + + const [step, setStep] = useState(1); + const [loading, setLoading] = useState(false); + + // Step 1 Data + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [repositoryOption, setRepositoryOption] = useState("CREATE_NEW"); + + // Step 2 Data: CREATE_NEW + const [repoName, setRepoName] = useState(""); + const [repoDesc, setRepoDesc] = useState(""); + const [isPrivate, setIsPrivate] = useState(true); + + // Step 2 Data: CONNECT_EXISTING + const [githubRepos, setGithubRepos] = useState([]); + const [selectedRepoId, setSelectedRepoId] = useState(""); + + // Fetch GitHub repos automatically when they reach Step 2 + useEffect(() => { + if ( + step === 2 && + repositoryOption === "CONNECT_EXISTING" && + githubRepos.length === 0 + ) { + fetchGithubRepos(); + } + }, [step, repositoryOption]); + + const fetchGithubRepos = async () => { + try { + const res = await axios.get( + "http://localhost:5000/api/github/repositories", + { withCredentials: true }, + ); + if (res.data.success) setGithubRepos(res.data.data); + } catch (error) { + console.error("Failed to fetch github repos", error); + } + }; + + const handleNext = () => { + if (step === 1 && !name) return alert("Workspace Name is required!"); + if (step === 2 && repositoryOption === "CREATE_NEW" && !repoName) + return alert("Repository name is required!"); + if ( + step === 2 && + repositoryOption === "CONNECT_EXISTING" && + !selectedRepoId + ) + return alert("Please select a repository!"); + setStep(step + 1); + }; + + const handleSubmit = async () => { + setLoading(true); + try { + let payload = { name, description, repositoryOption }; + + if (repositoryOption === "CREATE_NEW") { + payload.repositoryName = repoName; + payload.repositoryDescription = repoDesc; + payload.isPrivate = isPrivate; + } else { + payload.existingRepo = githubRepos.find( + (r) => r.id.toString() === selectedRepoId.toString(), + ); + } + + // Hit our shiny new transaction API! + const res = await axios.post( + `http://localhost:5000/api/organizations/${currentOrg.id}/workspaces`, + payload, + { withCredentials: true }, + ); + + if (res.data.success) { + // Redirect them to the new Workspace Details page (we will build this next) + navigate( + `/organizations/${currentOrg.id}/workspaces/${res.data.data.id}/dashboard`, + ); + } + } catch (error) { + alert(error.response?.data?.message || "Failed to create workspace"); + setLoading(false); + } + }; + + const renderStep1 = () => ( +
+

Workspace Details

+
+ + setName(e.target.value)} + placeholder="e.g. Developer Collaboration Platform" + /> +
+
+ +