chore: some fixes to staff panel

This commit is contained in:
Alexander Medvedev 2026-06-11 22:04:39 +02:00
parent 35e5a8d56b
commit 8a7feb93e8
5 changed files with 554 additions and 289 deletions

465
package-lock.json generated
View File

@ -8,38 +8,38 @@
"name": "client", "name": "client",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@marsidev/react-turnstile": "^1.5.2", "@marsidev/react-turnstile": "^1.5.3",
"@vnedyalk0v/react19-simple-maps": "^2.0.7", "@vnedyalk0v/react19-simple-maps": "^2.0.9",
"axios": "^1.16.1", "axios": "^1.17.0",
"d3-geo": "^3.1.1", "d3-geo": "^3.1.1",
"d3-scale": "^4.0.2", "d3-scale": "^4.0.2",
"i18next": "^26.1.0", "i18next": "^26.3.1",
"i18next-browser-languagedetector": "^8.2.1", "i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^1.16.0", "lucide-react": "^1.17.0",
"or": "^0.2.0", "or": "^0.2.0",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.2.6", "react": "^19.2.7",
"react-dom": "^19.2.6", "react-dom": "^19.2.7",
"react-i18next": "^17.0.7", "react-i18next": "^17.0.8",
"react-is": "^19.2.6", "react-is": "^19.2.7",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.15.1", "react-router-dom": "^7.17.0",
"recharts": "^3.8.1", "recharts": "^3.8.1",
"remark-gfm": "^4.0.1" "remark-gfm": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/node": "^25.8.0", "@types/node": "^25.9.3",
"@types/react": "^19.2.14", "@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.2",
"eslint": "^10.3.0", "eslint": "^10.4.1",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0", "globals": "^17.6.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.59.3", "typescript-eslint": "^8.61.0",
"vite": "^8.0.13" "vite": "^8.0.16"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@ -383,9 +383,9 @@
} }
}, },
"node_modules/@eslint/config-helpers": { "node_modules/@eslint/config-helpers": {
"version": "0.5.5", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
"integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -440,9 +440,9 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.7.1", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
"integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -570,9 +570,9 @@
} }
}, },
"node_modules/@marsidev/react-turnstile": { "node_modules/@marsidev/react-turnstile": {
"version": "1.5.2", "version": "1.5.3",
"resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-1.5.2.tgz", "resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-1.5.3.tgz",
"integrity": "sha512-+3aBPxp86JzSC0ZmgyonoGoUEENcUkH3LGahXSpkV87ArvD2DzRCmPgh0FyQk6PQRmJwQJDAfwNavFsxUxMQWA==", "integrity": "sha512-8Dij2jiNGNczq1U4EKpO4do2XepcTPxSMc2ZzvHndO+gcp68tvMULm27z2P99rGkdB89hc3452NZeu2Rti4g6A==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "^17.0.2 || ^18.0.0 || ^19.0", "react": "^17.0.2 || ^18.0.0 || ^19.0",
@ -580,14 +580,14 @@
} }
}, },
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@tybys/wasm-util": "^0.10.1" "@tybys/wasm-util": "^0.10.2"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
@ -599,9 +599,9 @@
} }
}, },
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.130.0", "version": "0.133.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
@ -645,9 +645,9 @@
} }
}, },
"node_modules/@rolldown/binding-android-arm64": { "node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
"integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -662,9 +662,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-arm64": { "node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
"integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -679,9 +679,9 @@
} }
}, },
"node_modules/@rolldown/binding-darwin-x64": { "node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
"integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -696,9 +696,9 @@
} }
}, },
"node_modules/@rolldown/binding-freebsd-x64": { "node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
"integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -713,9 +713,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm-gnueabihf": { "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
"integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -730,9 +730,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-gnu": { "node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
"integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -747,9 +747,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-arm64-musl": { "node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
"integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -764,9 +764,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-ppc64-gnu": { "node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
"integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -781,9 +781,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-s390x-gnu": { "node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
"integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -798,9 +798,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-gnu": { "node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
"integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -815,9 +815,9 @@
} }
}, },
"node_modules/@rolldown/binding-linux-x64-musl": { "node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
"integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -832,9 +832,9 @@
} }
}, },
"node_modules/@rolldown/binding-openharmony-arm64": { "node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
"integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -849,9 +849,9 @@
} }
}, },
"node_modules/@rolldown/binding-wasm32-wasi": { "node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
"integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
@ -868,9 +868,9 @@
} }
}, },
"node_modules/@rolldown/binding-win32-arm64-msvc": { "node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
"integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -885,9 +885,9 @@
} }
}, },
"node_modules/@rolldown/binding-win32-x64-msvc": { "node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
"integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -902,9 +902,9 @@
} }
}, },
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.7", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -1057,9 +1057,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "25.8.0", "version": "25.9.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
"integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1067,10 +1067,9 @@
} }
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.14", "version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
@ -1099,17 +1098,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz",
"integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", "integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/scope-manager": "8.61.0",
"@typescript-eslint/type-utils": "8.59.3", "@typescript-eslint/type-utils": "8.61.0",
"@typescript-eslint/utils": "8.59.3", "@typescript-eslint/utils": "8.61.0",
"@typescript-eslint/visitor-keys": "8.59.3", "@typescript-eslint/visitor-keys": "8.61.0",
"ignore": "^7.0.5", "ignore": "^7.0.5",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0" "ts-api-utils": "^2.5.0"
@ -1122,7 +1121,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.59.3", "@typescript-eslint/parser": "^8.61.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0" "typescript": ">=4.8.4 <6.1.0"
} }
@ -1138,16 +1137,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz",
"integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/scope-manager": "8.61.0",
"@typescript-eslint/types": "8.59.3", "@typescript-eslint/types": "8.61.0",
"@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/typescript-estree": "8.61.0",
"@typescript-eslint/visitor-keys": "8.59.3", "@typescript-eslint/visitor-keys": "8.61.0",
"debug": "^4.4.3" "debug": "^4.4.3"
}, },
"engines": { "engines": {
@ -1163,14 +1162,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz",
"integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", "integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.59.3", "@typescript-eslint/tsconfig-utils": "^8.61.0",
"@typescript-eslint/types": "^8.59.3", "@typescript-eslint/types": "^8.61.0",
"debug": "^4.4.3" "debug": "^4.4.3"
}, },
"engines": { "engines": {
@ -1185,14 +1184,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz",
"integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", "integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.59.3", "@typescript-eslint/types": "8.61.0",
"@typescript-eslint/visitor-keys": "8.59.3" "@typescript-eslint/visitor-keys": "8.61.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1203,9 +1202,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz",
"integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", "integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1220,15 +1219,15 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz",
"integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", "integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.59.3", "@typescript-eslint/types": "8.61.0",
"@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/typescript-estree": "8.61.0",
"@typescript-eslint/utils": "8.59.3", "@typescript-eslint/utils": "8.61.0",
"debug": "^4.4.3", "debug": "^4.4.3",
"ts-api-utils": "^2.5.0" "ts-api-utils": "^2.5.0"
}, },
@ -1245,9 +1244,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz",
"integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1259,16 +1258,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz",
"integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", "integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.59.3", "@typescript-eslint/project-service": "8.61.0",
"@typescript-eslint/tsconfig-utils": "8.59.3", "@typescript-eslint/tsconfig-utils": "8.61.0",
"@typescript-eslint/types": "8.59.3", "@typescript-eslint/types": "8.61.0",
"@typescript-eslint/visitor-keys": "8.59.3", "@typescript-eslint/visitor-keys": "8.61.0",
"debug": "^4.4.3", "debug": "^4.4.3",
"minimatch": "^10.2.2", "minimatch": "^10.2.2",
"semver": "^7.7.3", "semver": "^7.7.3",
@ -1287,9 +1286,9 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.8.0", "version": "7.8.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@ -1300,16 +1299,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz",
"integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", "integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.9.1", "@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/scope-manager": "8.61.0",
"@typescript-eslint/types": "8.59.3", "@typescript-eslint/types": "8.61.0",
"@typescript-eslint/typescript-estree": "8.59.3" "@typescript-eslint/typescript-estree": "8.61.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1324,13 +1323,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz",
"integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", "integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.59.3", "@typescript-eslint/types": "8.61.0",
"eslint-visitor-keys": "^5.0.0" "eslint-visitor-keys": "^5.0.0"
}, },
"engines": { "engines": {
@ -1348,13 +1347,13 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
"integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rolldown/pluginutils": "1.0.0-rc.7" "@rolldown/pluginutils": "^1.0.0"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
@ -1374,9 +1373,9 @@
} }
}, },
"node_modules/@vnedyalk0v/react19-simple-maps": { "node_modules/@vnedyalk0v/react19-simple-maps": {
"version": "2.0.7", "version": "2.0.9",
"resolved": "https://registry.npmjs.org/@vnedyalk0v/react19-simple-maps/-/react19-simple-maps-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@vnedyalk0v/react19-simple-maps/-/react19-simple-maps-2.0.9.tgz",
"integrity": "sha512-JB7u1pBwnR6SJGP+Vxq0b8BzgJW7AHRLNHLZEr9YlCoZtiC5hsCZab9ZvE9wzQcJ3TebD/j7F6WOomdkBjfHQg==", "integrity": "sha512-QMV6QkRcAVulhgA4vEVE68tvXk7xVn5MAi5gCTSfWhWFd4YRwnGaRLA/m4UEmhTRuXe4dabGVIVKFSMfHBy77Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"d3-color": "^3.1.0", "d3-color": "^3.1.0",
@ -1454,9 +1453,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.16.1", "version": "1.17.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz",
"integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.16.0", "follow-redirects": "^1.16.0",
@ -1499,9 +1498,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "5.0.5", "version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1705,7 +1704,6 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-array": { "node_modules/d3-array": {
@ -2091,18 +2089,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "10.3.0", "version": "10.4.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz",
"integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.23.5", "@eslint/config-array": "^0.23.5",
"@eslint/config-helpers": "^0.5.5", "@eslint/config-helpers": "^0.6.0",
"@eslint/core": "^1.2.1", "@eslint/core": "^1.2.1",
"@eslint/plugin-kit": "^0.7.1", "@eslint/plugin-kit": "^0.7.2",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2", "@humanwhocodes/retry": "^0.4.2",
@ -2658,9 +2656,9 @@
} }
}, },
"node_modules/i18next": { "node_modules/i18next": {
"version": "26.1.0", "version": "26.3.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.1.0.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz",
"integrity": "sha512-dIU6td04DvQuIqVst5S9g0GviTmhZ0DYD4b9ociVGJmuCa5vZ2de/t+Enf4olvj87mF8Y2lwjNQBwC9QZsvzKQ==", "integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -3201,9 +3199,9 @@
} }
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "1.16.0", "version": "1.17.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz",
"integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==",
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@ -4273,9 +4271,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.14", "version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -4293,7 +4291,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.12",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
@ -4350,30 +4348,30 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "19.2.6", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "19.2.6", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^19.2.6" "react": "^19.2.7"
} }
}, },
"node_modules/react-i18next": { "node_modules/react-i18next": {
"version": "17.0.7", "version": "17.0.8",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.7.tgz", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
"integrity": "sha512-rwtPXsb/zwzDafN+gytcjF5YnqGQQIRmCQ6DctBC1VSipRB8GD/MWEVrFP42vjMyuYydxWxM8CZRt+yiNuuoHg==", "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.29.2", "@babel/runtime": "^7.29.2",
@ -4381,7 +4379,7 @@
"use-sync-external-store": "^1.6.0" "use-sync-external-store": "^1.6.0"
}, },
"peerDependencies": { "peerDependencies": {
"i18next": ">= 26.0.10", "i18next": ">= 26.2.0",
"react": ">= 16.8.0", "react": ">= 16.8.0",
"typescript": "^5 || ^6" "typescript": "^5 || ^6"
}, },
@ -4398,9 +4396,9 @@
} }
}, },
"node_modules/react-is": { "node_modules/react-is": {
"version": "19.2.6", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz",
"integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-markdown": { "node_modules/react-markdown": {
@ -4454,9 +4452,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.15.1", "version": "7.17.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz",
"integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==", "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^1.0.1", "cookie": "^1.0.1",
@ -4476,12 +4474,12 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "7.15.1", "version": "7.17.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz",
"integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==", "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"react-router": "7.15.1" "react-router": "7.17.0"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
@ -4609,13 +4607,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@oxc-project/types": "=0.130.0", "@oxc-project/types": "=0.133.0",
"@rolldown/pluginutils": "^1.0.0" "@rolldown/pluginutils": "^1.0.0"
}, },
"bin": { "bin": {
@ -4625,30 +4623,23 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.1", "@rolldown/binding-android-arm64": "1.0.3",
"@rolldown/binding-darwin-arm64": "1.0.1", "@rolldown/binding-darwin-arm64": "1.0.3",
"@rolldown/binding-darwin-x64": "1.0.1", "@rolldown/binding-darwin-x64": "1.0.3",
"@rolldown/binding-freebsd-x64": "1.0.1", "@rolldown/binding-freebsd-x64": "1.0.3",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
"@rolldown/binding-linux-arm64-gnu": "1.0.1", "@rolldown/binding-linux-arm64-gnu": "1.0.3",
"@rolldown/binding-linux-arm64-musl": "1.0.1", "@rolldown/binding-linux-arm64-musl": "1.0.3",
"@rolldown/binding-linux-ppc64-gnu": "1.0.1", "@rolldown/binding-linux-ppc64-gnu": "1.0.3",
"@rolldown/binding-linux-s390x-gnu": "1.0.1", "@rolldown/binding-linux-s390x-gnu": "1.0.3",
"@rolldown/binding-linux-x64-gnu": "1.0.1", "@rolldown/binding-linux-x64-gnu": "1.0.3",
"@rolldown/binding-linux-x64-musl": "1.0.1", "@rolldown/binding-linux-x64-musl": "1.0.3",
"@rolldown/binding-openharmony-arm64": "1.0.1", "@rolldown/binding-openharmony-arm64": "1.0.3",
"@rolldown/binding-wasm32-wasi": "1.0.1", "@rolldown/binding-wasm32-wasi": "1.0.3",
"@rolldown/binding-win32-arm64-msvc": "1.0.1", "@rolldown/binding-win32-arm64-msvc": "1.0.3",
"@rolldown/binding-win32-x64-msvc": "1.0.1" "@rolldown/binding-win32-x64-msvc": "1.0.3"
} }
}, },
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
"license": "MIT"
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@ -4753,9 +4744,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.16", "version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4841,7 +4832,7 @@
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -4852,16 +4843,16 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.59.3", "version": "8.61.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz",
"integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", "integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.59.3", "@typescript-eslint/eslint-plugin": "8.61.0",
"@typescript-eslint/parser": "8.59.3", "@typescript-eslint/parser": "8.61.0",
"@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/typescript-estree": "8.61.0",
"@typescript-eslint/utils": "8.59.3" "@typescript-eslint/utils": "8.61.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -5070,17 +5061,17 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "8.0.13", "version": "8.0.16",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lightningcss": "^1.32.0", "lightningcss": "^1.32.0",
"picomatch": "^4.0.4", "picomatch": "^4.0.4",
"postcss": "^8.5.14", "postcss": "^8.5.15",
"rolldown": "1.0.1", "rolldown": "1.0.3",
"tinyglobby": "^0.2.16" "tinyglobby": "^0.2.17"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"

