From 2f0bc4770138e36a6adf1866eed522e46e595c0f Mon Sep 17 00:00:00 2001 From: Sai4158 <116776995+Sai4158@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:52:43 -0400 Subject: [PATCH 1/4] Added UI changes, auto suggestions and custom alerts Changed UI and section layout latest users and the new users tab and page indexing auto suggestion for filling username and challenge management Custom alerts instead of window default alerts User interface and section layout have been updated. --- package-lock.json | 1281 +++++++++++++++++++++++++++- src/pages/moderation.jsx | 1698 +++++++++++++++++++++++++++++++------- yarn.lock | 941 +++++++++++++++++++-- 3 files changed, 3550 insertions(+), 370 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5be40a9a..ac204637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2066,6 +2066,614 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@firebase/ai": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.1.tgz", + "integrity": "sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g==", + "peer": true, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz", + "integrity": "sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.23.tgz", + "integrity": "sha512-3AdO10RN18G5AzREPoFgYhW6vWXr3u+OYQv6pl3CX6Fky8QRk0AHurZlY3Q1xkXO0TDxIsdhO3y65HF7PBOJDw==", + "peer": true, + "dependencies": { + "@firebase/analytics": "0.10.17", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", + "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "peer": true + }, + "node_modules/@firebase/app": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz", + "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.1.tgz", + "integrity": "sha512-MgNdlms9Qb0oSny87pwpjKush9qUwCJhfmTJHDfrcKo4neLGiSeVE4qJkzP7EQTIUFKp84pbTxobSAXkiuQVYQ==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.26.tgz", + "integrity": "sha512-PkX+XJMLDea6nmnopzFKlr+s2LMQGqdyT2DHdbx1v1dPSqOol2YzgpgymmhC67vitXVpNvS3m/AiWQWWhhRRPQ==", + "peer": true, + "dependencies": { + "@firebase/app-check": "0.10.1", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "peer": true + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", + "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "peer": true + }, + "node_modules/@firebase/app-compat": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz", + "integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==", + "peer": true, + "dependencies": { + "@firebase/app": "0.13.2", + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "peer": true + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.28.tgz", + "integrity": "sha512-HpMSo/cc6Y8IX7bkRIaPPqT//Jt83iWy5rmDWeThXQCAImstkdNo3giFLORJwrZw2ptiGkOij64EH1ztNJzc7Q==", + "peer": true, + "dependencies": { + "@firebase/auth": "1.10.8", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz", + "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "peer": true + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", + "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz", + "integrity": "sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ==", + "peer": true, + "dependencies": { + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.10.tgz", + "integrity": "sha512-VMVk7zxIkgwlVQIWHOKFahmleIjiVFwFOjmakXPd/LDgaB/5vzwsB5DWIYo+3KhGxWpidQlR8geCIn39YflJIQ==", + "peer": true, + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.20.tgz", + "integrity": "sha512-H9Rpj1pQ1yc9+4HQOotFGLxqAXwOzCHsRSRjcQFNOr8lhUt6LeYjf0NSRL04sc4X0dWe8DsCvYKxMYvFG/iOJw==", + "peer": true, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.11.tgz", + "integrity": "sha512-itEsHARSsYS95+udF/TtIzNeQ0Uhx4uIna0sk4E0wQJBUnLc/G1X6D7oRljoOuwwCezRLGvWBRyNrugv/esOEw==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/database": "1.0.20", + "@firebase/database-types": "1.0.15", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.15.tgz", + "integrity": "sha512-XWHJ0VUJ0k2E9HDMlKxlgy/ZuTa9EvHCGLjaKSUvrQnwhgZuRU5N3yX6SZ+ftf2hTzZmfRkv+b3QRvGg40bKNw==", + "peer": true, + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.12.1" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz", + "integrity": "sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "@firebase/webchannel-wrapper": "1.0.3", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.53", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.53.tgz", + "integrity": "sha512-qI3yZL8ljwAYWrTousWYbemay2YZa+udLWugjdjju2KODWtLG94DfO4NALJgPLv8CVGcDHNFXoyQexdRA0Cz8Q==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/firestore": "4.8.0", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", + "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.9.tgz", + "integrity": "sha512-FG95w6vjbUXN84Ehezc2SDjGmGq225UYbHrb/ptkRT7OTuCiQRErOQuyt1jI1tvcDekdNog+anIObihNFz79Lg==", + "peer": true, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.18", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.26.tgz", + "integrity": "sha512-A798/6ff5LcG2LTWqaGazbFYnjBW8zc65YfID/en83ALmkhu2b0G8ykvQnLtakbV9ajrMYPn7Yc/XcYsZIUsjA==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/functions": "0.12.9", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", + "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "peer": true + }, + "node_modules/@firebase/installations": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz", + "integrity": "sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.18.tgz", + "integrity": "sha512-aLFohRpJO5kKBL/XYL4tN+GdwEB/Q6Vo9eZOM/6Kic7asSUgmSfGPpGUZO1OAaSRGwF4Lqnvi1f/f9VZnKzChw==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", + "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.22", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz", + "integrity": "sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.12.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.22.tgz", + "integrity": "sha512-5ZHtRnj6YO6f/QPa/KU6gryjmX4Kg33Kn4gRpNU6M1K47Gm8kcQwPkX7erRUYEH1mIWptfvjvXMHWoZaWjkU7A==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/messaging": "0.12.22", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", + "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "peer": true + }, + "node_modules/@firebase/performance": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.7.tgz", + "integrity": "sha512-JTlTQNZKAd4+Q5sodpw6CN+6NmwbY72av3Lb6wUKTsL7rb3cuBIhQSrslWbVz0SwK3x0ZNcqX24qtRbwKiv+6w==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.20.tgz", + "integrity": "sha512-XkFK5NmOKCBuqOKWeRgBUFZZGz9SzdTZp4OqeUg+5nyjapTiZ4XoiiUL8z7mB2q+63rPmBl7msv682J3rcDXIQ==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/performance": "0.7.7", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", + "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "peer": true + }, + "node_modules/@firebase/remote-config": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.5.tgz", + "integrity": "sha512-fU0c8HY0vrVHwC+zQ/fpXSqHyDMuuuglV94VF6Yonhz8Fg2J+KOowPGANM0SZkLvVOYpTeWp3ZmM+F6NjwWLnw==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/installations": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.18.tgz", + "integrity": "sha512-YiETpldhDy7zUrnS8e+3l7cNs0sL7+tVAxvVYU0lu7O+qLHbmdtAxmgY+wJqWdW2c9nDvBFec7QiF58pEUu0qQ==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/remote-config": "0.6.5", + "@firebase/remote-config-types": "0.4.0", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", + "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==", + "peer": true + }, + "node_modules/@firebase/storage": { + "version": "0.13.14", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.14.tgz", + "integrity": "sha512-xTq5ixxORzx+bfqCpsh+o3fxOsGoDjC1nO0Mq2+KsOcny3l7beyBhP/y1u5T6mgsFQwI1j6oAkbT5cWdDBx87g==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.24.tgz", + "integrity": "sha512-XHn2tLniiP7BFKJaPZ0P8YQXKiVJX+bMyE2j2YWjYfaddqiJnROJYqSomwW6L3Y+gZAga35ONXUJQju6MB6SOQ==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/storage": "0.13.14", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", + "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz", + "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", + "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==", + "peer": true + }, "node_modules/@floating-ui/core": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", @@ -2167,6 +2775,37 @@ "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "peer": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "peer": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@headlessui/react": { "version": "1.7.15", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz", @@ -3031,6 +3670,70 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "peer": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "peer": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "peer": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "peer": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "peer": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "peer": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "peer": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "peer": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "peer": true + }, "node_modules/@puppeteer/browsers": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", @@ -3507,10 +4210,30 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -4032,6 +4755,152 @@ } } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, "node_modules/@webcontainer/api": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@webcontainer/api/-/api-1.5.3.tgz", @@ -4042,6 +4911,18 @@ "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==" }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "peer": true + }, "node_modules/accessor-fn": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", @@ -4051,9 +4932,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": { "acorn": "bin/acorn" }, @@ -4061,6 +4942,18 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.3.tgz", + "integrity": "sha512-jtKLnfoOzm28PazuQ4dVBcE9Jeo6ha1GAJvq3N0LlNOszmTfx+wSycBehn+FN0RnyeR77IBxN/qVYMw0Rlj0Xw==", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4965,6 +5858,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/chromium-bidi": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", @@ -6029,6 +6931,19 @@ "node": ">=10.0.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -6134,6 +7049,12 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "peer": true + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -6689,7 +7610,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -6736,6 +7656,15 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6842,6 +7771,18 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "peer": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -6941,6 +7882,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz", + "integrity": "sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w==", + "peer": true, + "dependencies": { + "@firebase/ai": "1.4.1", + "@firebase/analytics": "0.10.17", + "@firebase/analytics-compat": "0.2.23", + "@firebase/app": "0.13.2", + "@firebase/app-check": "0.10.1", + "@firebase/app-check-compat": "0.3.26", + "@firebase/app-compat": "0.4.2", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.10.8", + "@firebase/auth-compat": "0.5.28", + "@firebase/data-connect": "0.3.10", + "@firebase/database": "1.0.20", + "@firebase/database-compat": "2.0.11", + "@firebase/firestore": "4.8.0", + "@firebase/firestore-compat": "0.3.53", + "@firebase/functions": "0.12.9", + "@firebase/functions-compat": "0.3.26", + "@firebase/installations": "0.6.18", + "@firebase/installations-compat": "0.2.18", + "@firebase/messaging": "0.12.22", + "@firebase/messaging-compat": "0.2.22", + "@firebase/performance": "0.7.7", + "@firebase/performance-compat": "0.2.20", + "@firebase/remote-config": "0.6.5", + "@firebase/remote-config-compat": "0.2.18", + "@firebase/storage": "0.13.14", + "@firebase/storage-compat": "0.3.24", + "@firebase/util": "1.12.1" + } + }, + "node_modules/firebase/node_modules/@firebase/auth": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz", + "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==", + "peer": true, + "dependencies": { + "@firebase/component": "0.6.18", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -7293,6 +8294,12 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "peer": true + }, "node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -7772,6 +8779,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "peer": true + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -8580,6 +9593,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, "node_modules/loader-utils": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", @@ -8630,6 +9652,12 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "peer": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -8647,6 +9675,12 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "peer": true + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -9656,6 +10690,12 @@ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "peer": true + }, "node_modules/motion-dom": { "version": "11.18.1", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", @@ -9734,6 +10774,12 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "peer": true + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -10815,6 +11861,30 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -13275,6 +14345,15 @@ "node": ">=10.13.0" } }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-fs": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", @@ -13827,6 +14906,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/typo-js": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.3.tgz", @@ -14185,6 +15278,19 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -14194,12 +15300,173 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "peer": true + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/webpack": { + "version": "5.100.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.1.tgz", + "integrity": "sha512-YJB/ESPUe2Locd0NKXmw72Dx8fZQk1gTzI6rc9TAT4+Sypbnhl8jd8RywB1bDsDF9Dy1RUR7gn3q/ZJTd0OZZg==", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.2", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "peer": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "peer": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/src/pages/moderation.jsx b/src/pages/moderation.jsx index b4689c5e..a2f802fc 100644 --- a/src/pages/moderation.jsx +++ b/src/pages/moderation.jsx @@ -2,47 +2,360 @@ import React, { useEffect, useState } from 'react'; import { ArrowRightIcon, QuestionMarkCircleIcon, + ChevronLeftIcon, + ChevronRightIcon, + UserGroupIcon, + DocumentTextIcon, + ExclamationTriangleIcon, + ChartBarIcon, } from '@heroicons/react/20/solid'; import Head from 'next/head'; import { StandardNav } from '@/components/StandardNav'; import { Footer } from '@/components/Footer'; -import { PracticeNav } from '@/components/practice/PracticeNav'; -import { Community } from '@/components/practice/community'; import request from '@/utils/request'; -import { MyTable } from '@/components/Table'; -import { TableHead, TableRow, TableHeader, TableCell, TableBody, Table } from "@/components/ui/table" import ViewChallenge from '@/components/moderation/ViewChallenge'; import ViewReport from '@/components/moderation/ViewReport'; -export default function Competitions() { +export default function Moderation() { + // Challenge states const [selectedChallenges, setSelectedChallenges] = useState([]); const [selectedId, setSelectedId] = useState(null); const [pendingChallenges, setPendingChallenges] = useState([]); const [challengeIsOpen, setChallengeIsOpen] = useState(false); + const [bonusPoints, setBonusPoints] = useState(0); - + // Report states const [reports, setReports] = useState([]); - - - const [bonusPoints, setBonusPoints] = useState(0); const [selectedReport, setSelectedReport] = useState(null); const [reportIsOpen, setReportIsOpen] = useState(false); - - // get reports - useEffect(() => { - const fetchReports = async () => { - const response = await request(process.env.NEXT_PUBLIC_API_URL + '/reports', "GET"); - setReports(response); + // User management states + const [users, setUsers] = useState([]); + const [currentUserPage, setCurrentUserPage] = useState(0); + const [totalUserPages, setTotalUserPages] = useState(0); + const [totalUsers, setTotalUsers] = useState(0); + + // Recent activity states + const [recentComments, setRecentComments] = useState([]); + const [recentWriteups, setRecentWriteups] = useState([]); + const [recentLessons, setRecentLessons] = useState([]); + + // Platform stats + const [stats, setStats] = useState(null); + + // Active tab state + const [activeTab, setActiveTab] = useState('overview'); + + // Autocomplete states + const [userSuggestions, setUserSuggestions] = useState([]); + const [challengeSuggestions, setChallengeSuggestions] = useState([]); + const [showUserSuggestions, setShowUserSuggestions] = useState(false); + const [showChallengeSuggestions, setShowChallengeSuggestions] = + useState(false); + const [userSearchQuery, setUserSearchQuery] = useState(''); + const [challengeSearchQuery, setChallengeSearchQuery] = useState(''); + + // Notification states + const [notification, setNotification] = useState(null); + + // Notification helper function + const showNotification = (message, type = 'info') => { + const id = Date.now(); + setNotification({ id, message, type }); + }; + + // Notification component + const NotificationModal = () => { + if (!notification) return null; + + const getNotificationIcon = () => { + switch (notification.type) { + case 'success': + return 'fas fa-check-circle'; + case 'error': + return 'fas fa-exclamation-circle'; + case 'warning': + return 'fas fa-exclamation-triangle'; + default: + return 'fas fa-info-circle'; + } }; - fetchReports(); - }, []); - + + const icon = getNotificationIcon(); + + return ( +
+
setNotification(null)} + /> +
+
+
+ +
+
+

+ {notification.message} +

+
+ +
+
+
+ ); + }; + + // Fetch functions + const fetchReports = async () => { + try { + console.log('Fetching reports...'); + const response = await request( + process.env.NEXT_PUBLIC_API_URL + '/reports', + 'GET' + ); + console.log('Reports response:', response); + setReports(response || []); + } catch (error) { + console.error('Failed to fetch reports:', error); + setReports([]); + } + }; + + const fetchPendingChallenges = async () => { + try { + console.log('Fetching pending challenges...'); + const response = await request( + process.env.NEXT_PUBLIC_API_URL + '/pending', + 'GET' + ); + console.log('Pending challenges response:', response); + setPendingChallenges(response || []); + } catch (error) { + console.error('Failed to fetch pending challenges:', error); + setPendingChallenges([]); + } + }; + + const fetchUsers = async (page = 0) => { + try { + console.log('Fetching users for page:', page); + const url = `${process.env.NEXT_PUBLIC_API_URL}/admin/users?page=${page}&size=30&order=createdAt&ordertype=desc`; + console.log('Fetching from URL:', url); + + const response = await request(url, 'GET'); + console.log('Users API response:', response); + + if (response === null) { + console.error('Request returned null - likely authentication issue'); + setUsers([]); + setTotalUserPages(0); + setTotalUsers(0); + setCurrentUserPage(0); + return; + } + + if (response && response.result) { + console.log('Setting users data:', response.result.length, 'users'); + setUsers(response.result || []); + setTotalUserPages((response.lastPage || 0) + 1); + setTotalUsers(response.totalEntries || 0); + setCurrentUserPage(page); + } else { + console.error('Invalid response format:', response); + if (response && response.error) { + console.error('API Error:', response.error); + } + setUsers([]); + setTotalUserPages(0); + setTotalUsers(0); + setCurrentUserPage(0); + } + } catch (error) { + console.error('Failed to fetch users:', error); + setUsers([]); + setTotalUserPages(0); + setTotalUsers(0); + setCurrentUserPage(0); + } + }; + + const fetchRecentActivity = async () => { + try { + console.log('Fetching recent activity...'); + const [commentsRes, writeupsRes, lessonsRes] = await Promise.all([ + request(`${process.env.NEXT_PUBLIC_API_URL}/activity/comments`, 'GET'), + request(`${process.env.NEXT_PUBLIC_API_URL}/activity/writeups`, 'GET'), + request(`${process.env.NEXT_PUBLIC_API_URL}/activity/lessons`, 'GET'), + ]); + console.log('Activity responses:', { + commentsRes, + writeupsRes, + lessonsRes, + }); + setRecentComments(commentsRes || []); + setRecentWriteups(writeupsRes || []); + setRecentLessons(lessonsRes || []); + } catch (error) { + console.error('Failed to fetch recent activity:', error); + setRecentComments([]); + setRecentWriteups([]); + setRecentLessons([]); + } + }; + + const fetchStats = async () => { + try { + console.log('Fetching platform stats...'); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/stats`, + 'GET' + ); + console.log('Stats response:', response); + setStats(response); + } catch (error) { + console.error('Failed to fetch platform stats', error); + setStats(null); + } + }; + + // Autocomplete functions + const fetchUserSuggestions = async (query) => { + if (!query || query.length < 2) { + setUserSuggestions([]); + setShowUserSuggestions(false); + return; + } + + try { + const response = await request( + `${ + process.env.NEXT_PUBLIC_API_URL + }/admin/users?search=${encodeURIComponent(query)}&size=10`, + 'GET' + ); + + if (response && response.result) { + setUserSuggestions(response.result); + setShowUserSuggestions(true); + } else { + setUserSuggestions([]); + setShowUserSuggestions(false); + } + } catch (error) { + console.error('Failed to fetch user suggestions:', error); + setUserSuggestions([]); + setShowUserSuggestions(false); + } + }; + + const fetchChallengeSuggestions = async (query) => { + if (!query || query.length < 2) { + setChallengeSuggestions([]); + setShowChallengeSuggestions(false); + return; + } + + try { + // Try to fetch both pending and approved challenges + const [pendingRes, approvedRes] = await Promise.all([ + request(`${process.env.NEXT_PUBLIC_API_URL}/pending`, 'GET'), + request( + `${ + process.env.NEXT_PUBLIC_API_URL + }/challenges?search=${encodeURIComponent(query)}&limit=10`, + 'GET' + ), + ]); + + let suggestions = []; + + // Add pending challenges + if (pendingRes && Array.isArray(pendingRes)) { + const filteredPending = pendingRes.filter( + (challenge) => + challenge.title.toLowerCase().includes(query.toLowerCase()) || + challenge.id.toLowerCase().includes(query.toLowerCase()) + ); + suggestions = [...suggestions, ...filteredPending]; + } + + // Add approved challenges + if (approvedRes && Array.isArray(approvedRes)) { + const filteredApproved = approvedRes.filter( + (challenge) => + challenge.title.toLowerCase().includes(query.toLowerCase()) || + challenge.id.toLowerCase().includes(query.toLowerCase()) + ); + suggestions = [...suggestions, ...filteredApproved]; + } + + // Remove duplicates and limit to 10 + const uniqueSuggestions = suggestions + .filter( + (challenge, index, self) => + index === self.findIndex((c) => c.id === challenge.id) + ) + .slice(0, 10); + + setChallengeSuggestions(uniqueSuggestions); + setShowChallengeSuggestions(uniqueSuggestions.length > 0); + } catch (error) { + console.error('Failed to fetch challenge suggestions:', error); + setChallengeSuggestions([]); + setShowChallengeSuggestions(false); + } + }; + + // Handle autocomplete selection + const handleUserSelection = (user) => { + setUserSearchQuery(user.username); + document.getElementById('usernameInput').value = user.username; + setShowUserSuggestions(false); + }; + + const handleChallengeSelection = (challenge) => { + setChallengeSearchQuery(challenge.id); + document.getElementById('challengeIdInput').value = challenge.id; + setShowChallengeSuggestions(false); + }; + + // Handle input changes + const handleUserInputChange = (e) => { + const value = e.target.value; + setUserSearchQuery(value); + + if (value.length === 0) { + setUserSuggestions([]); + setShowUserSuggestions(false); + } else { + fetchUserSuggestions(value); + } + }; + + const handleChallengeInputChange = (e) => { + const value = e.target.value; + setChallengeSearchQuery(value); + + if (value.length === 0) { + setChallengeSuggestions([]); + setShowChallengeSuggestions(false); + } else { + fetchChallengeSuggestions(value); + } + }; + + // Handle functions const handleSelectChallenge = (id) => { - setSelectedChallenges(prev => { + setSelectedChallenges((prev) => { if (prev.includes(id)) { - return prev.filter(item => item !== id); + return prev.filter((item) => item !== id); } else { return [...prev, id]; } @@ -50,26 +363,30 @@ export default function Competitions() { }; const resetFields = () => { - document.getElementById('usernameInput').value = ""; - document.getElementById('reasonInput').value = ""; + document.getElementById('usernameInput').value = ''; + document.getElementById('reasonInput').value = ''; + setUserSearchQuery(''); + setShowUserSuggestions(false); + setUserSuggestions([]); }; - const fetchPendingChallenges = async () => { - try { - const response = await request(process.env.NEXT_PUBLIC_API_URL + '/pending', "GET"); - console.log(response) - setPendingChallenges(response); - } catch (error) { - console.error(error); - } + const resetChallengeFields = () => { + document.getElementById('challengeIdInput').value = ''; + document.getElementById('challengeReasonInput').value = ''; + setChallengeSearchQuery(''); + setShowChallengeSuggestions(false); + setChallengeSuggestions([]); }; const handleApproveChallenge = async () => { try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/challenges/${selectedId}/approve`, "POST", { bonusPoints }); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/challenges/${selectedId}/approve`, + 'POST', + { bonusPoints } + ); console.log(response); - // Handle success or error response - fetchPendingChallenges(); // Refresh pending challenges + fetchPendingChallenges(); } catch (error) { console.error(error); } @@ -79,138 +396,166 @@ export default function Competitions() { const challengeId = document.getElementById('challengeIdInput').value; const reason = document.getElementById('challengeReasonInput').value; - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${challengeId}/deleteChallenge`, "POST", {reason}); - if(response.success){ - alert("Challenge deleted successfully!"); - }else { - alert("Failed to delete challenge."); + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${challengeId}/deleteChallenge`, + 'POST', + { reason } + ); + if (response.success) { + showNotification('Challenge deleted successfully!', 'success'); + } else { + showNotification('Failed to delete challenge.', 'error'); } - document.getElementById('challengeIdInput').value = ""; - document.getElementById('challengeReasonInput').value = ""; - - - }catch(err){ + resetChallengeFields(); + } catch (err) { console.log(err); - alert("An error occurred while deleting the challenge."); + showNotification( + 'An error occurred while deleting the challenge.', + 'error' + ); } - }; const handleUnapproveChallenge = async () => { const challengeId = document.getElementById('challengeIdInput').value; const reason = document.getElementById('challengeReasonInput').value; - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${challengeId}/unapproveChallenge`, "POST", {reason}); - if(response.success){ - alert("Challenge unapproved successfully!"); - }else { - alert("Failed to unapprove challenge."); + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${challengeId}/unapproveChallenge`, + 'POST', + { reason } + ); + if (response.success) { + showNotification('Challenge unapproved successfully!', 'success'); + } else { + showNotification('Failed to unapprove challenge.', 'error'); } - document.getElementById('challengeIdInput').value = ""; - document.getElementById('challengeReasonInput').value = ""; - - - }catch(err){ + resetChallengeFields(); + } catch (err) { console.log(err); - alert("An error occurred while unapproving the challenge."); + showNotification( + 'An error occurred while unapproving the challenge.', + 'error' + ); } - }; const handleResyncLeaderboard = async () => { - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/syncLeaderboard`, "POST"); - if(response.success){ - alert("Leaderboard resynced successfully!"); - }else { - alert("Failed to resync leaderboard."); + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/syncLeaderboard`, + 'POST' + ); + if (response.success) { + showNotification('Leaderboard resynced successfully!', 'success'); + } else { + showNotification('Failed to resync leaderboard.', 'error'); } - }catch(err){ + } catch (err) { console.log(err); - alert("An error occurred while resyncing the leaderboard."); + showNotification( + 'An error occurred while resyncing the leaderboard.', + 'error' + ); } }; - useEffect(() => { - fetchPendingChallenges(); - }, []); - const handleResetPFP = async () => { const username = document.getElementById('usernameInput').value; const reason = document.getElementById('reasonInput').value; - console.log('REASON: ', reason); if (!username) { - alert("Please enter a username."); + showNotification('Please enter a username.', 'warning'); return; } try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetPFP`, "POST", { reason }); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetPFP`, + 'POST', + { reason } + ); if (response.success) { - alert("Profile picture reset successfully!"); + showNotification('Profile picture reset successfully!', 'success'); } else { - alert("Failed to reset profile picture."); + showNotification('Failed to reset profile picture.', 'error'); } - resetFields(); - } catch (error) { console.error(error); - alert("An error occurred while resetting the profile picture."); + showNotification( + 'An error occurred while resetting the profile picture.', + 'error' + ); } }; const deleteBulk = async () => { if (selectedChallenges.length === 0) { - alert("No challenges selected."); + showNotification('No challenges selected.', 'warning'); return; } - - if (!confirm("Are you sure you want to delete the selected challenges?")) { + + if (!confirm('Are you sure you want to delete the selected challenges?')) { return; } + try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/deleteChallenges`, "POST", { challengeIds: selectedChallenges }); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/deleteChallenges`, + 'POST', + { challengeIds: selectedChallenges } + ); if (response.success) { - alert("Selected challenges deleted successfully!"); - setSelectedChallenges([]); // Clear selected challenges - fetchPendingChallenges(); // Refresh pending challenges + showNotification( + 'Selected challenges deleted successfully!', + 'success' + ); + setSelectedChallenges([]); + fetchPendingChallenges(); } else { - alert("Failed to delete selected challenges."); + showNotification('Failed to delete selected challenges.', 'error'); } } catch (error) { console.error(error); - alert("An error occurred while deleting the selected challenges."); + showNotification( + 'An error occurred while deleting the selected challenges.', + 'error' + ); } }; - const handleResetBanner = async () => { const username = document.getElementById('usernameInput').value; const reason = document.getElementById('reasonInput').value; - if(!username) { - alert("Please enter a username."); + if (!username) { + showNotification('Please enter a username.', 'warning'); return; } - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetBanner`, "POST", {reason}); - if(response.success){ - alert("Banner reset successfully!"); - }else { - alert("Failed to reset banner."); - } + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetBanner`, + 'POST', + { reason } + ); + if (response.success) { + showNotification('Banner reset successfully!', 'success'); + } else { + showNotification('Failed to reset banner.', 'error'); + } resetFields(); - - }catch(err){ + } catch (err) { console.log(err); - alert("An error occurred while resetting the banner."); + showNotification( + 'An error occurred while resetting the banner.', + 'error' + ); } }; @@ -219,46 +564,55 @@ export default function Competitions() { const reason = document.getElementById('reasonInput').value; if (!username) { - alert("Please enter a username."); + showNotification('Please enter a username.', 'warning'); return; } try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/disableAccount`, "POST", { reason }); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/disableAccount`, + 'POST', + { reason } + ); if (response.success) { - alert("Account disabled successfully!"); + showNotification('Account disabled successfully!', 'success'); } else { - alert("Failed to disable account."); + showNotification('Failed to disable account.', 'error'); } - resetFields(); - - }catch(err){ + } catch (err) { console.log(err); - alert("An error occurred while disabling the account."); + showNotification( + 'An error occurred while disabling the account.', + 'error' + ); } }; - const handleResetBio = async () => { + const handleResetBio = async () => { const username = document.getElementById('usernameInput').value; const reason = document.getElementById('reasonInput').value; if (!username) { - alert("Please enter a username."); + showNotification('Please enter a username.', 'warning'); return; } - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetBio`, "POST", {reason}); - if(response.success){ - alert("Bio reset successfully!"); - }else{ - alert("Failed to reset bio."); - } + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetBio`, + 'POST', + { reason } + ); + if (response.success) { + showNotification('Bio reset successfully!', 'success'); + } else { + showNotification('Failed to reset bio.', 'error'); + } resetFields(); - }catch(err){ + } catch (err) { console.log(err); - alert("An error occurred while resetting the bio."); + showNotification('An error occurred while resetting the bio.', 'error'); } }; @@ -267,21 +621,28 @@ export default function Competitions() { const reason = document.getElementById('reasonInput').value; if (!username) { - alert("Please enter a username."); + showNotification('Please enter a username.', 'warning'); return; } - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/enableAccount`, "POST", {reason}); - if(response.success){ - alert("Account enabled successfully!"); - }else { - alert("Failed to enable account."); - } + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/enableAccount`, + 'POST', + { reason } + ); + if (response.success) { + showNotification('Account enabled successfully!', 'success'); + } else { + showNotification('Failed to enable account.', 'error'); + } resetFields(); - }catch(err){ + } catch (err) { console.log(err); - alert("An error occurred while enabling the account."); + showNotification( + 'An error occurred while enabling the account.', + 'error' + ); } }; @@ -290,233 +651,958 @@ export default function Competitions() { const reason = document.getElementById('reasonInput').value; if (!username) { - alert("Please enter a username."); + showNotification('Please enter a username.', 'warning'); return; } - try{ - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/warnUser`, "POST", {reason}); - if(response.success){ - alert("User warned successfully!"); - }else { - alert("Failed to warn user."); - } + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/warnUser`, + 'POST', + { reason } + ); + if (response.success) { + showNotification('User warned successfully!', 'success'); + } else { + showNotification('Failed to warn user.', 'error'); + } resetFields(); - }catch(err){ + } catch (err) { console.log(err); - alert("An error occurred while warning the user."); + showNotification('An error occurred while warning the user.', 'error'); } }; - // Fetch platform stats - const [stats, setStats] = useState(null); + // User pagination handlers + const handleNextUserPage = () => { + if (currentUserPage < totalUserPages - 1) { + fetchUsers(currentUserPage + 1); + } + }; + + const handlePrevUserPage = () => { + if (currentUserPage > 0) { + fetchUsers(currentUserPage - 1); + } + }; + // Initial data fetch useEffect(() => { - const fetchStats = async () => { + console.log('=== MODERATION PANEL INITIALIZING ==='); + console.log('API URL:', process.env.NEXT_PUBLIC_API_URL); + console.log('Starting data fetch sequence...'); + + const fetchAllData = async () => { try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/stats`, "GET"); - setStats(response); + console.log('1. Fetching reports...'); + await fetchReports(); + + console.log('2. Fetching pending challenges...'); + await fetchPendingChallenges(); + + console.log('3. Fetching users...'); + await fetchUsers(); + + console.log('4. Fetching recent activity...'); + await fetchRecentActivity(); + + console.log('5. Fetching stats...'); + await fetchStats(); + + console.log('=== ALL DATA FETCH COMPLETE ==='); } catch (error) { - console.error("Failed to fetch platform stats", error); + console.error('Error in data fetch sequence:', error); } }; - fetchStats(); + + fetchAllData(); }, []); + // Handle clicking outside autocomplete dropdowns + useEffect(() => { + const handleClickOutside = (event) => { + const userInput = document.getElementById('usernameInput'); + const challengeInput = document.getElementById('challengeIdInput'); + + if ( + userInput && + !userInput.contains(event.target) && + !event.target.closest('.user-suggestions') + ) { + setShowUserSuggestions(false); + } + + if ( + challengeInput && + !challengeInput.contains(event.target) && + !event.target.closest('.challenge-suggestions') + ) { + setShowChallengeSuggestions(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + // Tab navigation component + const TabButton = ({ id, label, icon: Icon, count }) => ( + + ); + return ( <> CTFGuide Moderation Panel - + -
+
-
-
-
+
+
+ {/* Header */} +
-

ADMIN CENTER

-

ΥΠΕΡΑΣΠΙΣΗ ΤΟΥ CTFGUIDE ΑΠΟ ΤΟ OPS

+

+ Admin Center +

+

+ ΥΠΕΡΑΣΠΙΣΗ ΤΟΥ CTFGUIDE ΑΠΟ ΤΟ OPS +

-
- - - +
+
-
- - -

-
- -
- -

USER ACTIONS

- - -
- - - - - - +
-
- -
- - -
-

CHALLENGE ACTIONS

- - - - - -
-
-
-
-

Challenges Pending Approval

- {selectedChallenges.length > 0 && } -
- {pendingChallenges && pendingChallenges.length > 0 ? ( - pendingChallenges.map((challenge) => ( -
{setSelectedId(challenge.id); setChallengeIsOpen(true);}} className='bg-neutral-800 w-full mb-2 border focus:bg-blue-900 focus:border-blue-500 border-neutral-700 hover:bg-neutral-700/50 cursor-pointer px-2 py-1 flex items-center text-sm'> + {/* Tab Navigation */} +
+ + + + + +
+ + {/* Tab Content */} + {activeTab === 'overview' && ( +
+ {/* Platform Stats */} +
+

+ Platform Statistics +

+ {stats ? ( +
+
+

+ {stats.userCount} +

+

Total Users

+
+
+

+ {stats.challengeCount} +

+

Total Challenges

+
+
+

+ {stats.verifiedChallengeCount} +

+

Verified Challenges

+
+
+ ) : ( +
+

Failed to load statistics

+
+ )} + + {/* Recent Sign-Ups Section */} + {stats && + stats.recentSignUps && + stats.recentSignUps.length > 0 && ( +
+
+

+ Recent Sign-Ups +

+
+

+ {stats.recentSignUpsCount24h || 0} users joined in + last 24h +

+

+ Showing latest{' '} + {Math.min(6, stats.recentSignUps.length)} users +

+
+
+
+ {stats.recentSignUps.slice(0, 6).map((user) => ( +
+ window.open(`/users/${user.username}`, '_blank') + } + > +
+ {`${user.username}'s +
+

+ {user.username} +

+

+ {new Date( + user.createdAt + ).toLocaleDateString()} +

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

User Management

+
+ +
+
+
+ +
e.stopPropagation()} // Prevent click event propagation - type="checkbox" - checked={selectedChallenges.includes(challenge.id)} - onChange={() => handleSelectChallenge(challenge.id)} - className="" + type="text" + placeholder="Enter username to manage" + className="w-full rounded-xl border-0 bg-neutral-700/80 py-3 pl-12 pr-4 text-white placeholder-gray-400 shadow-inner transition-all duration-200 focus:bg-neutral-700 focus:ring-2 focus:ring-blue-500/50 focus:ring-offset-0" + id="usernameInput" + value={userSearchQuery} + onChange={handleUserInputChange} + onFocus={() => { + if (userSuggestions.length > 0) { + setShowUserSuggestions(true); + } + }} + onBlur={() => { + // Delay hiding suggestions to allow clicking + setTimeout( + () => setShowUserSuggestions(false), + 200 + ); + }} + autoComplete="off" /> -
-

{challenge.title}

-
-
-

Submitted by {challenge.creator}

-

{new Date(challenge.createdAt).toLocaleString([], { hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric' })}

+ + {/* User Suggestions Dropdown */} + {showUserSuggestions && userSuggestions.length > 0 && ( +
+ {userSuggestions.map((user) => ( +
handleUserSelection(user)} + > + {`${user.username}'s +
+

+ {user.username} +

+

+ {user.points || 0} points • Joined{' '} + {new Date( + user.createdAt + ).toLocaleDateString()} +

+
+ {user.disabled && ( + + Disabled + + )} +
+ ))} +
+ )} +
+ +
+
+ +
+ + )} +
+
+ )} + {activeTab === 'activity' && ( +
+ {/* Recent Comments */} +
+

+ Recent Comments +

+
+ {recentComments && recentComments.length > 0 ? ( + recentComments.map((comment) => ( +
+
+
+

+ {comment.content} +

+

+ by{' '} + + {comment.user?.username || 'Unknown'} + + {comment.challenge && ( + + {' '} + on{' '} + + {comment.challenge.title} + + + )} +

+
+

+ {new Date(comment.createdAt).toLocaleString()} +

+
+
+ )) + ) : ( +

+ No recent comments +

+ )} +
+
+ {/* Recent Writeups */} +
+

+ Recent Writeups +

+
+ {recentWriteups && recentWriteups.length > 0 ? ( + recentWriteups.map((writeup) => ( +
+
+
+

+ {writeup.title} +

+

+ by{' '} + + {writeup.user?.username || 'Unknown'} + + {writeup.challenge && ( + + {' '} + for{' '} + + {writeup.challenge.title} + + + )} +

+
+

+ {new Date(writeup.createdAt).toLocaleString()} +

+
+
+ )) + ) : ( +

+ No recent writeups +

+ )} +
+
+ {/* Recent Lessons */} +
+

+ Recent Lessons +

+
+ {recentLessons && recentLessons.length > 0 ? ( + recentLessons.map((lesson) => ( +
+
+
+

+ {lesson.title} +

+

+ {lesson.description} +

+

+ by{' '} + + {lesson.author || 'Unknown'} + +

+
+

+ {new Date(lesson.createdAt).toLocaleString()} +

+
+
+ )) + ) : ( +

+ No recent lessons +

+ )} +
+
+
+ )}
-
- - + + +
+ + {/* Notification Modal */} +
); -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index afc2d4f6..654382c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,7 +43,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz" integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== -"@babel/core@^7.11.1", "@babel/core@^7.22.9": +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.1", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.22.9", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0": version "7.26.10" resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz" integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== @@ -1128,7 +1128,7 @@ "@codemirror/view" "^6.0.0" "@lezer/highlight" "^1.0.0" -"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.6.0": +"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.6.0", "@codemirror/view@>=6.0.0": version "6.36.5" resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz" integrity sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg== @@ -1170,7 +1170,7 @@ resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== -"@emotion/is-prop-valid@^1.2.1", "@emotion/is-prop-valid@1.2.2": +"@emotion/is-prop-valid@*", "@emotion/is-prop-valid@^1.2.1", "@emotion/is-prop-valid@1.2.2": version "1.2.2" resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz" integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== @@ -1187,7 +1187,7 @@ resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@emotion/react@^11.13.3": +"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.13.3", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.9.0": version "11.14.0" resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz" integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== @@ -1217,7 +1217,7 @@ resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== -"@emotion/styled@^11.11.0": +"@emotion/styled@^11.11.0", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1": version "11.11.0" resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz" integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== @@ -1269,6 +1269,397 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@firebase/ai@1.4.1": + version "1.4.1" + resolved "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.1.tgz" + integrity sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/analytics-compat@0.2.23": + version "0.2.23" + resolved "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.23.tgz" + integrity sha512-3AdO10RN18G5AzREPoFgYhW6vWXr3u+OYQv6pl3CX6Fky8QRk0AHurZlY3Q1xkXO0TDxIsdhO3y65HF7PBOJDw== + dependencies: + "@firebase/analytics" "0.10.17" + "@firebase/analytics-types" "0.8.3" + "@firebase/component" "0.6.18" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/analytics-types@0.8.3": + version "0.8.3" + resolved "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz" + integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg== + +"@firebase/analytics@0.10.17": + version "0.10.17" + resolved "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz" + integrity sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/installations" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/app-check-compat@0.3.26": + version "0.3.26" + resolved "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.26.tgz" + integrity sha512-PkX+XJMLDea6nmnopzFKlr+s2LMQGqdyT2DHdbx1v1dPSqOol2YzgpgymmhC67vitXVpNvS3m/AiWQWWhhRRPQ== + dependencies: + "@firebase/app-check" "0.10.1" + "@firebase/app-check-types" "0.5.3" + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/app-check-interop-types@0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz" + integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A== + +"@firebase/app-check-types@0.5.3": + version "0.5.3" + resolved "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz" + integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng== + +"@firebase/app-check@0.10.1": + version "0.10.1" + resolved "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.1.tgz" + integrity sha512-MgNdlms9Qb0oSny87pwpjKush9qUwCJhfmTJHDfrcKo4neLGiSeVE4qJkzP7EQTIUFKp84pbTxobSAXkiuQVYQ== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/app-compat@0.4.2", "@firebase/app-compat@0.x": + version "0.4.2" + resolved "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz" + integrity sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q== + dependencies: + "@firebase/app" "0.13.2" + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/app-types@0.9.3", "@firebase/app-types@0.x": + version "0.9.3" + resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz" + integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw== + +"@firebase/app@0.13.2", "@firebase/app@0.x": + version "0.13.2" + resolved "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz" + integrity sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/auth-compat@0.5.28": + version "0.5.28" + resolved "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.28.tgz" + integrity sha512-HpMSo/cc6Y8IX7bkRIaPPqT//Jt83iWy5rmDWeThXQCAImstkdNo3giFLORJwrZw2ptiGkOij64EH1ztNJzc7Q== + dependencies: + "@firebase/auth" "1.10.8" + "@firebase/auth-types" "0.13.0" + "@firebase/component" "0.6.18" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/auth-interop-types@0.2.4": + version "0.2.4" + resolved "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz" + integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA== + +"@firebase/auth-types@0.13.0": + version "0.13.0" + resolved "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz" + integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg== + +"@firebase/auth@1.10.8": + version "1.10.8" + resolved "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz" + integrity sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/component@0.6.18": + version "0.6.18" + resolved "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz" + integrity sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ== + dependencies: + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/data-connect@0.3.10": + version "0.3.10" + resolved "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.10.tgz" + integrity sha512-VMVk7zxIkgwlVQIWHOKFahmleIjiVFwFOjmakXPd/LDgaB/5vzwsB5DWIYo+3KhGxWpidQlR8geCIn39YflJIQ== + dependencies: + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/database-compat@2.0.11": + version "2.0.11" + resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.11.tgz" + integrity sha512-itEsHARSsYS95+udF/TtIzNeQ0Uhx4uIna0sk4E0wQJBUnLc/G1X6D7oRljoOuwwCezRLGvWBRyNrugv/esOEw== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/database" "1.0.20" + "@firebase/database-types" "1.0.15" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/database-types@1.0.15": + version "1.0.15" + resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.15.tgz" + integrity sha512-XWHJ0VUJ0k2E9HDMlKxlgy/ZuTa9EvHCGLjaKSUvrQnwhgZuRU5N3yX6SZ+ftf2hTzZmfRkv+b3QRvGg40bKNw== + dependencies: + "@firebase/app-types" "0.9.3" + "@firebase/util" "1.12.1" + +"@firebase/database@1.0.20": + version "1.0.20" + resolved "https://registry.npmjs.org/@firebase/database/-/database-1.0.20.tgz" + integrity sha512-H9Rpj1pQ1yc9+4HQOotFGLxqAXwOzCHsRSRjcQFNOr8lhUt6LeYjf0NSRL04sc4X0dWe8DsCvYKxMYvFG/iOJw== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + faye-websocket "0.11.4" + tslib "^2.1.0" + +"@firebase/firestore-compat@0.3.53": + version "0.3.53" + resolved "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.53.tgz" + integrity sha512-qI3yZL8ljwAYWrTousWYbemay2YZa+udLWugjdjju2KODWtLG94DfO4NALJgPLv8CVGcDHNFXoyQexdRA0Cz8Q== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/firestore" "4.8.0" + "@firebase/firestore-types" "3.0.3" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/firestore-types@3.0.3": + version "3.0.3" + resolved "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz" + integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== + +"@firebase/firestore@4.8.0": + version "4.8.0" + resolved "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz" + integrity sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + "@firebase/webchannel-wrapper" "1.0.3" + "@grpc/grpc-js" "~1.9.0" + "@grpc/proto-loader" "^0.7.8" + tslib "^2.1.0" + +"@firebase/functions-compat@0.3.26": + version "0.3.26" + resolved "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.26.tgz" + integrity sha512-A798/6ff5LcG2LTWqaGazbFYnjBW8zc65YfID/en83ALmkhu2b0G8ykvQnLtakbV9ajrMYPn7Yc/XcYsZIUsjA== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/functions" "0.12.9" + "@firebase/functions-types" "0.6.3" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/functions-types@0.6.3": + version "0.6.3" + resolved "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz" + integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== + +"@firebase/functions@0.12.9": + version "0.12.9" + resolved "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.9.tgz" + integrity sha512-FG95w6vjbUXN84Ehezc2SDjGmGq225UYbHrb/ptkRT7OTuCiQRErOQuyt1jI1tvcDekdNog+anIObihNFz79Lg== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.6.18" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/installations-compat@0.2.18": + version "0.2.18" + resolved "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.18.tgz" + integrity sha512-aLFohRpJO5kKBL/XYL4tN+GdwEB/Q6Vo9eZOM/6Kic7asSUgmSfGPpGUZO1OAaSRGwF4Lqnvi1f/f9VZnKzChw== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/installations" "0.6.18" + "@firebase/installations-types" "0.5.3" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/installations-types@0.5.3": + version "0.5.3" + resolved "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz" + integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA== + +"@firebase/installations@0.6.18": + version "0.6.18" + resolved "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz" + integrity sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/util" "1.12.1" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/logger@0.4.4": + version "0.4.4" + resolved "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz" + integrity sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g== + dependencies: + tslib "^2.1.0" + +"@firebase/messaging-compat@0.2.22": + version "0.2.22" + resolved "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.22.tgz" + integrity sha512-5ZHtRnj6YO6f/QPa/KU6gryjmX4Kg33Kn4gRpNU6M1K47Gm8kcQwPkX7erRUYEH1mIWptfvjvXMHWoZaWjkU7A== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/messaging" "0.12.22" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/messaging-interop-types@0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz" + integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q== + +"@firebase/messaging@0.12.22": + version "0.12.22" + resolved "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz" + integrity sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/installations" "0.6.18" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.12.1" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.20": + version "0.2.20" + resolved "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.20.tgz" + integrity sha512-XkFK5NmOKCBuqOKWeRgBUFZZGz9SzdTZp4OqeUg+5nyjapTiZ4XoiiUL8z7mB2q+63rPmBl7msv682J3rcDXIQ== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/performance" "0.7.7" + "@firebase/performance-types" "0.2.3" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/performance-types@0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz" + integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== + +"@firebase/performance@0.7.7": + version "0.7.7" + resolved "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.7.tgz" + integrity sha512-JTlTQNZKAd4+Q5sodpw6CN+6NmwbY72av3Lb6wUKTsL7rb3cuBIhQSrslWbVz0SwK3x0ZNcqX24qtRbwKiv+6w== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/installations" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + web-vitals "^4.2.4" + +"@firebase/remote-config-compat@0.2.18": + version "0.2.18" + resolved "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.18.tgz" + integrity sha512-YiETpldhDy7zUrnS8e+3l7cNs0sL7+tVAxvVYU0lu7O+qLHbmdtAxmgY+wJqWdW2c9nDvBFec7QiF58pEUu0qQ== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/remote-config" "0.6.5" + "@firebase/remote-config-types" "0.4.0" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/remote-config-types@0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz" + integrity sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg== + +"@firebase/remote-config@0.6.5": + version "0.6.5" + resolved "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.5.tgz" + integrity sha512-fU0c8HY0vrVHwC+zQ/fpXSqHyDMuuuglV94VF6Yonhz8Fg2J+KOowPGANM0SZkLvVOYpTeWp3ZmM+F6NjwWLnw== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/installations" "0.6.18" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/storage-compat@0.3.24": + version "0.3.24" + resolved "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.24.tgz" + integrity sha512-XHn2tLniiP7BFKJaPZ0P8YQXKiVJX+bMyE2j2YWjYfaddqiJnROJYqSomwW6L3Y+gZAga35ONXUJQju6MB6SOQ== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/storage" "0.13.14" + "@firebase/storage-types" "0.8.3" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/storage-types@0.8.3": + version "0.8.3" + resolved "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz" + integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg== + +"@firebase/storage@0.13.14": + version "0.13.14" + resolved "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.14.tgz" + integrity sha512-xTq5ixxORzx+bfqCpsh+o3fxOsGoDjC1nO0Mq2+KsOcny3l7beyBhP/y1u5T6mgsFQwI1j6oAkbT5cWdDBx87g== + dependencies: + "@firebase/component" "0.6.18" + "@firebase/util" "1.12.1" + tslib "^2.1.0" + +"@firebase/util@1.12.1", "@firebase/util@1.x": + version "1.12.1" + resolved "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz" + integrity sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w== + dependencies: + tslib "^2.1.0" + +"@firebase/webchannel-wrapper@1.0.3": + version "1.0.3" + resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz" + integrity sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ== + "@floating-ui/core@^1.6.0": version "1.6.9" resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz" @@ -1301,7 +1692,7 @@ resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz" integrity sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ== -"@fortawesome/fontawesome-svg-core@^6.3.0": +"@fortawesome/fontawesome-svg-core@^6.3.0", "@fortawesome/fontawesome-svg-core@~1 || ~6": version "6.4.0" resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz" integrity sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw== @@ -1339,6 +1730,24 @@ resolved "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz" integrity sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw== +"@grpc/grpc-js@~1.9.0": + version "1.9.15" + resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz" + integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ== + dependencies: + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.8": + version "0.7.15" + resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz" + integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.5" + yargs "^17.7.2" + "@headlessui/react@^1.7.2": version "1.7.15" resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz" @@ -1596,7 +2005,7 @@ resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.17.1.tgz" integrity sha512-OcZj+cs6EfUD39IoPBOgN61zf1XFVY+imsGoBDwXeSq2UHJZE3N59zzBOVjclck91Ne3e9gudONOeILvHCIhUA== -"@mui/material@^5.0.0", "@mui/material@^5.15.2": +"@mui/material@^5.0.0", "@mui/material@^5.15.2", "@mui/material@^5.4.1": version "5.17.1" resolved "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz" integrity sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw== @@ -1633,7 +2042,7 @@ csstype "^3.1.3" prop-types "^15.8.1" -"@mui/system@^5.17.1": +"@mui/system@^5.17.1", "@mui/system@^5.4.1": version "5.17.1" resolved "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz" integrity sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg== @@ -1734,6 +2143,59 @@ resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@puppeteer/browsers@2.3.0": version "2.3.0" resolved "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz" @@ -2024,6 +2486,22 @@ dependencies: "@types/ms" "*" +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + "@types/estree-jsx@^1.0.0": version "1.0.5" resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz" @@ -2031,10 +2509,10 @@ dependencies: "@types/estree" "*" -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.7" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz" - integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.8": + version "1.0.8" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@types/estree@0.0.39": version "0.0.39" @@ -2068,7 +2546,7 @@ dependencies: "@types/unist" "*" -"@types/hoist-non-react-statics@^3.3.0": +"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@>= 3.3.1": version "3.3.6" resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz" integrity sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw== @@ -2081,7 +2559,7 @@ resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz" integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2113,7 +2591,7 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz" integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@types/node@*", "@types/node@>=8.1.0": +"@types/node@*", "@types/node@>= 12", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0": version "20.4.2" resolved "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== @@ -2148,7 +2626,7 @@ resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz" integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== -"@types/react@*": +"@types/react@*", "@types/react@^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react@>= 16", "@types/react@>=0.0.0 <=99", "@types/react@>=18": version "18.2.15" resolved "https://registry.npmjs.org/@types/react/-/react-18.2.15.tgz" integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA== @@ -2348,6 +2826,127 @@ resolved "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz" integrity sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g== +"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@^1.14.1", "@webassemblyjs/wasm-parser@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + "@webcontainer/api@^1.5.1-internal.5": version "1.5.3" resolved "https://registry.npmjs.org/@webcontainer/api/-/api-1.5.3.tgz" @@ -2358,20 +2957,35 @@ resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz" integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + accessor-fn@1: version "1.5.3" resolved "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz" integrity sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA== +acorn-import-phases@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.3.tgz" + integrity sha512-jtKLnfoOzm28PazuQ4dVBcE9Jeo6ha1GAJvq3N0LlNOszmTfx+wSycBehn+FN0RnyeR77IBxN/qVYMw0Rlj0Xw== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.8.2, acorn@^8.9.0: - version "8.14.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0, acorn@^8.15.0, acorn@^8.8.2, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.3" @@ -2397,7 +3011,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.12.4, ajv@^6.9.1: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2417,7 +3031,7 @@ ajv@^8.0.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ajv@^8.6.0: +ajv@^8.6.0, ajv@>=8: version "8.17.1" resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -2427,7 +3041,7 @@ ajv@^8.6.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ajv@^8.9.0: +ajv@^8.8.2, ajv@^8.9.0: version "8.17.1" resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -2702,7 +3316,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -bare-events@^2.2.0, bare-events@^2.5.4: +bare-events@*, bare-events@^2.2.0, bare-events@^2.5.4: version "2.5.4" resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz" integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== @@ -2787,7 +3401,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browserslist@^4.21.5, browserslist@^4.24.0, browserslist@^4.24.4: +browserslist@^4.21.5, browserslist@^4.24.0, browserslist@^4.24.4, "browserslist@>= 4.21.0": version "4.24.4" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz" integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== @@ -2904,7 +3518,7 @@ character-reference-invalid@^2.0.0: resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz" integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== -chart.js@^4.4.3: +chart.js@^4.1.1, chart.js@^4.4.3: version "4.4.9" resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz" integrity sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg== @@ -2926,6 +3540,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + chromium-bidi@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz" @@ -3535,7 +4154,7 @@ devlop@^1.0.0, devlop@^1.1.0: dependencies: dequal "^2.0.0" -devtools-protocol@0.0.1312386: +devtools-protocol@*, devtools-protocol@0.0.1312386: version "0.0.1312386" resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz" integrity sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA== @@ -3612,7 +4231,7 @@ earcut@3: resolved "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz" integrity sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw== -easymde@^2.18.0: +easymde@^2.18.0, "easymde@>= 2.0.0 < 3.0.0": version "2.18.0" resolved "https://registry.npmjs.org/easymde/-/easymde-2.18.0.tgz" integrity sha512-IxVVUxNWIoXLeqtBU4BLc+eS/ScYhT1Dcb6yF5Wchoj1iXAV+TIIDWx+NCaZhY7RcSHqDPKllbYq7nwGKILnoA== @@ -3678,6 +4297,14 @@ engine.io-parser@~5.2.1: resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz" integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== +enhanced-resolve@^5.17.2: + version "5.18.2" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz" + integrity sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" @@ -3757,6 +4384,11 @@ es-errors@^1.3.0: resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-module-lexer@^1.2.1: + version "1.7.0" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" @@ -3863,7 +4495,7 @@ eslint-module-utils@^2.7.4: dependencies: debug "^3.2.7" -eslint-plugin-import@^2.26.0: +eslint-plugin-import@*, eslint-plugin-import@^2.26.0: version "2.27.5" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz" integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== @@ -3940,6 +4572,14 @@ eslint-scope@^7.1.1: esrecurse "^4.3.0" estraverse "^5.2.0" +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-utils@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" @@ -3957,7 +4597,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@8.26.0: +eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^7.23.0 || ^8.0.0", eslint@>=5, eslint@>=7.0.0, eslint@8.26.0: version "8.26.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz" integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== @@ -4030,6 +4670,11 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" @@ -4055,6 +4700,11 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.1: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +events@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + extend@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" @@ -4129,6 +4779,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +faye-websocket@0.11.4: + version "0.11.4" + resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" @@ -4203,6 +4860,40 @@ find-up@^6.3.0: locate-path "^7.1.0" path-exists "^5.0.0" +"firebase@>= 9.0.0": + version "11.10.0" + resolved "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz" + integrity sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w== + dependencies: + "@firebase/ai" "1.4.1" + "@firebase/analytics" "0.10.17" + "@firebase/analytics-compat" "0.2.23" + "@firebase/app" "0.13.2" + "@firebase/app-check" "0.10.1" + "@firebase/app-check-compat" "0.3.26" + "@firebase/app-compat" "0.4.2" + "@firebase/app-types" "0.9.3" + "@firebase/auth" "1.10.8" + "@firebase/auth-compat" "0.5.28" + "@firebase/data-connect" "0.3.10" + "@firebase/database" "1.0.20" + "@firebase/database-compat" "2.0.11" + "@firebase/firestore" "4.8.0" + "@firebase/firestore-compat" "0.3.53" + "@firebase/functions" "0.12.9" + "@firebase/functions-compat" "0.3.26" + "@firebase/installations" "0.6.18" + "@firebase/installations-compat" "0.2.18" + "@firebase/messaging" "0.12.22" + "@firebase/messaging-compat" "0.2.22" + "@firebase/performance" "0.7.7" + "@firebase/performance-compat" "0.2.20" + "@firebase/remote-config" "0.6.5" + "@firebase/remote-config-compat" "0.2.18" + "@firebase/storage" "0.13.14" + "@firebase/storage-compat" "0.3.24" + "@firebase/util" "1.12.1" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" @@ -4402,6 +5093,11 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@^7.0.3, glob@^7.1.3, glob@^7.1.6, glob@^7.2.0: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -4497,7 +5193,7 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: 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== @@ -4770,6 +5466,11 @@ html-void-elements@^3.0.0: resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz" integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== +http-parser-js@>=0.5.1: + version "0.5.10" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== + http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: version "7.0.2" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" @@ -4791,7 +5492,7 @@ hyphenate-style-name@^1.0.0, hyphenate-style-name@^1.0.3: resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz" integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw== -idb@^7.0.1: +idb@^7.0.1, idb@7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== @@ -5175,7 +5876,7 @@ jsesc@~3.0.2: resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz" integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== -json-parse-even-better-errors@^2.3.0: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -5293,6 +5994,11 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + loader-utils@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz" @@ -5328,6 +6034,11 @@ lodash-es@4: resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.debounce@^4.0.8, lodash.debounce@4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" @@ -5348,6 +6059,11 @@ lodash@^4.17.20, lodash@^4.17.21: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +long@^5.0.0: + version "5.3.2" + resolved "https://registry.npmjs.org/long/-/long-5.3.2.tgz" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + longest-streak@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz" @@ -5921,7 +6637,7 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@^2.1.27: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -5957,6 +6673,11 @@ mitt@3.0.1: resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== +"monaco-editor@>= 0.25.0 < 1": + version "0.52.2" + resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz" + integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ== + motion-dom@^11.18.1: version "11.18.1" resolved "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz" @@ -6017,6 +6738,11 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + netmask@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" @@ -6043,7 +6769,7 @@ next-remove-imports@^1.0.12: babel-loader "^9.1.3" babel-plugin-transform-remove-imports "^1.7.0" -next@^14.0.2: +next@^14.0.2, "next@>= 13", next@>=9.0.0: version "14.2.28" resolved "https://registry.npmjs.org/next/-/next-14.2.28.tgz" integrity sha512-QLEIP/kYXynIxtcKB6vNjtWLVs3Y4Sb+EClTC/CSVzdLD1gIuItccpu/n1lhmduffI32iPGEK2cLLxxt28qgYA== @@ -6476,7 +7202,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.23, postcss@8.4.49: +postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4, postcss@^8.4.21, postcss@^8.4.23, postcss@>=8.0.9, postcss@8.4.49: version "8.4.49" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz" integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== @@ -6509,7 +7235,7 @@ prettier-plugin-tailwindcss@^0.1.13: resolved "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.13.tgz" integrity sha512-/EKQURUrxLu66CMUg4+1LwGdxnz8of7IDvrSLqEtDqhLH61SAlNNUSr90UTvZaemujgl3OH/VHg+fyGltrNixw== -prettier@^2.7.1: +prettier@^2.7.1, prettier@>=2.2.0: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -6552,6 +7278,24 @@ property-information@^7.0.0: resolved "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz" integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== +protobufjs@^7.2.5: + version "7.5.3" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz" + integrity sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-agent@^6.4.0: version "6.5.0" resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz" @@ -6703,7 +7447,7 @@ react-dnd@^16.0.1: fast-deep-equal "^3.1.3" hoist-non-react-statics "^3.3.2" -react-dom@18.2.0: +react-dom@*, "react-dom@^15.0.1 || ^16.0.1", "react-dom@^16 || ^17 || ^18", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.3.0 || ^17.0.0-0 || ^18.0.0-0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || 17.x || 18.x", "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18.0.0 || ^19.0.0", react-dom@^18.2.0, "react-dom@>= 16.3.0", "react-dom@>= 16.8.0", react-dom@>=16, react-dom@>=16.0.0, react-dom@>=16.14.0, react-dom@>=16.3.0, react-dom@>=16.4.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=16.8.2, react-dom@>=18, "react-dom@~15 || ~16 || ~17 || ~18", "react-dom@15 - 18", react-dom@18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -6799,6 +7543,11 @@ react-is@^16.7.0: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +"react-is@^16.8 || ^17.0.0-0 || ^18.0.0-0", react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-is@^16.8.1: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" @@ -6809,11 +7558,6 @@ react-is@^17.0.2: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.3.1: - version "18.3.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - react-is@^19.0.0: version "19.1.0" resolved "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz" @@ -7059,7 +7803,7 @@ react-visibility-sensor@^5.1.1: dependencies: prop-types "^15.7.2" -react@18.2.0: +react@*, "react@^0.14.9 || ^15.3.0 || ^16.0.0", "react@^15.0.1 || ^16.0.1", "react@^16 || ^17 || ^18", "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.3.0 || ^17.0.0-0 || ^18.0.0-0", "react@^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || 17.x || 18.x", "react@^16.8.3 || ^17 || ^18", "react@^16.8.3 || ^17 || ^18 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.5 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18 || ^19 || ^19.0.0-rc", react@^18.0.0, "react@^18.0.0 || ^19.0.0", react@^18.2.0, "react@>= 16", "react@>= 16.14", "react@>= 16.3", "react@>= 16.3.0", "react@>= 16.8.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=0.0.0 <=99", react@>=0.14.0, react@>=16, react@>=16.0.0, react@>=16.13.1, react@>=16.14.0, react@>=16.3, react@>=16.3.0, react@>=16.4.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=16.8.2, react@>=18, react@>=18.0.0, "react@~15 || ~16 || ~17 || ~18", "react@15 - 18", react@18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -7418,7 +8162,7 @@ rollup-plugin-terser@^7.0.0: serialize-javascript "^4.0.0" terser "^5.0.0" -rollup@^2.43.1: +"rollup@^1.20.0 || ^2.0.0", rollup@^1.20.0||^2.0.0, rollup@^2.0.0, rollup@^2.43.1: version "2.79.2" resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz" integrity sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ== @@ -7449,7 +8193,7 @@ safe-array-concat@^1.0.0: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@^5.1.0: +safe-buffer@^5.1.0, safe-buffer@>=5.1.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7499,6 +8243,16 @@ schema-utils@^4.3.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" +schema-utils@^4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz" + integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + screenfull@^5.1.0: version "5.2.0" resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz" @@ -7560,7 +8314,7 @@ seroval-plugins@^1.1.0: resolved "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.2.1.tgz" integrity sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw== -seroval@^1.1.0: +seroval@^1.0, seroval@^1.1.0: version "1.2.1" resolved "https://registry.npmjs.org/seroval/-/seroval-1.2.1.tgz" integrity sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw== @@ -7930,7 +8684,7 @@ style-to-object@1.0.8: dependencies: inline-style-parser "0.2.4" -styled-components@^6.1.13: +"styled-components@^4.0.0 || ^5.0.0", styled-components@^6.1.13: version "6.1.17" resolved "https://registry.npmjs.org/styled-components/-/styled-components-6.1.17.tgz" integrity sha512-97D7DwWanI7nN24v0D4SvbfjLE9656umNSJZkBkDIWL37aZqG/wRQ+Y9pWtXyBIM/NSfcBzHLErEsqHmJNSVUg== @@ -8009,7 +8763,7 @@ tailwindcss-animate@^1.0.7: resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz" integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== -tailwindcss@^3.2.1: +tailwindcss@^3.2.1, "tailwindcss@>=3.0.0 || >= 3.0.0-alpha.1", "tailwindcss@>=3.0.0 || insiders": version "3.3.3" resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz" integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== @@ -8037,6 +8791,11 @@ tailwindcss@^3.2.1: resolve "^1.22.2" sucrase "^3.32.0" +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.2" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz" + integrity sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg== + tar-fs@^3.0.6: version "3.0.8" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz" @@ -8072,7 +8831,7 @@ tempy@^0.6.0: type-fest "^0.16.0" unique-string "^2.0.0" -terser-webpack-plugin@^5.3.3: +terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.3: version "5.3.14" resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz" integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== @@ -8184,7 +8943,7 @@ three-slippy-map-globe@1: d3-octree "^1.1" d3-scale "1 - 4" -three@^0.175.0, "three@>=0.154 <1": +three@^0.175.0, three@>=0.154, "three@>=0.154 <1", three@>=0.168, three@>=0.72.0: version "0.175.0" resolved "https://registry.npmjs.org/three/-/three-0.175.0.tgz" integrity sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg== @@ -8293,16 +9052,16 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@*, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@^1.8.1: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - tslib@^2.8.1: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" @@ -8381,6 +9140,11 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +"typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=3.3.1, typescript@>=4.9.5: + version "5.8.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + typo-js@*: version "1.2.3" resolved "https://registry.npmjs.org/typo-js/-/typo-js-1.2.3.tgz" @@ -8603,11 +9367,24 @@ w3c-keyname@^2.2.4: resolved "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz" integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== +watchpack@^2.4.1: + version "2.4.4" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz" + integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + web-namespaces@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +web-vitals@^4.2.4: + version "4.2.4" + resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" @@ -8626,6 +9403,56 @@ webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" +webpack-sources@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz" + integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== + +"webpack@^4.4.0 || ^5.9.0", webpack@^5.1.0, webpack@>=2, "webpack@>=4.0.0 <6.0.0", webpack@>=5: + version "5.100.1" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.100.1.tgz" + integrity sha512-YJB/ESPUe2Locd0NKXmw72Dx8fZQk1gTzI6rc9TAT4+Sypbnhl8jd8RywB1bDsDF9Dy1RUR7gn3q/ZJTd0OZZg== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.8" + "@types/json-schema" "^7.0.15" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.15.0" + acorn-import-phases "^1.0.3" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.2" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^4.3.2" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.11" + watchpack "^2.4.1" + webpack-sources "^3.3.3" + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" @@ -8875,7 +9702,7 @@ xterm-addon-fit@^0.8.0: resolved "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz" integrity sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw== -xterm@^5.3.0: +xterm@^5.0.0, xterm@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz" integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== From 94e222edcb109137ccae6d6dc84944f2d4da0e41 Mon Sep 17 00:00:00 2001 From: Sai4158 <116776995+Sai4158@users.noreply.github.com> Date: Tue, 15 Jul 2025 22:32:50 -0400 Subject: [PATCH 2/4] New report page, moderation page and able to disable and delete accounts --- src/components/AccountReactivation.jsx | 395 ++ src/components/CommentReporter.jsx | 219 + .../moderation/UserActionModals.jsx | 144 + src/components/moderation/ViewChallenge.jsx | 16 +- src/components/moderation/ViewReport.jsx | 67 +- src/components/settingComponents/dropdown.jsx | 11 +- src/components/settingComponents/sidebar.jsx | 21 +- src/pages/dashboard.jsx | 773 ++- src/pages/login.jsx | 217 +- src/pages/moderation.jsx | 4532 ++++++++++++++--- src/pages/report.jsx | 648 ++- src/pages/settings/account-management.jsx | 1003 ++++ src/utils/request.js | 105 +- 13 files changed, 6980 insertions(+), 1171 deletions(-) create mode 100644 src/components/AccountReactivation.jsx create mode 100644 src/components/CommentReporter.jsx create mode 100644 src/components/moderation/UserActionModals.jsx create mode 100644 src/pages/settings/account-management.jsx diff --git a/src/components/AccountReactivation.jsx b/src/components/AccountReactivation.jsx new file mode 100644 index 00000000..07149308 --- /dev/null +++ b/src/components/AccountReactivation.jsx @@ -0,0 +1,395 @@ +import React, { useState, useEffect } from 'react'; +import request from '@/utils/request'; + +const AccountReactivation = ({ + email, + accountType, + password, + deletionInfo, + onReactivated, + onCancel, +}) => { + const [isReactivating, setIsReactivating] = useState(false); + const [notification, setNotification] = useState(null); + const [detectedAccountType, setDetectedAccountType] = useState( + accountType || 'EMAIL' + ); + + // Detect account type on mount + useEffect(() => { + if (accountType) { + setDetectedAccountType(accountType); + } + }, [accountType]); + + const showNotification = (message, type = 'info') => { + setNotification({ message, type }); + setTimeout(() => setNotification(null), 5000); + }; + + // Calculate deletion deadline + const getDeletionInfo = () => { + if (!deletionInfo?.scheduledFor) { + return { + hasDeadline: false, + message: 'Your account deletion is pending.', + }; + } + + try { + const deadline = new Date(deletionInfo.scheduledFor); + const now = new Date(); + const timeRemaining = deadline.getTime() - now.getTime(); + const daysRemaining = Math.max( + 0, + Math.ceil(timeRemaining / (1000 * 60 * 60 * 24)) + ); + const hoursRemaining = Math.max( + 0, + Math.ceil(timeRemaining / (1000 * 60 * 60)) + ); + + if (timeRemaining > 0) { + return { + hasDeadline: true, + deadline: deadline.toLocaleDateString(), + daysRemaining, + hoursRemaining, + message: `You have ${daysRemaining} day(s) to save your account!`, + urgency: + daysRemaining <= 1 ? 'high' : daysRemaining <= 3 ? 'medium' : 'low', + }; + } else { + return { + hasDeadline: false, + message: 'Your deletion deadline has passed.', + }; + } + } catch (error) { + return { + hasDeadline: false, + message: 'Your account deletion is pending.', + }; + } + }; + + const deletionData = getDeletionInfo(); + + const handleReactivation = async () => { + setIsReactivating(true); + try { + // Validate that we have password for EMAIL accounts + if (detectedAccountType === 'EMAIL' && !password) { + throw new Error( + 'Password is required for email accounts. Please try logging in again.' + ); + } + + const requestData = { + email: email, + accountType: detectedAccountType, + }; + + // Include password for EMAIL accounts + if (detectedAccountType === 'EMAIL' && password) { + requestData.password = password; + } + + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/account/reactivate`, + 'POST', + requestData + ); + + if (response.success) { + const successMessage = deletionInfo + ? 'Account saved successfully! Your deletion has been cancelled.' + : 'Account reactivated successfully!'; + showNotification(successMessage, 'success'); + + // Store the token securely + if (response.token) { + localStorage.setItem('token', response.token); + } + + setTimeout(() => { + onReactivated?.(response.token); + }, 1500); + } else { + throw new Error(response.error || 'Failed to reactivate account'); + } + } catch (error) { + let errorMessage = error.message || 'Failed to reactivate account.'; + + // Handle specific error cases + if (errorMessage.includes('Password is required')) { + errorMessage = + 'Password verification failed. Please go back and try logging in again.'; + } else if (errorMessage.includes('Invalid password')) { + errorMessage = + 'The password you entered is incorrect. Please go back and try again.'; + } + + showNotification(errorMessage, 'error'); + } finally { + setIsReactivating(false); + } + }; + + const NotificationModal = () => { + if (!notification) return null; + + const getIcon = () => { + switch (notification.type) { + case 'success': + return 'fas fa-check-circle text-green-400'; + case 'error': + return 'fas fa-exclamation-circle text-red-400'; + case 'warning': + return 'fas fa-exclamation-triangle text-yellow-400'; + default: + return 'fas fa-info-circle text-blue-400'; + } + }; + + return ( +
+
setNotification(null)} + /> +
+
+ +
+