View File

@ -11,40 +11,40 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@marsidev/react-turnstile": "^1.5.2", "@marsidev/react-turnstile": "^1.5.3",
"@vnedyalk0v/react19-simple-maps": "^2.0.7", "@vnedyalk0v/react19-simple-maps": "^2.0.9",
"axios": "^1.16.1", "axios": "^1.17.0",
"d3-geo": "^3.1.1", "d3-geo": "^3.1.1",
"d3-scale": "^4.0.2", "d3-scale": "^4.0.2",
"i18next": "^26.1.0", "i18next": "^26.3.1",
"i18next-browser-languagedetector": "^8.2.1", "i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^1.16.0", "lucide-react": "^1.17.0",
"or": "^0.2.0", "or": "^0.2.0",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.2.6", "react": "^19.2.7",
"react-dom": "^19.2.6", "react-dom": "^19.2.7",
"react-i18next": "^17.0.7", "react-i18next": "^17.0.8",
"react-is": "^19.2.6", "react-is": "^19.2.7",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.15.1", "react-router-dom": "^7.17.0",
"recharts": "^3.8.1", "recharts": "^3.8.1",
"remark-gfm": "^4.0.1" "remark-gfm": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/node": "^25.8.0", "@types/node": "^25.9.3",
"@types/react": "^19.2.14", "@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.2",
"eslint": "^10.3.0", "eslint": "^10.4.1",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0", "globals": "^17.6.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.59.3", "typescript-eslint": "^8.61.0",
"vite": "^8.0.13" "vite": "^8.0.16"
}, },
"overrides": { "overrides": {
"vite": "^8.0.13" "vite": "^8.0.16"
} }
} }