{notification.message}

+
+ +
+
+
+ ); + }; + + return ( + <> +
+
+ {/* Header */} +
+
+
+ + {/* Main Card */} +
+ {/* Title */} +
+

+ {deletionInfo && deletionData.hasDeadline + ? 'Save Your Account from Permanent Deletion' + : deletionInfo + ? 'Account Deletion Request' + : 'Account Reactivation'} +

+

+ {deletionInfo && deletionData.hasDeadline + ? `Login before ${deletionData.deadline} to prevent permanent deletion` + : deletionInfo + ? 'Your account deletion request is pending' + : 'Your account is temporarily suspended'} +

+
+ + {/* Deletion Deadline Warning - Only show if account deletion is scheduled */} + {deletionInfo && deletionData.hasDeadline && ( +
+
+
+ +
+
+

+ Deleting in {deletionData.daysRemaining} day(s) +

+

+ Scheduled: {deletionData.deadline} +

+ {deletionData.daysRemaining <= 3 && ( +

+ {deletionData.daysRemaining}d,{' '} + {deletionData.hoursRemaining % 24}h remaining +

+ )} +
+
+
+ )} + + {/* Account Info */} +
+
+
+ {detectedAccountType === 'GOOGLE' ? ( + + ) : ( + + )} +
+
+

{email}

+

+ {detectedAccountType === 'GOOGLE' + ? 'Google Account' + : 'Email Account'} +

+
+
+ +
+
+
+ + {/* Info Cards */} +
+
+

+ What happened? +

+

+ {deletionInfo + ? 'You requested account deletion and your account has been suspended for 7 days as a safety measure.' + : 'Your account has been temporarily suspended but can be reactivated instantly.'} +

+
+ +
+

+ {deletionInfo ? 'Your content is still safe!' : 'Good news!'} +

+

+ {deletionInfo + ? 'All your challenges, comments, and progress remain visible and intact during this 7-day period. Nothing is transferred to the system account until the deadline passes.' + : 'All your data is safe and will be restored immediately upon reactivation.'} +

+
+
+ + {/* Ready to Reactivate Notice */} +
+
+
+ +
+
+

+ {deletionInfo + ? 'Ready to Cancel Deletion' + : 'Ready to Reactivate'} +

+

+ {deletionInfo + ? 'Click below to immediately cancel your deletion request' + : "You're already authenticated - just click the button below"} +

+
+
+
+ + {/* Buttons */} +
+ + + +
+ + {/* Footer */} +
+

+ Need help?{' '} + + support@ctfguide.com + +

+
+
+ + {/* Bottom Footer */} +
+

© CTFGuide Corporation 2025

+
+
+
+ + + + ); +}; + +export default AccountReactivation; diff --git a/src/components/CommentReporter.jsx b/src/components/CommentReporter.jsx new file mode 100644 index 00000000..a05815fc --- /dev/null +++ b/src/components/CommentReporter.jsx @@ -0,0 +1,219 @@ +import React, { useState } from 'react'; +import request from '@/utils/request'; + +const CommentReporter = ({ commentId, commentContent, challengeId }) => { + const [isOpen, setIsOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [selectedReason, setSelectedReason] = useState(''); + const [customReason, setCustomReason] = useState(''); + const [notification, setNotification] = useState(null); + + const reportReasons = [ + { value: 'SPAM', label: 'Spam or unwanted commercial content' }, + { value: 'HARASSMENT', label: 'Harassment or bullying' }, + { value: 'INAPPROPRIATE', label: 'Inappropriate content' }, + { value: 'MISINFORMATION', label: 'False or misleading information' }, + { value: 'SPOILER', label: 'Contains spoilers or solution hints' }, + { value: 'OFF_TOPIC', label: 'Off-topic or irrelevant' }, + { value: 'OTHER', label: 'Other (please specify)' }, + ]; + + const showNotification = (message, type = 'info') => { + setNotification({ message, type }); + setTimeout(() => setNotification(null), 4000); + }; + + const handleSubmitReport = async () => { + if (!selectedReason) { + showNotification('Please select a reason for reporting', 'error'); + return; + } + + if (selectedReason === 'OTHER' && !customReason.trim()) { + showNotification('Please provide a custom reason', 'error'); + return; + } + + setIsSubmitting(true); + + try { + const reportData = { + type: 'COMMENT', + desc: + selectedReason === 'OTHER' + ? customReason + : reportReasons.find((r) => r.value === selectedReason)?.label, + metadata: { + commentId, + challengeId, + reason: selectedReason, + commentContent: commentContent?.substring(0, 200) || '', // First 200 chars for context + }, + }; + + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/reports`, + 'POST', + reportData + ); + + if (response.success) { + showNotification( + 'Comment reported successfully. Thank you for helping keep our community safe.', + 'success' + ); + setIsOpen(false); + setSelectedReason(''); + setCustomReason(''); + } else { + throw new Error(response.error || 'Failed to submit report'); + } + } catch (error) { + console.error('Error reporting comment:', error); + showNotification( + error.message || 'Failed to submit report. Please try again.', + 'error' + ); + } finally { + setIsSubmitting(false); + } + }; + + const NotificationModal = () => { + if (!notification) return null; + + const getIcon = () => { + switch (notification.type) { + case 'success': + return 'fas fa-check-circle text-green-400'; + case 'error': + return 'fas fa-exclamation-circle text-red-400'; + case 'warning': + return 'fas fa-exclamation-triangle text-yellow-400'; + default: + return 'fas fa-info-circle text-blue-400'; + } + }; + + return ( +
+
setNotification(null)} + /> +
+
+ +
+

{notification.message}

+
+ +
+
+
+ ); + }; + + return ( + <> + + + {isOpen && ( +
+
setIsOpen(false)} + /> +
+
+

+ Report Comment +

+ +
+ +

+ Help us keep the community safe by reporting inappropriate + content. +

+ +
+ {reportReasons.map((reason) => ( + + ))} +
+ + {selectedReason === 'OTHER' && ( +



-
+

Set Base Points

Beginner: 100, @@ -191,18 +191,18 @@ const ViewChallenge = ({ open, setOpen, selected }) => {

- - @@ -223,7 +223,7 @@ const ViewChallenge = ({ open, setOpen, selected }) => { } }} type="button" - className="ml-4 justify-center rounded-md bg-yellow-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-yellow-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-yellow-500" + className="ml-4 justify-center bg-yellow-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-yellow-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-yellow-500" > Request Changes @@ -244,7 +244,7 @@ const ViewChallenge = ({ open, setOpen, selected }) => { } }} type="submit" - className="ml-4 justify-center rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-500" + className="ml-4 justify-center bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-500" > {isLoading ? ( diff --git a/src/components/moderation/ViewReport.jsx b/src/components/moderation/ViewReport.jsx index ab10eb04..0223db29 100644 --- a/src/components/moderation/ViewReport.jsx +++ b/src/components/moderation/ViewReport.jsx @@ -1,10 +1,12 @@ -import { Fragment } from 'react' -import { Dialog, Transition } from '@headlessui/react' +import { Fragment } from 'react'; +import { Dialog, Transition } from '@headlessui/react'; export default function ViewReport({ open, setOpen, report }) { // Parse metadata if it exists and is a string - const metadata = report?.metadata ? - (typeof report.metadata === 'string' ? JSON.parse(report.metadata) : report.metadata) + const metadata = report?.metadata + ? typeof report.metadata === 'string' + ? JSON.parse(report.metadata) + : report.metadata : {}; return ( @@ -33,10 +35,13 @@ export default function ViewReport({ open, setOpen, report }) { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
- + Report Details
@@ -49,15 +54,14 @@ export default function ViewReport({ open, setOpen, report }) { {metadata.reportedUserId && ( )} @@ -79,12 +83,18 @@ export default function ViewReport({ open, setOpen, report }) {

Severity:

- + {metadata.severity}

@@ -100,14 +110,15 @@ export default function ViewReport({ open, setOpen, report }) {

Description:

-

- {report?.desc} -

+

{report?.desc}

Submitted:

-

{report?.createdAt && new Date(report.createdAt).toLocaleString()}

+

+ {report?.createdAt && + new Date(report.createdAt).toLocaleString()} +

@@ -116,7 +127,7 @@ export default function ViewReport({ open, setOpen, report }) {
- ) -} \ No newline at end of file + ); +} diff --git a/src/components/settingComponents/dropdown.jsx b/src/components/settingComponents/dropdown.jsx index a3205e58..b0dc2323 100644 --- a/src/components/settingComponents/dropdown.jsx +++ b/src/components/settingComponents/dropdown.jsx @@ -4,17 +4,20 @@ export default function Dropdown({ tab }) { const router = useRouter(); return ( -
+
); -} \ No newline at end of file +} diff --git a/src/components/settingComponents/sidebar.jsx b/src/components/settingComponents/sidebar.jsx index 6514ae0e..6cb21b0b 100644 --- a/src/components/settingComponents/sidebar.jsx +++ b/src/components/settingComponents/sidebar.jsx @@ -1,8 +1,8 @@ import Link from 'next/link'; -import { useRouter } from 'next/router' +import { useRouter } from 'next/router'; export default function Sidebar() { - const router = useRouter() + const router = useRouter(); return (
+ + {/* Divider */} +
  • +
    +
  • + + {/* Account Management Section */} +
  • + +
    +
    + Account Management +
    + +
  • ); -} \ No newline at end of file +} diff --git a/src/pages/dashboard.jsx b/src/pages/dashboard.jsx index c0038c30..b44c9e7e 100644 --- a/src/pages/dashboard.jsx +++ b/src/pages/dashboard.jsx @@ -4,16 +4,25 @@ import { StandardNav } from '@/components/StandardNav'; import { DashboardHeader } from '@/components/dashboard/DashboardHeader'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useEffect, useState } from 'react'; -import { ArrowLeftIcon, CheckCircleIcon, XCircleIcon, ClockIcon } from '@heroicons/react/24/solid'; +import { + ArrowLeftIcon, + CheckCircleIcon, + XCircleIcon, + ClockIcon, +} from '@heroicons/react/24/solid'; import request from '@/utils/request'; import ChallengeCard from '@/components/profile/ChallengeCard'; -import { BoltIcon, RocketLaunchIcon, TrophyIcon } from '@heroicons/react/20/solid'; +import { + BoltIcon, + RocketLaunchIcon, + TrophyIcon, +} from '@heroicons/react/20/solid'; import Skeleton from 'react-loading-skeleton'; import Upgrade from '@/components/nav/Upgrade'; import { useRouter } from 'next/router'; import { CheckIcon } from '@heroicons/react/20/solid'; -import { Dialog, Transition } from '@headlessui/react' -import { Fragment } from 'react' +import { Dialog, Transition } from '@headlessui/react'; +import { Fragment } from 'react'; import OnboardingModal from '@/components/modals/OnboardingModal'; import { UserCircleIcon } from '@heroicons/react/24/solid'; import timeTracker from '@/utils/timeTracker'; @@ -24,11 +33,9 @@ const includedFeatures = [ 'Machines with GUI', 'Access to more operating systems', 'Longer machine times', - 'CTFGuide Pro flair on your profile, comments, and created content' - -] + 'CTFGuide Pro flair on your profile, comments, and created content', +]; export default function Dashboard() { - const [likes, setLikes] = useState(null); const [badges, setbadges] = useState([]); const [challenges, setchallenges] = useState([]); @@ -42,16 +49,169 @@ export default function Dashboard() { const exampleObjectives = [ { completed: false, - description: "Touch grass", + description: 'Touch grass', }, { completed: true, - description: "Eat McDonalds", + description: 'Eat McDonalds', }, - ] + ]; const [isOnboardingOpen, setIsOnboardingOpen] = useState(false); const [completedTasks, setCompletedTasks] = useState(null); const [timeProgress, setTimeProgress] = useState(null); + const [lastNotificationCheck, setLastNotificationCheck] = useState(null); + const [processedWarningIds, setProcessedWarningIds] = useState(() => { + // Load processed warnings from localStorage on component mount + if (typeof window !== 'undefined') { + const stored = localStorage.getItem('processedWarningIds'); + return stored ? new Set(JSON.parse(stored)) : new Set(); + } + return new Set(); + }); + const [isCheckingNotifications, setIsCheckingNotifications] = useState(false); + + // Function to check for new notifications and show alerts + const checkForNewNotifications = async () => { + // Prevent multiple simultaneous checks + if (isCheckingNotifications) { + console.log('Notification check already in progress, skipping...'); + return; + } + + setIsCheckingNotifications(true); + + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/account/notifications`, + 'GET' + ); + if (response && response.length > 0) { + // Check for new warning notifications that haven't been processed + const newWarnings = response.filter( + (notification) => + notification.message && + notification.message.includes('warning') && + !processedWarningIds.has(notification.id) + ); + + // Only show toast for the most recent new warning that's within the last 5 minutes + if (newWarnings.length > 0) { + const latestNewWarning = newWarnings[0]; + const warningTime = new Date(latestNewWarning.createdAt); + const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); // 5 minutes ago + + // Only show toast if the warning is recent (within last 5 minutes) + if (warningTime > fiveMinutesAgo) { + // Mark this notification as processed BEFORE showing toast + const newProcessedIds = new Set([ + ...processedWarningIds, + latestNewWarning.id, + ]); + setProcessedWarningIds(newProcessedIds); + + // Save to localStorage immediately + if (typeof window !== 'undefined') { + localStorage.setItem( + 'processedWarningIds', + JSON.stringify([...newProcessedIds]) + ); + } + + // Show custom notification for warning (only once for recent warnings) + showCustomNotification(latestNewWarning.message, 8000); + + setLastNotificationCheck(new Date(latestNewWarning.createdAt)); + + console.log(`Toast shown for warning ID: ${latestNewWarning.id}`); + } else { + // For older warnings, just mark them as processed without showing toast + const newProcessedIds = new Set([ + ...processedWarningIds, + latestNewWarning.id, + ]); + setProcessedWarningIds(newProcessedIds); + + // Save to localStorage + if (typeof window !== 'undefined') { + localStorage.setItem( + 'processedWarningIds', + JSON.stringify([...newProcessedIds]) + ); + } + + console.log( + `Older warning marked as processed (no toast): ${latestNewWarning.id}` + ); + } + } else { + console.log('No new warnings found'); + } + } + } catch (error) { + console.error('Failed to check notifications:', error); + } finally { + setIsCheckingNotifications(false); + } + }; + + // Function to cleanup old processed warnings (keep only last 100) + const cleanupProcessedWarnings = () => { + if (typeof window !== 'undefined' && processedWarningIds.size > 100) { + const warningArray = Array.from(processedWarningIds); + const recentWarnings = warningArray.slice(-100); // Keep only last 100 + const newProcessedIds = new Set(recentWarnings); + setProcessedWarningIds(newProcessedIds); + localStorage.setItem( + 'processedWarningIds', + JSON.stringify([...newProcessedIds]) + ); + } + }; + + // Listen for warning events from other components + useEffect(() => { + let debounceTimer = null; + + const handleWarningIssued = (event) => { + // Clear any existing timer + if (debounceTimer) { + clearTimeout(debounceTimer); + } + + // Set a new timer to debounce the event + debounceTimer = setTimeout(() => { + console.log('Warning issued event received, checking notifications...'); + checkForNewNotifications(); + }, 1000); // Small delay to ensure backend has processed the warning + }; + + // Add event listener for warning issued events + window.addEventListener('warningIssued', handleWarningIssued); + + // Add manual trigger function for testing (accessible from console) + window.triggerNotificationCheck = () => { + console.log('Manual notification check triggered'); + checkForNewNotifications(); + }; + + // Add function to clear processed warnings for testing + window.clearProcessedWarnings = () => { + console.log('Clearing processed warnings...'); + setProcessedWarningIds(new Set()); + if (typeof window !== 'undefined') { + localStorage.removeItem('processedWarningIds'); + } + }; + + return () => { + window.removeEventListener('warningIssued', handleWarningIssued); + delete window.triggerNotificationCheck; + delete window.clearProcessedWarnings; + if (debounceTimer) { + clearTimeout(debounceTimer); + } + }; + }, [processedWarningIds]); useEffect(() => { const user = localStorage.getItem('username'); @@ -63,28 +223,38 @@ export default function Dashboard() { ); const data = await response.json(); setbadges(data); - } catch { } + } catch {} }; fetchBadges(); setbadges([]); const fetchChallenges = async () => { try { - const pinnedChallengeEndPoint = process.env.NEXT_PUBLIC_API_URL + '/users/' + user + '/likes'; - const pinnedChallengeResult = await request(pinnedChallengeEndPoint, "GET", null); + const pinnedChallengeEndPoint = + process.env.NEXT_PUBLIC_API_URL + '/users/' + user + '/likes'; + const pinnedChallengeResult = await request( + pinnedChallengeEndPoint, + 'GET', + null + ); setchallenges(pinnedChallengeResult); - } catch (error) { } + } catch (error) {} }; fetchChallenges(); setchallenges([]); const fetchData = async () => { try { - const pinnedChallengeEndPoint = process.env.NEXT_PUBLIC_API_URL + '/users/' + user + '/likes'; - const pinnedChallengeResult = await request(pinnedChallengeEndPoint, "GET", null); + const pinnedChallengeEndPoint = + process.env.NEXT_PUBLIC_API_URL + '/users/' + user + '/likes'; + const pinnedChallengeResult = await request( + pinnedChallengeEndPoint, + 'GET', + null + ); // setLikes(pinnedChallengeResult); } catch (error) { - console.error("Failed to fetch pinnedChallengeResults: ", error) + console.error('Failed to fetch pinnedChallengeResults: ', error); } }; fetchData(); @@ -95,13 +265,17 @@ export default function Dashboard() { } catch (error) { console.error('Failed to fetch objectives: ', error); } - } + }; fetchObjectives(); const fetchRecommendedChallenges = async () => { setLoading(true); try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/challenges/dash/recommended`, 'GET', null); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/challenges/dash/recommended`, + 'GET', + null + ); setLikes(response); // Assuming the response directly contains the array of challenges } catch (error) { console.error('Failed to fetch recommended challenges: ', error); @@ -110,11 +284,14 @@ export default function Dashboard() { } }; - const fetchPopularChallenges = async () => { setLoading(true); try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/challenges/dash/popular`, 'GET', null); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/challenges/dash/popular`, + 'GET', + null + ); setPopular(response); // Assuming the response directly contains the array of challenges } catch (error) { console.error('Failed to fetch recommended challenges: ', error); @@ -123,20 +300,23 @@ export default function Dashboard() { } }; - const fetchAccountDetails = async() => { + const fetchAccountDetails = async () => { request(`${process.env.NEXT_PUBLIC_API_URL}/account`, 'GET', null) .then((data) => { - setRole(data.role); }) .catch((err) => { console.log(err); }); - } + }; const checkCompletedTasks = async () => { try { - const accountResponse = await request(`${process.env.NEXT_PUBLIC_API_URL}/account`, 'GET', null); + const accountResponse = await request( + `${process.env.NEXT_PUBLIC_API_URL}/account`, + 'GET', + null + ); const hasProfilePicture = accountResponse.profileImage; const hasCompletedChallenge = accountResponse.points > 0; @@ -154,34 +334,55 @@ export default function Dashboard() { fetchRecommendedChallenges(); fetchPopularChallenges(); fetchAccountDetails(); - request(`${process.env.NEXT_PUBLIC_API_URL}/activityFeed/`, 'GET', null).then(response => { - console.log(response) + + // Check for recent notifications immediately (within last 5 minutes) + setTimeout(() => { + checkForNewNotifications(); + }, 1000); // Check after 1 second to ensure component is fully loaded + + // Cleanup old processed warnings on initial load + cleanupProcessedWarnings(); + + request(`${process.env.NEXT_PUBLIC_API_URL}/activityFeed/`, 'GET', null) + .then((response) => { + console.log(response); setActivities(response.activityFeed); }) - .catch(error => { + .catch((error) => { console.error('Error fetching feed data: ', error); }); const intervalId = setInterval(() => { - request(`${process.env.NEXT_PUBLIC_API_URL}/activityFeed/`, 'GET', null).then(response => { + request(`${process.env.NEXT_PUBLIC_API_URL}/activityFeed/`, 'GET', null) + .then((response) => { //console.log(response) if (response.activityFeed.length > 0) { setActivities(response.activityFeed); } }) - .catch(error => { + .catch((error) => { console.error('Error fetching feed data: ', error); }); }, 5000); // 5000 milliseconds = 5 seconds - return () => clearInterval(intervalId); // - + // Cleanup old processed warnings every 5 minutes + const cleanupIntervalId = setInterval(() => { + cleanupProcessedWarnings(); + }, 300000); // 5 minutes + + return () => { + clearInterval(intervalId); + clearInterval(cleanupIntervalId); + }; }, []); useEffect(() => { // Check onboarding status from API const checkOnboardingStatus = async () => { try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/account`, 'GET'); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/account`, + 'GET' + ); if (!response.hasCompletedOnboarding) { setIsOnboardingOpen(true); } @@ -191,6 +392,8 @@ export default function Dashboard() { }; checkOnboardingStatus(); + + // Don't check for notifications on initial load to prevent showing old notifications on refresh }, []); const handleHideOnboarding = () => { @@ -231,77 +434,103 @@ export default function Dashboard() { - - -
    +
    -
    -
    - {completedTasks && (!completedTasks.profilePicture || !completedTasks.firstChallenge) && ( -
    -
    -

    +
    +
    + {completedTasks && + (!completedTasks.profilePicture || + !completedTasks.firstChallenge) && ( +
    +
    +

    Onboarding Tasks Hide

    -

    Looks like you're new around here. You should try to complete these tasks. Or don't.

    -
    -
    { - window.location.href = "./settings" - }} className={`${completedTasks.profilePicture ? 'bg-green-900/20 border border-green-800' : 'bg-neutral-700/50 hover:bg-neutral-700/70 border border-neutral-600'} transition-colors p-6 rounded-lg cursor-pointer`}> -
    -
    - +

    + Looks like you're new around here. You should try to + complete these tasks. Or don't. +

    +
    +
    { + window.location.href = './settings'; + }} + className={`${ + completedTasks.profilePicture + ? 'border border-green-800 bg-green-900/20' + : 'border border-neutral-600 bg-neutral-700/50 hover:bg-neutral-700/70' + } cursor-pointer rounded-lg p-6 transition-colors`} + > +
    +
    + +
    +

    + Set a profile picture +

    + {completedTasks.profilePicture && ( + + )} +
    +

    + Personalize your account by adding a profile picture +

    -

    Set a profile picture

    - {completedTasks.profilePicture && ( - +
    { + window.location.href = + './challenges/07671f2f-cd67-4f0f-a3d1-9bdea299c59c?onboarding=true'; + }} + className={`${ + completedTasks.firstChallenge + ? 'border border-green-800 bg-green-900/20' + : 'border border-neutral-600 bg-neutral-700/50 hover:bg-neutral-700/70' + } cursor-pointer rounded-lg p-6 transition-colors`} + > +
    +
    + +
    +

    + Complete your first challenge +

    + {completedTasks.firstChallenge && ( + )}
    -

    Personalize your account by adding a profile picture

    -
    -
    { - window.location.href = "./challenges/07671f2f-cd67-4f0f-a3d1-9bdea299c59c?onboarding=true" - }} className={`${completedTasks.firstChallenge ? 'bg-green-900/20 border border-green-800' : 'bg-neutral-700/50 hover:bg-neutral-700/70 border border-neutral-600'} transition-colors p-6 rounded-lg cursor-pointer`}> -
    -
    - +

    + Try solving an entry-level cybersecurity challenge +

    -

    Complete your first challenge

    - {completedTasks.firstChallenge && ( - - )} -
    -

    Try solving an entry-level cybersecurity challenge

    -
    )} -
    +
    {/* Add after the onboarding tasks section */} {timeProgress && ( -
    -

    +
    +

    Weekly Progress -
    +
    beta
    -
    +

    This is an experimental feature.

    @@ -310,162 +539,246 @@ export default function Dashboard() {

    -
    +
    {/* Progress Bar */} -
    +
    {/* Stats */} -
    -
    -

    Time Spent

    -

    - {Math.round(timeProgress.totalMinutesSpent / 60)}h {timeProgress.totalMinutesSpent % 60}m +

    +
    +

    Time Spent

    +

    + {Math.round(timeProgress.totalMinutesSpent / 60)}h{' '} + {timeProgress.totalMinutesSpent % 60}m

    -
    -

    Weekly Goal

    -

    +

    +

    Weekly Goal

    +

    {Math.round(timeProgress.weeklyGoalMinutes / 60)}h

    -
    -

    Days Left

    -

    +

    +

    Days Left

    +

    {timeProgress.daysLeft} days

    {/* Status Message */} -
    -

    +

    +

    {timeProgress.onTrack - ? `You're on track! ${Math.round(timeProgress.progressPercentage)}% of your weekly goal complete.` - : `You're behind schedule. ${Math.round(timeProgress.progressPercentage)}% complete with ${timeProgress.daysLeft} days left.` - } + ? `You're on track! ${Math.round( + timeProgress.progressPercentage + )}% of your weekly goal complete.` + : `You're behind schedule. ${Math.round( + timeProgress.progressPercentage + )}% complete with ${ + timeProgress.daysLeft + } days left.`}

    )} -

    Recommended Challenges

    -
    - {loading ? <> : ( - likes?.length > 0 ? - likes.map((challenge, index) => ) - : <> +

    + Recommended Challenges +

    +
    + {loading ? ( + <> + + + + ) : likes?.length > 0 ? ( + likes.map((challenge, index) => ( + + )) + ) : ( + <> + + + )}
    -
    -
    -

    Popular Challenges

    -
    - {loading ? <> : ( - popular?.length > 0 ? - popular.map((challenge, index) => ) - : <> +
    +
    +

    + Popular Challenges +

    +
    + {loading ? ( + <> + + + + ) : popular?.length > 0 ? ( + popular.map((challenge, index) => ( + + )) + ) : ( + <> + + + )}
    -
    - -
    -

    - +
    +
    +

    + Daily Objectives

    -
    -
      - {objectives && - objectives.map((objective) => +
      +
        + {(objectives && + objectives.map((objective) => (
        - {objective.completed && - || } -
      • {objective.description}
      • + {(objective.completed && ( + + )) || ( + + )} +
      • + {objective.description} +
      • - ) - || <> - - } + ))) || ( + <> + + + )}
    -
    -

    +
    +

    Global Feed

    -
    - -
    - { role == "USER" && -
    -

    +
    + {role == 'USER' && ( +
    +

    Sponsor Messaging

    setShowUpgradeModal(true)} > -
    - - Upgrade to CTFGuide Pro today for just $5/month. +
    + + + Upgrade to CTFGuide Pro today for just{' '} + $5/month. +
    -
    -} -

    + )} +

    Connect with CTFGuide

    -

    - Talk to other CTF players and get help with challenges! + Talk to other CTF players and get help with + challenges!

    -

    +

    Invite Link

    -
    - -
    - +
    -
    - - +
    + + +
    Follow us on X!

    - Stay updated with the latest CTFGuide news and updates! + Stay updated with the latest CTFGuide news and + updates!

    - + Follow @ctfguideapp -
    -
    - -
    -
    - -
    -

    -

    -
    +
    +
    +
    -
    +
    - {showUpgradeModal &&
    -
    -
    -
    + {showUpgradeModal && ( +
    +
    +
    +
    -

    Upgrade to CTFGuide Pro

    - +

    + Upgrade to CTFGuide{' '} + Pro +

    -
    +
    -

    Monthly Subscription

    +

    + Monthly Subscription +

    - Enjoy our core features for free and upgrade to get perks like priority access to terminals, custom container images, customization perks, and more! - + Enjoy our core features for free and upgrade to get perks + like priority access to terminals, custom container + images, customization perks, and more!

    -

    What's included

    +

    + What's included +

      {includedFeatures.map((feature) => (
    • -
    • ))}
    -
    - -
    - -
    -
    -
    -
    } - + )} + ); } diff --git a/src/pages/login.jsx b/src/pages/login.jsx index e2808958..e26117f8 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.jsx @@ -9,6 +9,7 @@ import 'react-toastify/dist/ReactToastify.css'; import AuthFooter from '@/components/auth/AuthFooter'; import { GoogleLogin } from '@react-oauth/google'; import { jwtDecode } from 'jwt-decode'; +import AccountReactivation from '@/components/AccountReactivation'; import { Context } from '@/context'; import { useContext } from 'react'; @@ -21,15 +22,17 @@ export default function Login() { const [password, setPassword] = useState(''); const [accountType, setAccountType] = useState('EMAIL'); // ['EMAIL', 'GOOGLE'] const [showOnboarding, setShowOnboarding] = useState(false); + const [accountSuspended, setAccountSuspended] = useState(false); + const [suspendedAccountData, setSuspendedAccountData] = useState(null); async function handleLoginRequest(requestOptions, isGoogle) { setIsLoading(true); - + try { const url = process.env.NEXT_PUBLIC_API_URL + '/account/login'; const response = await fetch(url, requestOptions); - if(response.status === 404) { - if(isGoogle) { + if (response.status === 404) { + if (isGoogle) { setShowOnboarding(true); return; } @@ -51,13 +54,30 @@ export default function Login() { router.push('/dashboard'); } else { - if(data.status === 500) { - toast.error("There was a problem logging in, try again later"); + // Check if account is suspended + if (data.code === 'ACCOUNT_SUSPENDED') { + setAccountSuspended(true); + + // Use deletion info directly from login response + const deletionInfo = data.deletionInfo || null; + + setSuspendedAccountData({ + email: data.email || email, + message: data.message, + accountType: accountType, + password: accountType === 'EMAIL' ? password : null, + deletionInfo: deletionInfo, // Pass deletion info to reactivation component + }); + return; + } + + if (data.status === 500) { + toast.error('There was a problem logging in, try again later'); } else { toast.error(data.message); } } - } catch(error) { + } catch (error) { console.log(error); } setIsLoading(false); @@ -65,24 +85,24 @@ export default function Login() { const handleLogin = async (e) => { e.preventDefault(); - const requestOptions = { - method: 'POST', - body: JSON.stringify({ email, password, accountType: 'EMAIL'}), - headers: { 'Content-Type': 'application/json' } + const requestOptions = { + method: 'POST', + body: JSON.stringify({ email, password, accountType: 'EMAIL' }), + headers: { 'Content-Type': 'application/json' }, }; setAccountType('EMAIL'); await handleLoginRequest(requestOptions, false); - } + }; async function handleSuccess(data) { const { credential } = data; const decode = jwtDecode(credential); const { email } = decode; setEmail(email); - const requestOptions = { - method: 'POST', - body: JSON.stringify({ email, password: null, accountType: 'GOOGLE'}), - headers: { 'Content-Type': 'application/json' } + const requestOptions = { + method: 'POST', + body: JSON.stringify({ email, password: null, accountType: 'GOOGLE' }), + headers: { 'Content-Type': 'application/json' }, }; setAccountType('GOOGLE'); await handleLoginRequest(requestOptions, true); @@ -93,8 +113,51 @@ export default function Login() { toast.error('Google login failed. Please try again later.'); } - - if(showOnboarding) return + const handleReactivated = (token) => { + if (token) { + // Account was reactivated successfully + document.cookie = `idToken=${token}; SameSite=None; Secure; Path=/`; + router.push('/dashboard'); + } else { + // Go back to login form + setAccountSuspended(false); + setSuspendedAccountData(null); + } + }; + + const handleCancelReactivation = () => { + setAccountSuspended(false); + setSuspendedAccountData(null); + }; + + if (showOnboarding) + return ( + + ); + + if (accountSuspended) { + return ( + <> + + Account Suspended - CTFGuide + +
    + +
    + + ); + } return ( <> @@ -105,30 +168,24 @@ export default function Login() { url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); -
    +
    - -
    - -
    - -
    +
    - @@ -287,7 +366,7 @@ export default function Login() { } function OnBoardingTransition(props) { - console.log('OnboardingTransition') + console.log('OnboardingTransition'); console.log(props); const { email, password, accountType } = props; return ( @@ -301,9 +380,13 @@ function OnBoardingTransition(props) {
    - +
    - ) + ); } diff --git a/src/pages/moderation.jsx b/src/pages/moderation.jsx index a2f802fc..2ed8dc55 100644 --- a/src/pages/moderation.jsx +++ b/src/pages/moderation.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { ArrowRightIcon, QuestionMarkCircleIcon, @@ -8,6 +8,10 @@ import { DocumentTextIcon, ExclamationTriangleIcon, ChartBarIcon, + EllipsisVerticalIcon, + MagnifyingGlassIcon, + ClockIcon, + ChevronDownIcon, } from '@heroicons/react/20/solid'; import Head from 'next/head'; @@ -16,6 +20,7 @@ import { Footer } from '@/components/Footer'; import request from '@/utils/request'; import ViewChallenge from '@/components/moderation/ViewChallenge'; import ViewReport from '@/components/moderation/ViewReport'; +import UserActionModals from '@/components/moderation/UserActionModals'; export default function Moderation() { // Challenge states @@ -25,27 +30,47 @@ export default function Moderation() { const [challengeIsOpen, setChallengeIsOpen] = useState(false); const [bonusPoints, setBonusPoints] = useState(0); - // Report states + // Report states - CONSOLIDATED const [reports, setReports] = useState([]); const [selectedReport, setSelectedReport] = useState(null); const [reportIsOpen, setReportIsOpen] = useState(false); + const [reportStats, setReportStats] = useState(null); + const [reportFilters, setReportFilters] = useState({ + type: '', + status: '', + page: 0, + }); // User management states const [users, setUsers] = useState([]); const [currentUserPage, setCurrentUserPage] = useState(0); const [totalUserPages, setTotalUserPages] = useState(0); const [totalUsers, setTotalUsers] = useState(0); + const [userSearchFilter, setUserSearchFilter] = useState(''); - // Recent activity states - const [recentComments, setRecentComments] = useState([]); - const [recentWriteups, setRecentWriteups] = useState([]); - const [recentLessons, setRecentLessons] = useState([]); + // Suspended accounts states + const [suspendedAccounts, setSuspendedAccounts] = useState([]); + const [suspendedAccountsPage, setSuspendedAccountsPage] = useState(0); + const [totalSuspendedAccounts, setTotalSuspendedAccounts] = useState(0); + const [suspendedAccountsPagination, setSuspendedAccountsPagination] = + useState(null); + const [suspendedSearchFilter, setSuspendedSearchFilter] = useState(''); // Platform stats const [stats, setStats] = useState(null); // Active tab state - const [activeTab, setActiveTab] = useState('overview'); + const [activeTab, setActiveTab] = useState('activity'); + const [activeAccountsTab, setActiveAccountsTab] = useState('suspended'); + const [systemStatus, setSystemStatus] = useState('online'); + const [lastStatusCheck, setLastStatusCheck] = useState(null); + const [isLoadingStatus, setIsLoadingStatus] = useState(false); + const [showMobileMenu, setShowMobileMenu] = useState(false); + + // Account deletion states + const [pendingDeletions, setPendingDeletions] = useState([]); + const [deletionStats, setDeletionStats] = useState(null); + const [deletionsSearchFilter, setDeletionsSearchFilter] = useState(''); // Autocomplete states const [userSuggestions, setUserSuggestions] = useState([]); @@ -59,42 +84,101 @@ export default function Moderation() { // Notification states const [notification, setNotification] = useState(null); + // Modal states for suspend and role (keeping these as they may still be used) + const [showSuspendModal, setShowSuspendModal] = useState(false); + const [showRoleModal, setShowRoleModal] = useState(false); + const [selectedUserForAction, setSelectedUserForAction] = useState(null); + const [suspendForm, setSuspendForm] = useState({ reason: '', duration: '' }); + const [roleForm, setRoleForm] = useState({ role: 'USER' }); + + // User management modal states + const [showUserManagementPopup, setShowUserManagementPopup] = useState(false); + const [selectedUserForManagement, setSelectedUserForManagement] = + useState(null); + + // Warning history states + const [loadingWarningHistory, setLoadingWarningHistory] = useState(false); + const [warningHistory, setWarningHistory] = useState([]); + const warningsLoadedRef = useRef(false); + + // Recent activity states + const [recentActivity, setRecentActivity] = useState({ + comments: [], + writeups: [], + lessons: [], + }); + const [loadingActivity, setLoadingActivity] = useState(false); + const activityLoadedRef = useRef(false); + + // New modal states for custom actions + const [showReactivateModal, setShowReactivateModal] = useState(false); + const [reactivateForm, setReactivateForm] = useState({ reason: '' }); + const [showDetailsModal, setShowDetailsModal] = useState(false); + const [selectedDetails, setSelectedDetails] = useState(null); + // Notification helper function const showNotification = (message, type = 'info') => { const id = Date.now(); setNotification({ id, message, type }); }; + // Helper function to get username and reason from various sources + const getUsernameAndReason = (prefilledUsername = null) => { + let username = prefilledUsername || userSearchQuery || ''; + let reason = ''; + + // Try to get values from DOM elements if they exist + const usernameElement = document.getElementById('username-input'); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + + if (usernameElement && usernameElement.value) { + username = usernameElement.value; + } + if (reasonElement && reasonElement.value) { + reason = reasonElement.value; + } + if (popupReasonElement && popupReasonElement.value) { + reason = popupReasonElement.value; + } + + // Ensure username and reason are strings before calling trim + const safeUsername = typeof username === 'string' ? username.trim() : ''; + const safeReason = typeof reason === 'string' ? reason.trim() : ''; + + return { username: safeUsername, reason: safeReason }; + }; + + // Helper function for challenge operations + const getChallengeIdAndReason = () => { + let challengeId = challengeSearchQuery; + let reason = ''; + + const challengeElement = document.getElementById('challenge-input'); + const reasonElement = document.getElementById('challenge-reason-input'); + + if (challengeElement && challengeElement.value) { + challengeId = challengeElement.value; + } + if (reasonElement && reasonElement.value) { + reason = reasonElement.value; + } + + return { challengeId: challengeId?.trim(), reason: reason?.trim() }; + }; + // Notification component const NotificationModal = () => { if (!notification) return null; - const getNotificationIcon = () => { - switch (notification.type) { - case 'success': - return 'fas fa-check-circle'; - case 'error': - return 'fas fa-exclamation-circle'; - case 'warning': - return 'fas fa-exclamation-triangle'; - default: - return 'fas fa-info-circle'; - } - }; - - const icon = getNotificationIcon(); - return (
    setNotification(null)} /> -
    +
    -
    - -

    {notification.message} @@ -102,7 +186,7 @@ export default function Moderation() {

    @@ -112,22 +196,163 @@ export default function Moderation() { ); }; - // Fetch functions - const fetchReports = async () => { + // ENHANCED AUTOCOMPLETE FUNCTIONS + const fetchUserSuggestions = async (query) => { + if (query.length < 2) { + setUserSuggestions([]); + return; + } + try { - console.log('Fetching reports...'); const response = await request( - process.env.NEXT_PUBLIC_API_URL + '/reports', + `${ + process.env.NEXT_PUBLIC_API_URL + }/admin/users?search=${encodeURIComponent(query)}&limit=15`, 'GET' ); + setUserSuggestions(response.users || []); + } catch (error) { + console.error('Failed to fetch user suggestions:', error); + setUserSuggestions([]); + } + }; + + const fetchChallengeSuggestions = async (query) => { + if (query.length < 2) { + setChallengeSuggestions([]); + return; + } + + try { + // Fetch both pending and approved challenges + const [pendingResponse, approvedResponse] = await Promise.all([ + request(`${process.env.NEXT_PUBLIC_API_URL}/pending`, 'GET'), + request( + `${ + process.env.NEXT_PUBLIC_API_URL + }/challenges?search=${encodeURIComponent(query)}&limit=10`, + 'GET' + ), + ]); + + const pendingChallenges = (pendingResponse || []) + .filter((challenge) => + challenge.title.toLowerCase().includes(query.toLowerCase()) + ) + .map((challenge) => ({ ...challenge, status: 'Pending' })); + + const approvedChallenges = (approvedResponse?.challenges || []).map( + (challenge) => ({ ...challenge, status: 'Approved' }) + ); + + const allChallenges = [...pendingChallenges, ...approvedChallenges]; + setChallengeSuggestions(allChallenges.slice(0, 10)); + } catch (error) { + console.error('Failed to fetch challenge suggestions:', error); + setChallengeSuggestions([]); + } + }; + + const handleUserInputChange = (e) => { + const value = e.target.value; + setUserSearchQuery(value); + + if (value.length >= 1) { + // Clear previous timeout + clearTimeout(window.userSearchTimeout); + + // Set new timeout for debouncing + window.userSearchTimeout = setTimeout(() => { + fetchUserSuggestions(value); + setShowUserSuggestions(true); + }, 300); + } else { + setUserSuggestions([]); + setShowUserSuggestions(false); + } + }; + + const handleChallengeInputChange = (e) => { + const value = e.target.value; + setChallengeSearchQuery(value); + + if (value.length >= 1) { + // Clear previous timeout + clearTimeout(window.challengeSearchTimeout); + + // Set new timeout for debouncing + window.challengeSearchTimeout = setTimeout(() => { + fetchChallengeSuggestions(value); + setShowChallengeSuggestions(true); + }, 300); + } else { + setChallengeSuggestions([]); + setShowChallengeSuggestions(false); + } + }; + + const handleUserSelection = (user) => { + setUserSearchQuery(user.username); + setShowUserSuggestions(false); + setUserSuggestions([]); + }; + + const handleChallengeSelection = (challenge) => { + setChallengeSearchQuery(challenge.title); + setShowChallengeSuggestions(false); + setChallengeSuggestions([]); + }; + + // UNIFIED FETCH FUNCTIONS + const fetchReports = async (filters = {}) => { + try { + console.log('Fetching reports with filters:', filters); + + const queryParams = new URLSearchParams(); + if (filters.type) queryParams.append('type', filters.type); + if (filters.status) queryParams.append('status', filters.status); + if (filters.page) queryParams.append('page', filters.page.toString()); + + const url = `${process.env.NEXT_PUBLIC_API_URL}/reports${ + queryParams.toString() ? '?' + queryParams.toString() : '' + }`; + const response = await request(url, 'GET'); + console.log('Reports response:', response); - setReports(response || []); + + // Handle different response formats + let reportsData = []; + if (Array.isArray(response)) { + reportsData = response; + } else if (response && Array.isArray(response.reports)) { + reportsData = response.reports; + } else if (response && response.data && Array.isArray(response.data)) { + reportsData = response.data; + } + + setReports(reportsData); + return response; } catch (error) { console.error('Failed to fetch reports:', error); setReports([]); + return { reports: [] }; + } + }; + + const fetchReportStats = async () => { + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/reports/stats`, + 'GET' + ); + setReportStats(response); + } catch (error) { + console.error('Failed to fetch report stats:', error); + setReportStats(null); } }; + // Fetch functions const fetchPendingChallenges = async () => { try { console.log('Fetching pending challenges...'); @@ -143,70 +368,116 @@ export default function Moderation() { } }; - const fetchUsers = async (page = 0) => { + const fetchUsers = async (page = 0, searchQuery = '') => { + try { + const response = await request( + `${ + process.env.NEXT_PUBLIC_API_URL + }/admin/users?page=${page}&size=25&search=${encodeURIComponent( + searchQuery + )}&sort=createdAt:desc`, + 'GET' + ); + if (response.result || response.users) { + // Sort users by createdAt descending (newest first) + const usersSorted = (response.result || response.users).sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt) + ); + setUsers(usersSorted); + setTotalUsers(response.totalEntries || 0); + setTotalUserPages(Math.min(response.lastPage + 1 || 0, 1000)); + setCurrentUserPage(page); + } else { + setUsers([]); + setTotalUsers(0); + setTotalUserPages(0); + } + } catch (error) { + console.error('Failed to fetch users:', error); + showNotification('Failed to fetch users. Please try again.', 'error'); + setUsers([]); + } + }; + + const fetchSuspendedAccounts = async (page = 0, searchQuery = '') => { try { - console.log('Fetching users for page:', page); - const url = `${process.env.NEXT_PUBLIC_API_URL}/admin/users?page=${page}&size=30&order=createdAt&ordertype=desc`; + console.log( + 'Fetching suspended accounts for page:', + page, + 'with search:', + searchQuery + ); + + const queryParams = new URLSearchParams(); + queryParams.append('page', page.toString()); + queryParams.append('size', '20'); + if (searchQuery.trim()) { + queryParams.append('search', searchQuery.trim()); + } + + const url = `${ + process.env.NEXT_PUBLIC_API_URL + }/admin/suspended-accounts?${queryParams.toString()}`; console.log('Fetching from URL:', url); const response = await request(url, 'GET'); - console.log('Users API response:', response); + console.log('Suspended accounts API response:', response); if (response === null) { console.error('Request returned null - likely authentication issue'); - setUsers([]); - setTotalUserPages(0); - setTotalUsers(0); - setCurrentUserPage(0); + setSuspendedAccounts([]); + setSuspendedAccountsPagination(null); + setTotalSuspendedAccounts(0); + setSuspendedAccountsPage(0); return; } - if (response && response.result) { - console.log('Setting users data:', response.result.length, 'users'); - setUsers(response.result || []); - setTotalUserPages((response.lastPage || 0) + 1); - setTotalUsers(response.totalEntries || 0); - setCurrentUserPage(page); + if (response && response.success) { + console.log( + 'Setting suspended accounts data:', + response.users.length, + 'accounts' + ); + setSuspendedAccounts(response.users || []); + setSuspendedAccountsPagination(response.pagination); + setTotalSuspendedAccounts(response.pagination?.totalCount || 0); + setSuspendedAccountsPage(page); } else { console.error('Invalid response format:', response); if (response && response.error) { console.error('API Error:', response.error); } - setUsers([]); - setTotalUserPages(0); - setTotalUsers(0); - setCurrentUserPage(0); + setSuspendedAccounts([]); + setSuspendedAccountsPagination(null); + setTotalSuspendedAccounts(0); + setSuspendedAccountsPage(0); } } catch (error) { - console.error('Failed to fetch users:', error); - setUsers([]); - setTotalUserPages(0); - setTotalUsers(0); - setCurrentUserPage(0); + console.error('Failed to fetch suspended accounts:', error); + setSuspendedAccounts([]); + setSuspendedAccountsPagination(null); + setTotalSuspendedAccounts(0); + setSuspendedAccountsPage(0); } }; - const fetchRecentActivity = async () => { + const handleProcessDeletion = async (requestId) => { try { - console.log('Fetching recent activity...'); - const [commentsRes, writeupsRes, lessonsRes] = await Promise.all([ - request(`${process.env.NEXT_PUBLIC_API_URL}/activity/comments`, 'GET'), - request(`${process.env.NEXT_PUBLIC_API_URL}/activity/writeups`, 'GET'), - request(`${process.env.NEXT_PUBLIC_API_URL}/activity/lessons`, 'GET'), - ]); - console.log('Activity responses:', { - commentsRes, - writeupsRes, - lessonsRes, - }); - setRecentComments(commentsRes || []); - setRecentWriteups(writeupsRes || []); - setRecentLessons(lessonsRes || []); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/account-deletion/admin/process/${requestId}`, + 'POST' + ); + + if (response.success) { + showNotification('Account deletion processed automatically', 'success'); + await fetchPendingDeletions(); + await fetchDeletionStats(); + } else { + throw new Error(response.error || 'Failed to process deletion'); + } } catch (error) { - console.error('Failed to fetch recent activity:', error); - setRecentComments([]); - setRecentWriteups([]); - setRecentLessons([]); + console.error('Error processing deletion:', error); + showNotification(`Failed to process deletion: ${error.message}`, 'error'); } }; @@ -225,133 +496,52 @@ export default function Moderation() { } }; - // Autocomplete functions - const fetchUserSuggestions = async (query) => { - if (!query || query.length < 2) { - setUserSuggestions([]); - setShowUserSuggestions(false); - return; - } - + // Fetch account deletion requests + const fetchPendingDeletions = async (searchQuery = '') => { try { - const response = await request( - `${ - process.env.NEXT_PUBLIC_API_URL - }/admin/users?search=${encodeURIComponent(query)}&size=10`, - 'GET' - ); + console.log('Fetching pending deletions with search:', searchQuery); - if (response && response.result) { - setUserSuggestions(response.result); - setShowUserSuggestions(true); - } else { - setUserSuggestions([]); - setShowUserSuggestions(false); + const queryParams = new URLSearchParams(); + if (searchQuery.trim()) { + queryParams.append('search', searchQuery.trim()); } - } catch (error) { - console.error('Failed to fetch user suggestions:', error); - setUserSuggestions([]); - setShowUserSuggestions(false); - } - }; - - const fetchChallengeSuggestions = async (query) => { - if (!query || query.length < 2) { - setChallengeSuggestions([]); - setShowChallengeSuggestions(false); - return; - } - try { - // Try to fetch both pending and approved challenges - const [pendingRes, approvedRes] = await Promise.all([ - request(`${process.env.NEXT_PUBLIC_API_URL}/pending`, 'GET'), - request( - `${ - process.env.NEXT_PUBLIC_API_URL - }/challenges?search=${encodeURIComponent(query)}&limit=10`, - 'GET' - ), - ]); - - let suggestions = []; - - // Add pending challenges - if (pendingRes && Array.isArray(pendingRes)) { - const filteredPending = pendingRes.filter( - (challenge) => - challenge.title.toLowerCase().includes(query.toLowerCase()) || - challenge.id.toLowerCase().includes(query.toLowerCase()) - ); - suggestions = [...suggestions, ...filteredPending]; - } + const url = `${ + process.env.NEXT_PUBLIC_API_URL + }/account-deletion/admin/pending${ + queryParams.toString() ? '?' + queryParams.toString() : '' + }`; - // Add approved challenges - if (approvedRes && Array.isArray(approvedRes)) { - const filteredApproved = approvedRes.filter( - (challenge) => - challenge.title.toLowerCase().includes(query.toLowerCase()) || - challenge.id.toLowerCase().includes(query.toLowerCase()) - ); - suggestions = [...suggestions, ...filteredApproved]; + const response = await request(url, 'GET'); + console.log('Pending deletions response:', response); + if (response.success) { + setPendingDeletions(response.data || []); } - - // Remove duplicates and limit to 10 - const uniqueSuggestions = suggestions - .filter( - (challenge, index, self) => - index === self.findIndex((c) => c.id === challenge.id) - ) - .slice(0, 10); - - setChallengeSuggestions(uniqueSuggestions); - setShowChallengeSuggestions(uniqueSuggestions.length > 0); } catch (error) { - console.error('Failed to fetch challenge suggestions:', error); - setChallengeSuggestions([]); - setShowChallengeSuggestions(false); - } - }; - - // Handle autocomplete selection - const handleUserSelection = (user) => { - setUserSearchQuery(user.username); - document.getElementById('usernameInput').value = user.username; - setShowUserSuggestions(false); - }; - - const handleChallengeSelection = (challenge) => { - setChallengeSearchQuery(challenge.id); - document.getElementById('challengeIdInput').value = challenge.id; - setShowChallengeSuggestions(false); - }; - - // Handle input changes - const handleUserInputChange = (e) => { - const value = e.target.value; - setUserSearchQuery(value); - - if (value.length === 0) { - setUserSuggestions([]); - setShowUserSuggestions(false); - } else { - fetchUserSuggestions(value); + console.error('Failed to fetch pending deletions:', error); + setPendingDeletions([]); } }; - const handleChallengeInputChange = (e) => { - const value = e.target.value; - setChallengeSearchQuery(value); - - if (value.length === 0) { - setChallengeSuggestions([]); - setShowChallengeSuggestions(false); - } else { - fetchChallengeSuggestions(value); + // Fetch deletion statistics + const fetchDeletionStats = async () => { + try { + console.log('Fetching deletion stats...'); + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/account-deletion/admin/stats`, + 'GET' + ); + console.log('Deletion stats response:', response); + if (response.success) { + setDeletionStats(response.data); + } + } catch (error) { + console.error('Failed to fetch deletion stats:', error); + setDeletionStats(null); } }; - // Handle functions + // Autocomplete functions const handleSelectChallenge = (id) => { setSelectedChallenges((prev) => { if (prev.includes(id)) { @@ -363,16 +553,28 @@ export default function Moderation() { }; const resetFields = () => { - document.getElementById('usernameInput').value = ''; - document.getElementById('reasonInput').value = ''; + const usernameInput = document.getElementById('username-input'); + const reasonInput = document.getElementById('reason-input'); + const popupReasonInput = document.getElementById('popupReasonInput'); + + if (usernameInput) usernameInput.value = ''; + if (reasonInput) reasonInput.value = ''; + if (popupReasonInput) popupReasonInput.value = ''; + setUserSearchQuery(''); setShowUserSuggestions(false); setUserSuggestions([]); }; const resetChallengeFields = () => { - document.getElementById('challengeIdInput').value = ''; - document.getElementById('challengeReasonInput').value = ''; + const challengeInput = document.getElementById('challenge-input'); + const challengeReasonInput = document.getElementById( + 'challenge-reason-input' + ); + + if (challengeInput) challengeInput.value = ''; + if (challengeReasonInput) challengeReasonInput.value = ''; + setChallengeSearchQuery(''); setShowChallengeSuggestions(false); setChallengeSuggestions([]); @@ -393,52 +595,80 @@ export default function Moderation() { }; const handleDeleteChallenge = async () => { - const challengeId = document.getElementById('challengeIdInput').value; - const reason = document.getElementById('challengeReasonInput').value; + const { challengeId, reason } = getChallengeIdAndReason(); - try { - const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${challengeId}/deleteChallenge`, - 'POST', + if (!challengeId) { + showNotification('Please enter a challenge ID to delete.', 'error'); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for deleting this challenge.', + 'error' + ); + return; + } + + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/challenges/${challengeId}/delete`, + 'POST', { reason } ); - if (response.success) { - showNotification('Challenge deleted successfully!', 'success'); - } else { - showNotification('Failed to delete challenge.', 'error'); - } + showNotification( + `Challenge ${challengeId} has been deleted successfully.`, + 'success' + ); - resetChallengeFields(); - } catch (err) { - console.log(err); + // Clear inputs + setChallengeSearchQuery(''); + const reasonElement = document.getElementById('challenge-reason-input'); + if (reasonElement) reasonElement.value = ''; + } catch (error) { + console.error('Failed to delete challenge:', error); showNotification( - 'An error occurred while deleting the challenge.', + `Failed to delete challenge ${challengeId}. Please try again.`, 'error' ); } }; const handleUnapproveChallenge = async () => { - const challengeId = document.getElementById('challengeIdInput').value; - const reason = document.getElementById('challengeReasonInput').value; + const { challengeId, reason } = getChallengeIdAndReason(); + + if (!challengeId) { + showNotification('Please enter a challenge ID to unapprove.', 'error'); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for unapproving this challenge.', + 'error' + ); + return; + } try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${challengeId}/unapproveChallenge`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/challenges/${challengeId}/unapprove`, 'POST', { reason } ); - if (response.success) { - showNotification('Challenge unapproved successfully!', 'success'); - } else { - showNotification('Failed to unapprove challenge.', 'error'); - } + showNotification( + `Challenge ${challengeId} has been unapproved successfully.`, + 'success' + ); - resetChallengeFields(); - } catch (err) { - console.log(err); + // Clear inputs + setChallengeSearchQuery(''); + const reasonElement = document.getElementById('challenge-reason-input'); + if (reasonElement) reasonElement.value = ''; + } catch (error) { + console.error('Failed to unapprove challenge:', error); showNotification( - 'An error occurred while unapproving the challenge.', + `Failed to unapprove challenge ${challengeId}. Please try again.`, 'error' ); } @@ -464,31 +694,49 @@ export default function Moderation() { } }; - const handleResetPFP = async () => { - const username = document.getElementById('usernameInput').value; - const reason = document.getElementById('reasonInput').value; + const handleResetPFP = async (prefilledUsername = null) => { + const { username, reason } = getUsernameAndReason(prefilledUsername); if (!username) { - showNotification('Please enter a username.', 'warning'); + showNotification( + 'Please enter a username to reset profile picture.', + 'error' + ); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for resetting the profile picture.', + 'error' + ); return; } try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetPFP`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${username}/reset-pfp`, 'POST', { reason } ); - if (response.success) { - showNotification('Profile picture reset successfully!', 'success'); - } else { - showNotification('Failed to reset profile picture.', 'error'); - } - resetFields(); + showNotification( + `Profile picture reset for ${username} successfully.`, + 'success' + ); + + // Refresh users list + fetchUsers(currentUserPage, userSearchFilter); + + // Clear inputs + setUserSearchQuery(''); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + if (reasonElement) reasonElement.value = ''; + if (popupReasonElement) popupReasonElement.value = ''; } catch (error) { - console.error(error); + console.error('Failed to reset profile picture:', error); showNotification( - 'An error occurred while resetting the profile picture.', + `Failed to reset profile picture for ${username}. Please try again.`, 'error' ); } @@ -529,240 +777,644 @@ export default function Moderation() { } }; - const handleResetBanner = async () => { - const username = document.getElementById('usernameInput').value; - const reason = document.getElementById('reasonInput').value; + const handleResetBanner = async (prefilledUsername = null) => { + const { username, reason } = getUsernameAndReason(prefilledUsername); if (!username) { - showNotification('Please enter a username.', 'warning'); + showNotification('Please enter a username to reset banner.', 'error'); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for resetting the banner.', + 'error' + ); return; } try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetBanner`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${username}/reset-banner`, 'POST', { reason } ); - if (response.success) { - showNotification('Banner reset successfully!', 'success'); - } else { - showNotification('Failed to reset banner.', 'error'); - } - resetFields(); - } catch (err) { - console.log(err); + showNotification(`Banner reset for ${username} successfully.`, 'success'); + + // Refresh users list + fetchUsers(currentUserPage, userSearchFilter); + + // Clear inputs + setUserSearchQuery(''); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + if (reasonElement) reasonElement.value = ''; + if (popupReasonElement) popupReasonElement.value = ''; + } catch (error) { + console.error('Failed to reset banner:', error); showNotification( - 'An error occurred while resetting the banner.', + `Failed to reset banner for ${username}. Please try again.`, 'error' ); } }; - const handleDisableAccount = async () => { - const username = document.getElementById('usernameInput').value; - const reason = document.getElementById('reasonInput').value; + const handleDisableAccount = async (prefilledUsername = null) => { + const { username, reason } = getUsernameAndReason(prefilledUsername); if (!username) { - showNotification('Please enter a username.', 'warning'); + showNotification('Please enter a username to disable.', 'error'); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for disabling this account.', + 'error' + ); return; } try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/disableAccount`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${username}/disable`, 'POST', { reason } ); - if (response.success) { - showNotification('Account disabled successfully!', 'success'); - } else { - showNotification('Failed to disable account.', 'error'); - } - resetFields(); - } catch (err) { - console.log(err); showNotification( - 'An error occurred while disabling the account.', + `Account ${username} has been disabled successfully.`, + 'success' + ); + + // Refresh users list + fetchUsers(currentUserPage, userSearchFilter); + + // Clear inputs + setUserSearchQuery(''); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + if (reasonElement) reasonElement.value = ''; + if (popupReasonElement) popupReasonElement.value = ''; + } catch (error) { + console.error('Failed to disable account:', error); + showNotification( + `Failed to disable account ${username}. Please try again.`, 'error' ); } }; - const handleResetBio = async () => { - const username = document.getElementById('usernameInput').value; - const reason = document.getElementById('reasonInput').value; + const handleResetBio = async (prefilledUsername = null) => { + const { username, reason } = getUsernameAndReason(prefilledUsername); if (!username) { - showNotification('Please enter a username.', 'warning'); + showNotification('Please enter a username to reset bio.', 'error'); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for resetting the bio.', + 'error' + ); return; } try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/resetBio`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${username}/reset-bio`, 'POST', { reason } ); - if (response.success) { - showNotification('Bio reset successfully!', 'success'); - } else { - showNotification('Failed to reset bio.', 'error'); - } - resetFields(); - } catch (err) { - console.log(err); - showNotification('An error occurred while resetting the bio.', 'error'); + showNotification(`Bio reset for ${username} successfully.`, 'success'); + + // Refresh users list + fetchUsers(currentUserPage, userSearchFilter); + + // Clear inputs + setUserSearchQuery(''); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + if (reasonElement) reasonElement.value = ''; + if (popupReasonElement) popupReasonElement.value = ''; + } catch (error) { + console.error('Failed to reset bio:', error); + showNotification( + `Failed to reset bio for ${username}. Please try again.`, + 'error' + ); } }; - const handleEnableAccount = async () => { - const username = document.getElementById('usernameInput').value; - const reason = document.getElementById('reasonInput').value; + const handleEnableAccount = async (prefilledUsername = null) => { + const { username, reason } = getUsernameAndReason(prefilledUsername); if (!username) { - showNotification('Please enter a username.', 'warning'); + showNotification('Please enter a username to enable.', 'error'); + return; + } + + if (!reason) { + showNotification( + 'Please provide a reason for enabling this account.', + 'error' + ); return; } try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/enableAccount`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${username}/enable`, 'POST', { reason } ); - if (response.success) { - showNotification('Account enabled successfully!', 'success'); + showNotification( + `Account ${username} has been enabled successfully.`, + 'success' + ); + + // Refresh users list + fetchUsers(currentUserPage, userSearchFilter); + + // Clear inputs + setUserSearchQuery(''); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + if (reasonElement) reasonElement.value = ''; + if (popupReasonElement) popupReasonElement.value = ''; + } catch (error) { + console.error('Failed to enable account:', error); + showNotification( + `Failed to enable account ${username}. Please try again.`, + 'error' + ); + } + }; + + // Removed duplicate handleWarnUser function - using the new one with userId parameter + + // User pagination handlers + const handleNextUserPage = () => { + if (currentUserPage < totalUserPages - 1) { + fetchUsers(currentUserPage + 1); + } + }; + + const handlePrevUserPage = () => { + if (currentUserPage > 0) { + fetchUsers(currentUserPage - 1); + } + }; + + // Reactivation modal handlers + const handleReactivateAccount = async (userId, username) => { + setSelectedUserForAction({ userId, username }); + setReactivateForm({ reason: '' }); + setShowReactivateModal(true); + }; + + const submitReactivation = async () => { + const reason = + reactivateForm.reason.trim() || 'Administrative reactivation'; + + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${selectedUserForAction.userId}/reactivate`, + 'POST', + { reason } + ); + + if (response && response.success) { + showNotification( + `Account ${selectedUserForAction.username} has been reactivated successfully.`, + 'success' + ); + setShowReactivateModal(false); + fetchSuspendedAccounts(suspendedAccountsPage, suspendedSearchFilter); } else { - showNotification('Failed to enable account.', 'error'); + throw new Error(response?.error || 'Failed to reactivate account'); } - resetFields(); - } catch (err) { - console.log(err); + } catch (error) { + console.error('Failed to reactivate account:', error); showNotification( - 'An error occurred while enabling the account.', + `Failed to reactivate account: ${error.message}`, 'error' ); } }; - const handleWarnUser = async () => { - const username = document.getElementById('usernameInput').value; - const reason = document.getElementById('reasonInput').value; + // Handle user suspension - open modal + const handleSuspendUser = async (userId, username) => { + setSelectedUserForAction({ userId, username }); + setSuspendForm({ reason: '', duration: '' }); + setShowSuspendModal(true); + }; - if (!username) { - showNotification('Please enter a username.', 'warning'); + // Submit suspension form + const submitSuspension = async () => { + if (!suspendForm.reason.trim()) { + showNotification('Please enter a reason for suspension.', 'error'); return; } + const duration = suspendForm.duration + ? parseInt(suspendForm.duration) + : null; + try { const response = await request( - `${process.env.NEXT_PUBLIC_API_URL}/admin/${username}/warnUser`, + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${selectedUserForAction.userId}/suspend`, 'POST', - { reason } + { reason: suspendForm.reason, duration } ); - if (response.success) { - showNotification('User warned successfully!', 'success'); + + if (response && response.success) { + showNotification( + `User ${selectedUserForAction.username} has been suspended successfully.`, + 'success' + ); + setShowSuspendModal(false); + fetchSuspendedAccounts(suspendedAccountsPage, suspendedSearchFilter); } else { - showNotification('Failed to warn user.', 'error'); + throw new Error(response?.error || 'Failed to suspend user'); } - resetFields(); - } catch (err) { - console.log(err); - showNotification('An error occurred while warning the user.', 'error'); + } catch (error) { + console.error('Failed to suspend user:', error); + showNotification(`Failed to suspend user: ${error.message}`, 'error'); } }; - // User pagination handlers - const handleNextUserPage = () => { - if (currentUserPage < totalUserPages - 1) { - fetchUsers(currentUserPage + 1); + // Function to refresh warning history + const refreshWarningHistory = async () => { + setLoadingWarningHistory(true); + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/admin/warnings`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}`, + }, + } + ); + if (response.ok) { + const data = await response.json(); + setWarningHistory(data.warnings || []); + } else { + console.error('Failed to fetch warnings:', response.status); + } + } catch (error) { + console.error('Error fetching warnings:', error); + } finally { + setLoadingWarningHistory(false); } }; - const handlePrevUserPage = () => { - if (currentUserPage > 0) { - fetchUsers(currentUserPage - 1); + const handleWarnUser = async (prefilledUsername = null) => { + const { username, reason } = getUsernameAndReason(prefilledUsername); + + if (!username) { + showNotification('Please enter a username to warn.', 'error'); + return; } - }; - // Initial data fetch - useEffect(() => { - console.log('=== MODERATION PANEL INITIALIZING ==='); - console.log('API URL:', process.env.NEXT_PUBLIC_API_URL); - console.log('Starting data fetch sequence...'); + if (!reason) { + showNotification( + 'Please provide a reason for warning this user.', + 'error' + ); + return; + } - const fetchAllData = async () => { - try { - console.log('1. Fetching reports...'); - await fetchReports(); + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${username}/warn`, + 'POST', + { reason, severity: 'LOW' } + ); - console.log('2. Fetching pending challenges...'); - await fetchPendingChallenges(); + if (response && response.success) { + showNotification( + `Warning issued to ${username} successfully.`, + 'success' + ); - console.log('3. Fetching users...'); - await fetchUsers(); + // Refresh users list + fetchUsers(currentUserPage, userSearchFilter); - console.log('4. Fetching recent activity...'); - await fetchRecentActivity(); + // Refresh warning history + refreshWarningHistory(); - console.log('5. Fetching stats...'); - await fetchStats(); + // Trigger notification check for the warned user + window.dispatchEvent( + new CustomEvent('warningIssued', { + detail: { username, reason }, + }) + ); - console.log('=== ALL DATA FETCH COMPLETE ==='); - } catch (error) { - console.error('Error in data fetch sequence:', error); + // Clear inputs + setUserSearchQuery(''); + const reasonElement = document.getElementById('reason-input'); + const popupReasonElement = document.getElementById('popupReasonInput'); + if (reasonElement) reasonElement.value = ''; + if (popupReasonElement) popupReasonElement.value = ''; + } else { + throw new Error(response?.error || 'Failed to issue warning'); } - }; + } catch (error) { + console.error('Failed to issue warning:', error); + showNotification( + `Failed to issue warning to ${username}. Please try again.`, + 'error' + ); + } + }; - fetchAllData(); - }, []); + // Handle role change - open modal + const handleRoleChange = async (userId, username) => { + setSelectedUserForAction({ userId, username }); + setRoleForm({ role: 'USER' }); + setShowRoleModal(true); + }; - // Handle clicking outside autocomplete dropdowns - useEffect(() => { - const handleClickOutside = (event) => { - const userInput = document.getElementById('usernameInput'); - const challengeInput = document.getElementById('challengeIdInput'); + // Submit role change form + const submitRoleChange = async () => { + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${selectedUserForAction.userId}/role`, + 'POST', + { role: roleForm.role } + ); - if ( - userInput && - !userInput.contains(event.target) && - !event.target.closest('.user-suggestions') - ) { - setShowUserSuggestions(false); + if (response && response.success) { + showNotification( + `User ${selectedUserForAction.username} role updated to ${roleForm.role}.`, + 'success' + ); + setShowRoleModal(false); + fetchSuspendedAccounts(suspendedAccountsPage, suspendedSearchFilter); + } else { + throw new Error(response?.error || 'Failed to update role'); } + } catch (error) { + console.error('Failed to update role:', error); + showNotification(`Failed to update role: ${error.message}`, 'error'); + } + }; - if ( - challengeInput && - !challengeInput.contains(event.target) && - !event.target.closest('.challenge-suggestions') - ) { - setShowChallengeSuggestions(false); - } - }; + // View user history + const handleViewUserHistory = async (userId, username) => { + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/users/${userId}/history`, + 'GET' + ); - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); + if (response) { + const { suspensions, warnings } = response; + + let historyText = `=== ${username} History ===\n\n`; + + if (suspensions.length > 0) { + historyText += 'SUSPENSION HISTORY:\n'; + suspensions.forEach((s, i) => { + historyText += `${i + 1}. ${new Date( + s.createdAt + ).toLocaleString()}\n`; + historyText += ` Reason: ${s.reason}\n`; + historyText += ` By: ${s.suspendedBy}\n`; + historyText += ` Status: ${s.isActive ? 'Active' : 'Resolved'}\n`; + if (s.reactivatedBy) { + historyText += ` Reactivated by: ${ + s.reactivatedBy + } on ${new Date(s.reactivatedAt).toLocaleString()}\n`; + historyText += ` Reactivation reason: ${s.reactivationReason}\n`; + } + historyText += '\n'; + }); + } + + if (warnings.length > 0) { + historyText += 'WARNING HISTORY:\n'; + warnings.forEach((w, i) => { + historyText += `${i + 1}. ${new Date( + w.createdAt + ).toLocaleString()}\n`; + historyText += ` Reason: ${w.reason}\n`; + historyText += ` Severity: ${w.severity}\n`; + historyText += ` By: ${w.warnedBy}\n\n`; + }); + } + + if (suspensions.length === 0 && warnings.length === 0) { + historyText += 'No suspension or warning history found.'; + } + + alert(historyText); + } + } catch (error) { + console.error('Failed to fetch user history:', error); + showNotification( + `Failed to fetch user history: ${error.message}`, + 'error' + ); + } + }; + + const reactivateAccount = async (userId, reason = '') => { + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/admin/reactivate-account/${userId}`, + 'POST', + { reason } + ); + + if (response && response.success) { + // Refresh the suspended accounts list + fetchSuspendedAccounts(suspendedAccountsPage); + return { success: true, message: response.message }; + } else { + throw new Error(response?.error || 'Failed to reactivate account'); + } + } catch (error) { + console.error('Failed to reactivate account:', error); + return { success: false, error: error.message }; + } + }; + + // Report management handlers - FIXED + const handleUpdateReportStatus = async ( + reportId, + status, + resolution = '' + ) => { + try { + const response = await request( + `${process.env.NEXT_PUBLIC_API_URL}/reports/${reportId}/status`, + 'PUT', + { status, resolution } + ); + + if (response && response.success) { + showNotification('Report status updated successfully', 'success'); + await fetchReports(reportFilters); + await fetchReportStats(); + } else { + throw new Error(response?.error || 'Failed to update report status'); + } + } catch (error) { + console.error('Error updating report status:', error); + showNotification( + error.message || 'Failed to update report status', + 'error' + ); + } + }; + + // Initial data fetch + useEffect(() => { + console.log('=== MODERATION PANEL INITIALIZING ==='); + console.log('API URL:', process.env.NEXT_PUBLIC_API_URL); + console.log('Starting data fetch sequence...'); + + const fetchAllData = async () => { + try { + console.log('1. Fetching reports...'); + await fetchReports(); + + console.log('2. Fetching pending challenges...'); + await fetchPendingChallenges(); + + console.log('3. Fetching users...'); + await fetchUsers(0, ''); + + console.log('4. Fetching recent activity...'); + await fetchRecentActivity(); + + console.log('5. Fetching stats...'); + await fetchStats(); + + console.log('6. Fetching pending deletions...'); + await fetchPendingDeletions(); + + console.log('7. Fetching deletion stats...'); + await fetchDeletionStats(); + + console.log('8. Fetching report stats...'); + await fetchReportStats(); + + console.log('9. Fetching warning history...'); + await refreshWarningHistory(); + + console.log('=== ALL DATA FETCH COMPLETE ==='); + } catch (error) { + console.error('Error in data fetch sequence:', error); + } + }; + + fetchAllData(); + }, []); + + // Handle clicking outside autocomplete dropdowns and escape key + useEffect(() => { + const handleClickOutside = (event) => { + const userInput = document.getElementById('username-input'); + const challengeInput = document.getElementById('challenge-input'); + + if ( + userInput && + !userInput.contains(event.target) && + !event.target.closest('.user-suggestions') + ) { + setShowUserSuggestions(false); + } + + if ( + challengeInput && + !challengeInput.contains(event.target) && + !event.target.closest('.challenge-suggestions') + ) { + setShowChallengeSuggestions(false); + } + }; + + const handleKeyDown = (event) => { + if (event.key === 'Escape') { + setShowUserManagementPopup(false); + setShowUserSuggestions(false); + setShowChallengeSuggestions(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleKeyDown); + }; + }, []); + + // Load data when tab becomes active + useEffect(() => { + if (activeTab === 'reports' && reports.length === 0) { + fetchReports(reportFilters); + } + if ( + activeTab === 'accounts' && + activeAccountsTab === 'suspended' && + suspendedAccounts.length === 0 + ) { + fetchSuspendedAccounts(0, suspendedSearchFilter); + } + if (activeTab === 'users' && users.length === 0) { + setCurrentUserPage(0); + fetchUsers(0, ''); + } + if ( + activeTab === 'accounts' && + activeAccountsTab === 'deleted' && + pendingDeletions.length === 0 + ) { + fetchPendingDeletions(deletionsSearchFilter); + } + }, [activeTab, activeAccountsTab]); + + // System status monitoring - client side only + useEffect(() => { + // Only run on client side to avoid hydration errors + if (typeof window !== 'undefined') { + // Set initial timestamp + setLastStatusCheck(new Date()); + + // Initial check + checkSystemStatus(); + + // Check every 30 seconds + const statusInterval = setInterval(checkSystemStatus, 30000); + + return () => clearInterval(statusInterval); + } + }, []); // Tab navigation component const TabButton = ({ id, label, icon: Icon, count }) => ( ); + // Check system status + const checkSystemStatus = async () => { + try { + setIsLoadingStatus(true); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout + + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/health`, + { + method: 'GET', + signal: controller.signal, + } + ); + + clearTimeout(timeoutId); + + if (response.ok) { + const data = await response.json(); + setSystemStatus('online'); + } else if (response.status >= 500) { + setSystemStatus('degraded'); + } else { + setSystemStatus('degraded'); + } + } catch (error) { + if (error.name === 'AbortError') { + setSystemStatus('offline'); + } else { + setSystemStatus('degraded'); + } + console.warn('System status check failed:', error.message); + } finally { + setIsLoadingStatus(false); + if (typeof window !== 'undefined') { + setLastStatusCheck(new Date()); + } + } + }; + + // Load warning history when warnings tab is accessed + useEffect(() => { + if ( + activeTab === 'warnings' && + !warningsLoadedRef.current && + !loadingWarningHistory + ) { + loadWarningHistory(); + } + }, [activeTab, loadingWarningHistory]); + + // Load recent activity when activity tab is accessed + useEffect(() => { + if ( + activeTab === 'activity' && + !loadingActivity && + !activityLoadedRef.current + ) { + fetchRecentActivity(); + activityLoadedRef.current = true; + } + }, [activeTab]); + + // Close mobile menu when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (showMobileMenu && !event.target.closest('.mobile-menu-container')) { + setShowMobileMenu(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [showMobileMenu]); + + const loadWarningHistory = async () => { + if (loadingWarningHistory) return; // Prevent multiple simultaneous requests + + setLoadingWarningHistory(true); + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/admin/warnings`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}`, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + setWarningHistory(data.warnings || []); + warningsLoadedRef.current = true; + } else { + console.error('Failed to fetch warnings:', response.status); + setWarningHistory([]); + warningsLoadedRef.current = true; + } + } catch (error) { + console.error('Error fetching warnings:', error); + setWarningHistory([]); + warningsLoadedRef.current = true; + } finally { + setLoadingWarningHistory(false); + } + }; + + // Fetch recent activity data + const fetchRecentActivity = async () => { + if (loadingActivity) return; + + setLoadingActivity(true); + try { + const token = localStorage.getItem('token'); + if (!token) { + console.error('No authentication token found'); + setRecentActivity({ comments: [], writeups: [], lessons: [] }); + return; + } + + const [commentsRes, writeupsRes, lessonsRes] = await Promise.all([ + fetch(`${process.env.NEXT_PUBLIC_API_URL}/activity/comments`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }), + fetch(`${process.env.NEXT_PUBLIC_API_URL}/activity/writeups`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }), + fetch(`${process.env.NEXT_PUBLIC_API_URL}/activity/lessons`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }), + ]); + + let comments = []; + let writeups = []; + let lessons = []; + + if (commentsRes.ok) { + const commentsData = await commentsRes.json(); + comments = Array.isArray(commentsData) ? commentsData : []; + } else { + console.error( + 'Comments API error:', + commentsRes.status, + commentsRes.statusText + ); + } + + if (writeupsRes.ok) { + const writeupsData = await writeupsRes.json(); + writeups = Array.isArray(writeupsData) ? writeupsData : []; + } else { + console.error( + 'Writeups API error:', + writeupsRes.status, + writeupsRes.statusText + ); + } + + if (lessonsRes.ok) { + const lessonsData = await lessonsRes.json(); + lessons = Array.isArray(lessonsData) ? lessonsData : []; + } else { + console.error( + 'Lessons API error:', + lessonsRes.status, + lessonsRes.statusText + ); + } + + setRecentActivity({ comments, writeups, lessons }); + } catch (error) { + console.error('Error fetching recent activity:', error); + setRecentActivity({ comments: [], writeups: [], lessons: [] }); + } finally { + setLoadingActivity(false); + } + }; + + // Manual refresh function + const refreshActivity = async () => { + activityLoadedRef.current = false; + await fetchRecentActivity(); + }; + + // Handle showing deletion details in modal + const handleViewDeletionDetails = (deletion) => { + setSelectedDetails({ + type: 'deletion', + data: deletion, + }); + setShowDetailsModal(true); + }; + return ( <> @@ -786,160 +1644,733 @@ export default function Moderation() {
    {/* Header */} -
    -
    -

    - Admin Center -

    -

    - ΥΠΕΡΑΣΠΙΣΗ ΤΟΥ CTFGUIDE ΑΠΟ ΤΟ OPS -

    +
    +
    +
    +
    + +
    +
    +

    + Admin Center +

    +

    + Platform Moderation & Management +

    +
    +
    -
    - +
    +
    +
    +
    + + System{' '} + {systemStatus.charAt(0).toUpperCase() + + systemStatus.slice(1)} + + + {lastStatusCheck + ? lastStatusCheck.toLocaleTimeString() + : 'Checking...'} + +
    +
    {/* Tab Navigation */} -
    - - - - - +
    + {/* Mobile Dropdown */} +
    +
    + + {showMobileMenu && ( +
    +
    + + + + + + + +
    +
    + )} +
    +
    + {/* Desktop Tabs */} +
    +
    + +
    +
    {/* Tab Content */} - {activeTab === 'overview' && ( -
    - {/* Platform Stats */} -
    -

    - Platform Statistics -

    - {stats ? ( -
    -
    -

    - {stats.userCount} -

    -

    Total Users

    -
    -
    -

    - {stats.challengeCount} -

    -

    Total Challenges

    -
    -
    -

    - {stats.verifiedChallengeCount} -

    -

    Verified Challenges

    + {activeTab === 'activity' && ( +
    +
    +
    +
    +

    + Recent Activity +

    +

    + Latest user-generated content and platform activity +

    +
    + +
    + {loadingActivity ? ( +
    +
    + Loading recent activity...
    ) : ( -
    -

    Failed to load statistics

    -
    - )} - - {/* Recent Sign-Ups Section */} - {stats && - stats.recentSignUps && - stats.recentSignUps.length > 0 && ( -
    -
    -

    - Recent Sign-Ups +
    + {/* Recent Comments */} +
    +
    +

    + Recent Comments

    -
    -

    - {stats.recentSignUpsCount24h || 0} users joined in - last 24h -

    -

    - Showing latest{' '} - {Math.min(6, stats.recentSignUps.length)} users -

    -
    + + {recentActivity.comments.length} +
    -
    - {stats.recentSignUps.slice(0, 6).map((user) => ( -
    - window.open(`/users/${user.username}`, '_blank') - } - > -
    - {`${user.username}'s -
    -

    - {user.username} -

    -

    - {new Date( - user.createdAt - ).toLocaleDateString()} -

    -
    -
    +
    + {recentActivity.comments.length === 0 ? ( +
    +

    + No recent comments +

    - ))} + ) : ( + recentActivity.comments + .slice(0, 10) + .map((comment) => ( +
    + window.open( + `/challenges/${ + comment.challengeSlug || + comment.challengeId || + '' + }?tab=comments`, + '_blank' + ) + } + > +
    + {comment.username} { + e.target.src = `https://robohash.org/${comment.username}.png?set=set1&size=150x150`; + e.target.onerror = null; + }} + /> + + {comment.username} + + + {new Date( + comment.createdAt + ).toLocaleDateString()} + +
    +
    + {comment.content} +
    +
    + on {comment.challengeTitle} +
    +
    + )) + )}
    - )} + {/* Recent Writeups */} +
    +
    +

    + Recent Writeups +

    + + {recentActivity.writeups.length} + +
    +
    + {recentActivity.writeups.length === 0 ? ( +
    +

    + No recent writeups +

    +
    + ) : ( + recentActivity.writeups + .slice(0, 10) + .map((writeup) => ( +
    + window.open( + `/challenges/${ + writeup.challengeSlug || + writeup.challengeId || + '' + }?tab=writeups`, + '_blank' + ) + } + > +
    + {writeup.username} { + e.target.src = `https://robohash.org/${writeup.username}.png?set=set1&size=150x150`; + e.target.onerror = null; + }} + /> + + {writeup.username} + + + {new Date( + writeup.createdAt + ).toLocaleDateString()} + +
    +
    + {writeup.content} +
    +
    + for {writeup.challengeTitle} +
    +
    + )) + )} +
    +
    + {/* Recent Lessons */} +
    +
    +

    + Recent Lessons +

    + + {recentActivity.lessons.length} + +
    +
    + {recentActivity.lessons.length === 0 ? ( +
    +

    + No recent lessons +

    +
    + ) : ( + recentActivity.lessons + .slice(0, 10) + .map((lesson) => ( +
    + window.open( + `/learn/lessons/${lesson.id}`, + '_blank' + ) + } + > +
    + + {lesson.title} + + + {new Date( + lesson.createdAt + ).toLocaleDateString()} + +
    +
    + {lesson.description} +
    +
    + + by {lesson.author} + + + {lesson.status} + +
    +
    + )) + )} +
    +
    +
    + )} +
    +
    + )} + + {activeTab === 'overview' && ( +
    + {/* Enhanced Platform Statistics Dashboard */} +
    +
    +
    +
    +

    + Platform Analytics +

    +

    + Real-time platform statistics and insights +

    +
    +
    +
    +
    + + Live + +
    +
    + + {stats ? ( + <> + {/* Primary Stats Grid */} +
    + {/* Total Users */} +
    +
    +

    + {stats?.userCount?.toLocaleString() || '0'} +

    +

    + Total Users +

    +
    +
    +
    +
    +
    +
    +
    + + {/* New Joiners (24h) */} +
    +
    +

    + {stats?.recentSignUpsCount24h || 0} +

    +

    + New Joiners (24h) +

    +
    +
    +
    + + + + {( + (stats?.recentSignUpsCount24h || 0) / 7 + ).toFixed(1)} + % vs avg + +
    +
    +
    + + {/* Total Challenges */} +
    +
    +

    + {stats?.challengeCount?.toLocaleString() || '0'} +

    +

    + Total Challenges +

    +
    +
    +
    +
    +
    +
    +
    + + {/* Verified Challenges */} +
    +
    +

    + {stats?.verifiedChallengeCount?.toLocaleString() || + '0'} +

    +

    + Verified Challenges +

    +
    +
    +
    + + {( + ((stats?.verifiedChallengeCount || 0) / + (stats?.challengeCount || 1)) * + 100 + ).toFixed(1)} + % verified + +
    +
    +
    +
    + + {/* Secondary Stats Grid */} +
    + {/* Pending Challenges */} +
    +

    + {pendingChallenges.length} +

    +

    + Pending +

    +
    + + {/* Reports */} +
    +

    + {reportStats?.totalReports || reports.length} +

    +

    + Reports +

    +
    + + {/* Suspended Accounts */} +
    +

    + {totalSuspendedAccounts} +

    +

    + Suspended +

    +
    + + {/* Account Deletions */} +
    +

    + {pendingDeletions.length} +

    +

    + Deletions +

    +
    + + {/* Verification Rate */} +
    +

    + {( + ((stats?.verifiedChallengeCount || 0) / + (stats?.challengeCount || 1)) * + 100 + ).toFixed(0)} + % +

    +

    + Verified +

    +
    + + {/* Weekly Projection */} +
    +

    + {(stats?.recentSignUpsCount24h || 0) * 7} +

    +

    + Weekly User Est. +

    +
    +
    + + {/* Recent Activity Section */} +
    + {/* Recent Joiners */} +
    +
    +
    +
    + +
    +
    +

    + Recent Joiners +

    +

    + Latest members to join the platform +

    +
    +
    +
    +

    + {stats?.recentSignUpsCount24h || 0} in last 24h +

    +

    + Showing latest{' '} + {Math.min(6, stats?.recentSignUps?.length || 0)} +

    +
    +
    + + {stats?.recentSignUps && + stats?.recentSignUps.length > 0 ? ( +
    + {stats?.recentSignUps.slice(0, 6).map((user) => ( +
    + window.open( + `/users/${user.username}`, + '_blank' + ) + } + > +
    +
    + {`${user.username}'s +
    +
    +
    +

    + {user.username} +

    +

    + {new Date( + user.createdAt + ).toLocaleDateString()} +

    +
    +
    +
    + ))} +
    + ) : ( +
    +
    + +
    +

    + No recent sign-ups +

    +
    + )} +
    + + {/* Recent Logins */} +
    +
    +
    +
    + +
    +
    +

    + Recent Logins +

    +

    + Users active in last 24h +

    +
    +
    +
    + + {stats?.recentLogins?.length || 0} users + +
    +
    + + {stats?.recentLogins?.length > 0 ? ( +
    + {stats?.recentLogins + .slice(0, 4) + .map((user, index) => ( +
    + window.open( + `/users/${user.username}`, + '_blank' + ) + } + > +
    +
    + {user.username} { + e.target.src = `https://robohash.org/${user.username}?set=set1&size=48x48`; + }} + /> +
    +
    +
    +

    + {user.username} +

    +

    + {new Date( + user.lastLogin + ).toLocaleTimeString()} +

    + {user.role === 'ADMIN' && ( + + ADMIN + + )} + {user.role === 'PRO' && ( + + PRO + + )} +
    +
    +
    + ))} +
    + ) : ( +
    +
    + +
    +

    No recent logins

    +
    + )} +
    +
    + + ) : ( +
    +
    + +
    +

    + Failed to load statistics +

    +

    + Unable to fetch platform analytics data +

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

    User Management

    +
    +
    +

    User Management

    -
    - -
    { @@ -959,7 +2390,7 @@ export default function Moderation() { {/* User Suggestions Dropdown */} {showUserSuggestions && userSuggestions.length > 0 && ( -
    +
    {userSuggestions.map((user) => (

    @@ -986,7 +2417,7 @@ export default function Moderation() {

    {user.disabled && ( - + Disabled )} @@ -997,68 +2428,60 @@ export default function Moderation() {
    -
    - -
    - -

    Uploaded Files

    -
    - {files && files.length > 0 ? ( - files.map((file, index) => ( -
    - {file.name} - - Download - -
    - )) - ) : ( -

    No files uploaded

    - )} -
    - -

    Moderator Notes

    - - - -



    -
    -

    Set Base Points

    -
    -

    Beginner: 100, - Easy: 200, - Medium: 300, - Hard: 400, - Insane: 500 -

    -
    - - -
    -
    - - -
    + return ( + + +
    +
    + + + {/* Sticky Close Button */} +
    + + {challenge.title || 'Loading...'} + + +
    + {/* Modal Content */} +
    +
    +

    + {' '} + Don't forget + to make sure the challenge is including an explanation. +

    +
    +
    +
    +

    + Description +

    + +
    +
    +

    Flag

    +

    + {(challenge && + challenge.solution && + challenge.solution.keyword) || + 'N/A'} +

    +
    +
    +

    Difficulty

    +

    {challenge && challenge.difficulty}

    +
    + +
    +

    Category

    +

    {challenge && challenge.category && challenge.category.join(", ")}

    +
    +
    +

    + Date of Creation +

    +

    + {challenge && + new Date(challenge.createdAt).toLocaleString([], { + hour: 'numeric', + minute: 'numeric', + hour12: true, + month: 'short', + day: 'numeric', + })} +

    +
    +
    +

    + Last Updated +

    +

    + {challenge && + new Date(challenge.updatedAt).toLocaleString([], { + hour: 'numeric', + minute: 'numeric', + hour12: true, + month: 'short', + day: 'numeric', + })} +

    +
    +
    +

    + Environment Container Configuration +

    + +

    + Uploaded Files +

    +
    + {files && files.length > 0 ? ( + files.map((file, index) => ( +
    + {file.name} + + Download + +
    + )) + ) : ( +

    No files uploaded

    + )}
    -
    - - - - - - - +

    + Moderator Notes +

    +