View File

@ -109,10 +109,10 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
return children; return children;
}; };
// --- Admin Route --- // --- Staff Route (Admin & Moderator) ---
const AdminRoute = ({ children }: { children: React.ReactNode }) => { const StaffRoute = ({ children }: { children: React.ReactNode }) => {
const { user } = useAuth(); const { user } = useAuth();
if (!user || user.role !== 'admin') { if (!user || (user.role !== 'admin' && user.role !== 'moderator')) {
return <Navigate to="/" />; return <Navigate to="/" />;
} }
return children; return children;
@ -147,7 +147,7 @@ const App = () => (
<Route path="add-plugin" element={<AddPlugin />} /> <Route path="add-plugin" element={<AddPlugin />} />
<Route path="manage-plugin/:id" element={<ManagePlugin />} /> <Route path="manage-plugin/:id" element={<ManagePlugin />} />
</Route> </Route>
<Route path="admin" element={<AdminRoute><AdminPanel /></AdminRoute>} /> <Route path="staff" element={<StaffRoute><AdminPanel /></StaffRoute>} />
<Route path="settings" element={<ProtectedRoute><ProfilePage /></ProtectedRoute>} /> <Route path="settings" element={<ProtectedRoute><ProfilePage /></ProtectedRoute>} />
</Route> </Route>
</Routes> </Routes>
@ -223,11 +223,11 @@ const Navbar = ({ user }: any) => {
<div className={`nav-content ${isMenuOpen ? 'open' : ''}`}> <div className={`nav-content ${isMenuOpen ? 'open' : ''}`}>
<div className="nav-links"> <div className="nav-links">
<NavLink to="/" onClick={() => setIsMenuOpen(false)}>{t('home')}</NavLink> <NavLink to="/" onClick={() => setIsMenuOpen(false)}>{t('home')}</NavLink>
{user && (user.plugin_count > 0 || user.role === 'admin') && ( {user && (user.plugin_count > 0 || user.role === 'admin' || user.role === 'moderator') && (
<NavLink to="/dashboard" onClick={() => setIsMenuOpen(false)}>{t('dashboard')}</NavLink> <NavLink to="/dashboard" onClick={() => setIsMenuOpen(false)}>{t('dashboard')}</NavLink>
)} )}
{user && <NavLink to="/dashboard/add-plugin" onClick={() => setIsMenuOpen(false)}>Publish</NavLink>} {user && <NavLink to="/dashboard/add-plugin" onClick={() => setIsMenuOpen(false)}>Publish</NavLink>}
{user && user.role === 'admin' && <NavLink to="/admin" onClick={() => setIsMenuOpen(false)}>Admin</NavLink>} {user && (user.role === 'admin' || user.role === 'moderator') && <NavLink to="/staff" onClick={() => setIsMenuOpen(false)}>Staff</NavLink>}
</div> </div>
{!user && ( {!user && (

View File

@ -154,15 +154,162 @@ select.role-badge option {
transition: all 0.2s; transition: all 0.2s;
} }
.btn-delete:hover { .action-buttons {
background: #ff4d4d; display: flex;
gap: 0.5rem;
}
.btn-view {
background: rgba(79, 126, 255, 0.1);
color: #4f7eff;
border: 1px solid rgba(79, 126, 255, 0.2);
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.btn-view:hover {
background: #4f7eff;
color: white; color: white;
} }
.admin-loading { /* Modal Styles */
text-align: center; .admin-modal-overlay {
padding: 4rem; position: fixed;
opacity: 0.6; top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(4px);
}
.admin-modal {
background: #1a1a1a;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
width: 90%;
max-width: 600px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
animation: modalAppear 0.3s ease-out;
}
@keyframes modalAppear {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.modal-header h2 {
margin: 0;
font-size: 1.25rem;
color: var(--primary, #4f7eff);
}
.close-modal {
background: none;
border: none;
color: #888;
font-size: 1.5rem;
cursor: pointer;
line-height: 1;
}
.modal-body {
padding: 1.5rem;
}
.details-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.detail-item {
font-size: 0.95rem;
color: #ccc;
}
.detail-item strong {
display: block;
color: #888;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.05em;
margin-bottom: 0.25rem;
}
.detail-item.full-width {
grid-column: 1 / -1;
}
@media (max-width: 768px) {
.details-grid {
grid-template-columns: 1fr;
}
}
.admin-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
gap: 1rem;
flex-wrap: wrap;
}
.admin-search {
flex: 1;
min-width: 250px;
}
.admin-search input {
width: 100%;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
padding: 10px 16px;
border-radius: 8px;
font-family: inherit;
outline: none;
transition: border-color 0.2s;
}
.admin-search input:focus {
border-color: var(--primary, #4f7eff);
}
.admin-filters {
display: flex;
gap: 0.5rem;
}
.admin-filters select {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
padding: 8px 12px;
border-radius: 8px;
font-family: inherit;
outline: none;
cursor: pointer;
}
.admin-filters select:focus {
border-color: var(--primary, #4f7eff);
} }
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@ -1,8 +1,12 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import api from '../../api'; import api from '../../api';
import { useAuth } from '../../App';
import './AdminPanel.css'; import './AdminPanel.css';
const AdminPanel = () => { const AdminPanel = () => {
const { user: currentUser } = useAuth();
const isAdmin = currentUser?.role === 'admin';
const [activeTab, setActiveTab] = useState<'users' | 'plugins' | 'logs'>('users'); const [activeTab, setActiveTab] = useState<'users' | 'plugins' | 'logs'>('users');
const [users, setUsers] = useState<any[]>([]); const [users, setUsers] = useState<any[]>([]);
const [plugins, setPlugins] = useState<any[]>([]); const [plugins, setPlugins] = useState<any[]>([]);
@ -10,8 +14,17 @@ const AdminPanel = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Search and Filter state
const [searchQuery, setSearchQuery] = useState('');
const [logFilterAction, setLogFilterAction] = useState('all');
const [logFilterModerator, setLogFilterModerator] = useState('all');
// Details Modal state
const [selectedItem, setSelectedItem] = useState<{type: 'user' | 'plugin' | 'log', data: any} | null>(null);
useEffect(() => { useEffect(() => {
fetchData(); fetchData();
setSearchQuery(''); // Reset search on tab change
}, [activeTab]); }, [activeTab]);
const fetchData = async () => { const fetchData = async () => {
@ -50,10 +63,9 @@ const AdminPanel = () => {
}; };
const changeRole = async (id: number, newRole: string) => { const changeRole = async (id: number, newRole: string) => {
const reason = window.prompt(`Enter reason for changing role to ${newRole}:`); const reason = window.prompt(`Enter reason for changing role to ${newRole} (Optional):`);
if (!reason) return;
try { try {
await api.put(`/admin/users/${id}/role`, { role: newRole, reason }); await api.put(`/admin/users/${id}/role`, { role: newRole, reason: reason || undefined });
setUsers(users.map(u => u.id === id ? { ...u, role: newRole } : u)); setUsers(users.map(u => u.id === id ? { ...u, role: newRole } : u));
alert('Role updated successfully'); alert('Role updated successfully');
} catch (err: any) { } catch (err: any) {
@ -72,10 +84,40 @@ const AdminPanel = () => {
} }
}; };
// Filtering logic
const filteredUsers = users.filter(u =>
u.username.toLowerCase().includes(searchQuery.toLowerCase()) ||
u.email?.toLowerCase().includes(searchQuery.toLowerCase()) ||
u.id.toString().includes(searchQuery)
);
const filteredPlugins = plugins.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.dev_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.id.toString().includes(searchQuery)
);
const filteredLogs = logs.filter(l => {
const matchesSearch =
l.moderator_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
l.action.toLowerCase().includes(searchQuery.toLowerCase()) ||
l.target_type.toLowerCase().includes(searchQuery.toLowerCase()) ||
l.reason?.toLowerCase().includes(searchQuery.toLowerCase()) ||
l.target_id.toString().includes(searchQuery);
const matchesAction = logFilterAction === 'all' || l.action === logFilterAction;
const matchesModerator = logFilterModerator === 'all' || l.moderator_name === logFilterModerator;
return matchesSearch && matchesAction && matchesModerator;
});
// Unique moderators for the filter dropdown
const uniqueModerators = Array.from(new Set(logs.map(l => l.moderator_name)));
return ( return (
<div className="admin-container"> <div className="admin-container">
<header className="admin-header"> <header className="admin-header">
<h1>Admin <span>Panel</span></h1> <h1>Staff <span>Panel</span></h1>
<div className="admin-tabs"> <div className="admin-tabs">
<button <button
className={activeTab === 'users' ? 'active' : ''} className={activeTab === 'users' ? 'active' : ''}
@ -98,6 +140,32 @@ const AdminPanel = () => {
</div> </div>
</header> </header>
<div className="admin-controls">
<div className="admin-search">
<input
type="text"
placeholder={`Search ${activeTab}...`}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{activeTab === 'logs' && (
<div className="admin-filters">
<select value={logFilterAction} onChange={(e) => setLogFilterAction(e.target.value)}>
<option value="all">All Actions</option>
<option value="soft_delete">Soft Delete</option>
<option value="role_change">Role Change</option>
</select>
<select value={logFilterModerator} onChange={(e) => setLogFilterModerator(e.target.value)}>
<option value="all">All Moderators</option>
{uniqueModerators.map(m => (
<option key={m} value={m}>{m}</option>
))}
</select>
</div>
)}
</div>
{error && <div className="admin-error">{error}</div>} {error && <div className="admin-error">{error}</div>}
<main className="admin-content"> <main className="admin-content">
@ -111,33 +179,38 @@ const AdminPanel = () => {
<th>Username</th> <th>Username</th>
<th>Email</th> <th>Email</th>
<th>Role</th> <th>Role</th>
<th>Stripe</th>
<th>Status</th> <th>Status</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.map(u => ( {filteredUsers.map(u => (
<tr key={u.id} className={u.deleted_at ? 'row-deleted' : ''}> <tr key={u.id} className={u.deleted_at ? 'row-deleted' : ''}>
<td data-label="ID">{u.id}</td> <td data-label="ID">{u.id}</td>
<td data-label="Username">{u.username}</td> <td data-label="Username">{u.username}</td>
<td data-label="Email">{u.email}</td> <td data-label="Email">{u.email}</td>
<td data-label="Role"> <td data-label="Role">
<select {isAdmin ? (
className={`role-badge ${u.role}`} <select
value={u.role} className={`role-badge ${u.role}`}
onChange={(e) => changeRole(u.id, e.target.value)} value={u.role}
disabled={!!u.deleted_at} onChange={(e) => changeRole(u.id, e.target.value)}
> disabled={!!u.deleted_at}
<option value="user">User</option> >
<option value="moderator">Moderator</option> <option value="user">User</option>
<option value="admin">Admin</option> <option value="moderator">Moderator</option>
</select> <option value="admin">Admin</option>
</select>
) : (
<span className={`role-badge ${u.role}`}>{u.role}</span>
)}
</td> </td>
<td data-label="Stripe">{u.stripe_ready ? '✅' : '❌'}</td>
<td data-label="Status">{u.deleted_at ? <span className="status-deleted">Deleted</span> : <span className="status-active">Active</span>}</td> <td data-label="Status">{u.deleted_at ? <span className="status-deleted">Deleted</span> : <span className="status-active">Active</span>}</td>
<td data-label="Actions"> <td data-label="Actions">
{!u.deleted_at && <button className="btn-delete" onClick={() => deleteUser(u.id)}>Delete</button>} <div className="action-buttons">
<button className="btn-view" onClick={() => setSelectedItem({type: 'user', data: u})}>View</button>
{!u.deleted_at && <button className="btn-delete" onClick={() => deleteUser(u.id)}>Delete</button>}
</div>
</td> </td>
</tr> </tr>
))} ))}
@ -157,7 +230,7 @@ const AdminPanel = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{plugins.map(p => ( {filteredPlugins.map(p => (
<tr key={p.id} className={p.deleted_at ? 'row-deleted' : ''}> <tr key={p.id} className={p.deleted_at ? 'row-deleted' : ''}>
<td data-label="ID">{p.id}</td> <td data-label="ID">{p.id}</td>
<td data-label="Name">{p.name}</td> <td data-label="Name">{p.name}</td>
@ -166,7 +239,10 @@ const AdminPanel = () => {
<td data-label="Developer">{p.dev_name}</td> <td data-label="Developer">{p.dev_name}</td>
<td data-label="Status">{p.deleted_at ? <span className="status-deleted">Deleted</span> : <span className="status-active">Active</span>}</td> <td data-label="Status">{p.deleted_at ? <span className="status-deleted">Deleted</span> : <span className="status-active">Active</span>}</td>
<td data-label="Actions"> <td data-label="Actions">
{!p.deleted_at && <button className="btn-delete" onClick={() => deletePlugin(p.id)}>Delete</button>} <div className="action-buttons">
<button className="btn-view" onClick={() => setSelectedItem({type: 'plugin', data: p})}>View</button>
{!p.deleted_at && <button className="btn-delete" onClick={() => deletePlugin(p.id)}>Delete</button>}
</div>
</td> </td>
</tr> </tr>
))} ))}
@ -180,23 +256,74 @@ const AdminPanel = () => {
<th>Moderator</th> <th>Moderator</th>
<th>Action</th> <th>Action</th>
<th>Target</th> <th>Target</th>
<th>Reason</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{logs.map(l => ( {filteredLogs.map(l => (
<tr key={l.id}> <tr key={l.id}>
<td data-label="Date">{new Date(l.created_at).toLocaleString()}</td> <td data-label="Date">{new Date(l.created_at).toLocaleString()}</td>
<td data-label="Moderator">{l.moderator_name}</td> <td data-label="Moderator">{l.moderator_name}</td>
<td data-label="Action"><strong>{l.action.toUpperCase()}</strong></td> <td data-label="Action"><strong>{l.action.toUpperCase()}</strong></td>
<td data-label="Target">{l.target_type} ({l.target_id})</td> <td data-label="Target">{l.target_type} ({l.target_id})</td>
<td data-label="Reason">{l.reason || 'No reason provided'}</td> <td data-label="Actions">
<button className="btn-view" onClick={() => setSelectedItem({type: 'log', data: l})}>View</button>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
)} )}
</main> </main>
{selectedItem && (
<div className="admin-modal-overlay" onClick={() => setSelectedItem(null)}>
<div className="admin-modal" onClick={e => e.stopPropagation()}>
<header className="modal-header">
<h2>{selectedItem.type.toUpperCase()} Details</h2>
<button className="close-modal" onClick={() => setSelectedItem(null)}>&times;</button>
</header>
<div className="modal-body">
{selectedItem.type === 'user' && (
<div className="details-grid">
<div className="detail-item"><strong>ID:</strong> {selectedItem.data.id}</div>
<div className="detail-item"><strong>Username:</strong> {selectedItem.data.username}</div>
<div className="detail-item"><strong>Email:</strong> {selectedItem.data.email}</div>
<div className="detail-item"><strong>Role:</strong> {selectedItem.data.role}</div>
<div className="detail-item"><strong>Stripe Ready:</strong> {selectedItem.data.stripe_ready ? 'Yes' : 'No'}</div>
<div className="detail-item"><strong>2FA Enabled:</strong> {selectedItem.data.totp_enabled ? 'Yes' : 'No'}</div>
<div className="detail-item"><strong>Created:</strong> {new Date(selectedItem.data.created_at).toLocaleString()}</div>
<div className="detail-item"><strong>Status:</strong> {selectedItem.data.deleted_at ? `Deleted at ${new Date(selectedItem.data.deleted_at).toLocaleString()}` : 'Active'}</div>
</div>
)}
{selectedItem.type === 'plugin' && (
<div className="details-grid">
<div className="detail-item"><strong>ID:</strong> {selectedItem.data.id}</div>
<div className="detail-item"><strong>Name:</strong> {selectedItem.data.name}</div>
<div className="detail-item"><strong>Developer:</strong> {selectedItem.data.dev_name}</div>
<div className="detail-item"><strong>Type:</strong> {selectedItem.data.type}</div>
<div className="detail-item"><strong>Category:</strong> {selectedItem.data.category}</div>
<div className="detail-item"><strong>Price:</strong> €{(selectedItem.data.price_cents / 100).toFixed(2)}</div>
<div className="detail-item"><strong>Downloads:</strong> {selectedItem.data.downloads}</div>
<div className="detail-item"><strong>Views:</strong> {selectedItem.data.views}</div>
<div className="detail-item"><strong>Created:</strong> {new Date(selectedItem.data.created_at).toLocaleString()}</div>
<div className="detail-item"><strong>Status:</strong> {selectedItem.data.deleted_at ? `Deleted at ${new Date(selectedItem.data.deleted_at).toLocaleString()}` : 'Active'}</div>
</div>
)}
{selectedItem.type === 'log' && (
<div className="details-grid">
<div className="detail-item"><strong>Date:</strong> {new Date(selectedItem.data.created_at).toLocaleString()}</div>
<div className="detail-item"><strong>Moderator:</strong> {selectedItem.data.moderator_name}</div>
<div className="detail-item"><strong>Action:</strong> {selectedItem.data.action}</div>
<div className="detail-item"><strong>Target Type:</strong> {selectedItem.data.target_type}</div>
<div className="detail-item"><strong>Target ID:</strong> {selectedItem.data.target_id}</div>
<div className="detail-item full-width"><strong>Reason:</strong> {selectedItem.data.reason || 'No reason provided'}</div>
</div>
)}
</div>
</div>
</div>
)}
</div> </div>
); );
}; };