diff --git a/package-lock.json b/package-lock.json index eeec88b..3336df3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@marsidev/react-turnstile": "^1.5.4", - "@vnedyalk0v/react19-simple-maps": "^2.0.9", + "@vnedyalk0v/react19-simple-maps": "^2.0.10", "axios": "^1.19.0", "country-list": "^2.4.1", "d3-geo": "^3.1.1", @@ -32,16 +32,16 @@ "@eslint/js": "^10.0.1", "@types/country-list": "^2.1.4", "@types/node": "^26.1.2", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.4", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.0.5", "eslint": "^10.8.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.8.0", "typescript": "^7.0.2", "typescript-eslint": "^8.65.0", - "vite": "^8.1.5" + "vite": "^8.2.0" } }, "node_modules/@babel/code-frame": { @@ -294,21 +294,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.2", + "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", "dev": true, "license": "MIT", "optional": true, @@ -317,9 +317,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", "dev": true, "license": "MIT", "optional": true, @@ -582,28 +582,31 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "dev": true, "license": "MIT", "funding": { @@ -637,9 +640,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", "cpu": [ "arm64" ], @@ -654,9 +657,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", "cpu": [ "arm64" ], @@ -671,9 +674,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", "cpu": [ "x64" ], @@ -688,9 +691,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", "cpu": [ "x64" ], @@ -705,9 +708,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", "cpu": [ "arm" ], @@ -722,9 +725,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", "cpu": [ "arm64" ], @@ -739,9 +742,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", "cpu": [ "arm64" ], @@ -756,9 +759,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", "cpu": [ "ppc64" ], @@ -773,9 +776,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", "cpu": [ "s390x" ], @@ -790,9 +793,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", "cpu": [ "x64" ], @@ -807,9 +810,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", "cpu": [ "x64" ], @@ -824,9 +827,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", "cpu": [ "arm64" ], @@ -841,28 +844,25 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", "cpu": [ "arm64" ], @@ -877,9 +877,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", "cpu": [ "x64" ], @@ -1066,9 +1066,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "dev": true, "license": "MIT", "dependencies": { @@ -1076,9 +1076,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1687,9 +1687,9 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.4.tgz", - "integrity": "sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { @@ -1713,9 +1713,9 @@ } }, "node_modules/@vnedyalk0v/react19-simple-maps": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@vnedyalk0v/react19-simple-maps/-/react19-simple-maps-2.0.9.tgz", - "integrity": "sha512-QMV6QkRcAVulhgA4vEVE68tvXk7xVn5MAi5gCTSfWhWFd4YRwnGaRLA/m4UEmhTRuXe4dabGVIVKFSMfHBy77Q==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@vnedyalk0v/react19-simple-maps/-/react19-simple-maps-2.0.10.tgz", + "integrity": "sha512-tw8PYah/5c1UiwqhtB0k40p+jlYRW9QjdZZQ37CKONXErIZFHl0Sn8Ho03HNP/mXQs0YrogrBIeolln5mfbIsA==", "license": "MIT", "dependencies": { "d3-color": "^3.1.0", @@ -3252,9 +3252,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -3268,23 +3268,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -3303,9 +3303,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -3324,9 +3324,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -3345,9 +3345,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -3366,9 +3366,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -3387,9 +3387,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -3408,9 +3408,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -3429,9 +3429,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -3450,9 +3450,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -3471,9 +3471,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -3492,9 +3492,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -4960,13 +4960,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.139.0", + "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -4976,21 +4976,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" } }, "node_modules/scheduler": { @@ -5435,16 +5435,16 @@ } }, "node_modules/vite": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", - "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", + "lightningcss": "^1.33.0", "picomatch": "^4.0.5", - "postcss": "^8.5.17", - "rolldown": "~1.1.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -5461,7 +5461,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/package.json b/package.json index 9fa24b7..0738aab 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@marsidev/react-turnstile": "^1.5.4", - "@vnedyalk0v/react19-simple-maps": "^2.0.9", + "@vnedyalk0v/react19-simple-maps": "^2.0.10", "axios": "^1.19.0", "country-list": "^2.4.1", "d3-geo": "^3.1.1", @@ -35,18 +35,18 @@ "@eslint/js": "^10.0.1", "@types/country-list": "^2.1.4", "@types/node": "^26.1.2", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.4", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.0.5", "eslint": "^10.8.0", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.8.0", "typescript": "^7.0.2", "typescript-eslint": "^8.65.0", - "vite": "^8.1.5" + "vite": "^8.2.0" }, "overrides": { - "vite": "^8.1.5" + "vite": "^8.2.0" } } diff --git a/src/App.css b/src/App.css index d7cde99..ee24a1f 100644 --- a/src/App.css +++ b/src/App.css @@ -64,12 +64,10 @@ h3 { font-size: 1.5rem; font-weight: 800; margin-bottom: 2rem; - text-transform: uppercase; } .section-title span { - color: var(--primary); - border-bottom: 2px solid var(--primary); + color: inherit; padding-bottom: 4px; } @@ -194,35 +192,48 @@ h3 { text-decoration: none !important; } -.nav-search { - flex-grow: 1; - margin: 0 2rem; - max-width: 400px; +.nav-search-center { + flex: 1; + display: flex; + justify-content: center; + margin: 0 1.5rem; } -.nav-search input { +.nav-search-input-wrap { + position: relative; width: 100%; - padding: 10px 15px; - border-radius: 6px; - border: 1px solid var(--border); - background: var(--surface); - color: var(--text); - font-size: 0.9rem; - outline: none; - transition: border-color 0.2s; + max-width: 440px; + display: flex; + align-items: center; } -.nav-search input::placeholder { +.nav-search-icon { + position: absolute; + left: 14px; + color: var(--text-muted); + pointer-events: none; +} + +.nav-search-input-wrap input { + width: 100%; + padding: 8px 16px 8px 40px; + border-radius: 20px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.05); + color: var(--text); + font-size: 0.875rem; + outline: none; + transition: all 0.2s ease; +} + +.nav-search-input-wrap input::placeholder { color: var(--text-muted); } -.nav-search input:focus { +.nav-search-input-wrap input:focus { border-color: var(--primary); -} - -.nav-search-right { - margin-right: 1rem; - width: 250px; + background: rgba(0, 0, 0, 0.4); + box-shadow: 0 0 12px rgba(249, 115, 22, 0.25); } .nav-search-right input { @@ -296,7 +307,7 @@ h3 { } .plugin-sidebar { - order: -1; + order: 1; width: 100%; } diff --git a/src/App.tsx b/src/App.tsx index b813a26..5068907 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, NavLink, useNavigate, Outlet, Navigate, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { Search } from 'lucide-react'; import Home from './pages/Home'; import PluginDetail from './pages/PluginDetail'; import LoginPage from './pages/auth/LoginPage'; @@ -171,9 +172,14 @@ import './pages/dashboard/Dashboard.css'; const DashboardLayout = () => { return (
- +
+
+ + Market Logo + DEVELOPER STUDIO + +
+
@@ -227,8 +233,10 @@ const Navbar = ({ user }: any) => { {user && (user.role === 'admin' || user.role === 'moderator') && setIsMenuOpen(false)}>Staff}
- {!user && ( -
+ {/* ── Center Search Bar ── */} +
+
+ { onKeyDown={handleSearch} />
- )} +
{user ? ( - <> -
- setSearchQuery(e.target.value)} - onKeyDown={handleSearch} - /> -
- setIsMenuOpen(false)}>{user.username} - + setIsMenuOpen(false)}>{user.username} ) : ( <> setIsMenuOpen(false)}>{t('login')} diff --git a/src/components/PluginCard.tsx b/src/components/PluginCard.tsx index bf0c668..562be9f 100644 --- a/src/components/PluginCard.tsx +++ b/src/components/PluginCard.tsx @@ -31,7 +31,7 @@ const getEffectivePrice = (plugin: any): { display: string; isSale: boolean; ori }; }; -const PluginCard = ({ plugin }: { plugin: any }) => { +const PluginCard = ({ plugin, hideDescription = false }: { plugin: any; hideDescription?: boolean }) => { const { i18n } = useTranslation(); const [currentIndex, setCurrentIndex] = useState(0); const [isHovering, setIsHovering] = useState(false); @@ -129,7 +129,7 @@ const PluginCard = ({ plugin }: { plugin: any }) => { {plugin.is_early_access && Early Access}

by {plugin.dev_name}

-

{desc}

+ {!hideDescription &&

{desc}

}
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 3694a9e..47d1c3e 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -219,7 +219,7 @@ const Home = () => {
{popular.length > 0 ? ( popular.map((plugin) => ( - + )) ) : (
{loading ? 'Loading popular plugins...' : 'Scroll to load popular plugins'}
@@ -232,7 +232,7 @@ const Home = () => {
{newest.length > 0 ? ( newest.map((plugin) => ( - + )) ) : (
{loading ? 'Loading new plugins...' : 'Scroll to load new plugins'}
@@ -245,7 +245,7 @@ const Home = () => {
{allPlugins.length > 0 ? ( allPlugins.map((plugin) => ( - + )) ) : (
{loading ? 'Loading all plugins...' : 'No plugins available'}
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 4b5ae4b..969ee0b 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -171,6 +171,51 @@ const ProfilePage = () => { }); }; + const handleSaveAccountInfo = async (e: React.FormEvent) => { + e.preventDefault(); + await withSaving('accountInfo', async () => { + let updatedAny = false; + + if (formData.username && formData.username !== user?.username) { + try { + const res = await api.post('/user/change-username', { newUsername: formData.username }); + if (res.data.token) login(res.data.token); + updatedAny = true; + } catch (err: any) { + showToast(err.response?.data?.error || 'Failed to update username.', 'error'); + return; + } + } + + if (formData.email && formData.email !== user?.email) { + try { + const res = await api.post('/user/change-email', { newEmail: formData.email }); + updatedAny = true; + } catch (err: any) { + showToast(err.response?.data?.error || 'Failed to update email.', 'error'); + return; + } + } + + if (formData.country && formData.country !== user?.country) { + try { + await api.post('/user/change-country', { newCountry: formData.country }); + updatedAny = true; + } catch (err: any) { + showToast(err.response?.data?.error || 'Failed to update country.', 'error'); + return; + } + } + + if (updatedAny) { + showToast('Account details updated successfully!'); + refreshUser?.(); + } else { + showToast('No changes to save.'); + } + }); + }; + const handleLogout = () => { logout(); navigate('/login'); }; const handleConnectStripe = async () => { @@ -375,48 +420,34 @@ const ProfilePage = () => {

Account Details

-

Manage your public identity and contact information.

- -
handleUpdate(e, '/user/change-username', { newUsername: formData.username }, 'username')}> - - setFormData({ ...formData, username: e.target.value })} - placeholder="Your username" - /> - - - -
+ +
+
+ + setFormData({ ...formData, username: e.target.value })} + placeholder="Your username" + required + /> + - - handleUpdate(e, '/user/change-email', { newEmail: formData.email }, 'email')}> - - setFormData({ ...formData, email: e.target.value })} - placeholder="your@email.com" - /> - - - - + + setFormData({ ...formData, email: e.target.value })} + placeholder="your@email.com" + required + /> + +
- -
{ - if (!formData.country) { - e.preventDefault(); - showToast('Please select a country.', 'error'); - return; - } - handleUpdate(e, '/user/change-country', { newCountry: formData.country }, 'country'); - }}> - + +
+ +
diff --git a/src/pages/dashboard/Dashboard.css b/src/pages/dashboard/Dashboard.css index 318c13a..ada61e0 100644 --- a/src/pages/dashboard/Dashboard.css +++ b/src/pages/dashboard/Dashboard.css @@ -13,6 +13,44 @@ --dash-shadow: 0 1px 3px rgba(0,0,0,0.08); /* New: for subtle shadows */ } +/* Dashboard Top App Header */ +.dashboard-app-header { + background: var(--dash-surface); + border-bottom: 1px solid var(--dash-border); + padding: 0.85rem 2rem; + margin-bottom: 2rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); +} + +.dashboard-app-header-inner { + max-width: 1400px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; +} + +.dashboard-app-brand { + display: flex; + align-items: center; + gap: 0.75rem; + text-decoration: none !important; + color: var(--dash-text); + font-weight: 800; + font-size: 1.1rem; + letter-spacing: -0.3px; +} + +.dashboard-app-logo { + height: 32px; + width: auto; + object-fit: contain; +} + +.dashboard-app-title span { + color: var(--dash-accent, #ff7518); +} + /* Override App.css within the dashboard */ .dashboard-content, .dashboard-nav { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; diff --git a/src/pages/dashboard/plugin/AddPlugin.tsx b/src/pages/dashboard/plugin/AddPlugin.tsx index 36b3a1f..a8030f1 100644 --- a/src/pages/dashboard/plugin/AddPlugin.tsx +++ b/src/pages/dashboard/plugin/AddPlugin.tsx @@ -3,10 +3,11 @@ import { useNavigate } from 'react-router-dom'; import { Globe, ImageIcon, Plus, Search, X, Upload, FileCode, DollarSign, CheckCircle, ChevronRight, ChevronLeft, - Zap, Lock, AlertTriangle, Tag, Store, User, + Zap, Lock, AlertTriangle, Tag, Store, User, Sparkles, } from 'lucide-react'; import api from '../../../api'; import { useAuth } from '../../../App'; +import { DeveloperOnboardingModal } from '../../../components/DeveloperOnboardingModal'; import './AddPlugin.css'; // ── Locale helpers ──────────────────────────────────────────────────────────── @@ -193,6 +194,7 @@ const LangPicker = ({ usedCodes, onAdd, onClose }: { usedCodes: string[]; onAdd: const AddPlugin = () => { const navigate = useNavigate(); const { user, refreshUser } = useAuth(); + const [isDevModalOpen, setIsDevModalOpen] = useState(false); const [step, setStep] = useState(0); const [submitting, setSubmitting] = useState(false); const [done, setDone] = useState(false); @@ -205,6 +207,45 @@ const AddPlugin = () => { const [youtubeVideoUrl, setYoutubeVideoUrl] = useState(''); const [keywords, setKeywords] = useState(''); + if (user && !user.is_developer) { + return ( +
+
+
+ +
+

+ Developer Profile Required +

+

+ Before publishing plugins on Pumpkin Market, you must complete your developer profile. It takes less than a minute! +

+ + + setIsDevModalOpen(false)} + onSuccess={() => refreshUser?.()} + /> +
+
+ ); + } + // ── Step 1: Store Listing ── const [descriptions, setDescriptions] = useState>({ [DEFAULT_LOCALE]: '' }); const [activeLocale, setActiveLocale] = useState(DEFAULT_LOCALE); diff --git a/src/pages/dashboard/plugin/Licenses.tsx b/src/pages/dashboard/plugin/Licenses.tsx new file mode 100644 index 0000000..6c55f33 --- /dev/null +++ b/src/pages/dashboard/plugin/Licenses.tsx @@ -0,0 +1,251 @@ +import { useState, useEffect } from 'react'; +import { Key, Plus, Trash2, ShieldAlert, ShieldCheck, Copy, Check, UserCheck } from 'lucide-react'; +import api from '../../../api'; +import type { PluginData } from './ManagePlugin'; + +interface PluginLicense { + id: number; + plugin_id: number; + user_id: number; + username: string; + email: string | null; + license_key: string; + revoked: boolean; + created_at: string; +} + +type Props = { plugin: PluginData }; + +const Licenses = ({ plugin }: Props) => { + const [licenses, setLicenses] = useState([]); + const [loading, setLoading] = useState(true); + + // Form state + const [userIdentifier, setUserIdentifier] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [copiedKey, setCopiedKey] = useState(null); + + useEffect(() => { + fetchLicenses(); + }, [plugin.id]); + + const fetchLicenses = async () => { + try { + const res = await api.get(`/plugins/${plugin.id}/licenses`); + setLicenses(res.data); + } catch (err) { + console.error('Failed to fetch licenses', err); + } finally { + setLoading(false); + } + }; + + const handleGrantLicense = async (e: React.FormEvent) => { + e.preventDefault(); + if (!userIdentifier.trim()) return; + setSubmitting(true); + + try { + const res = await api.post(`/plugins/${plugin.id}/licenses`, { + user_identifier: userIdentifier.trim() + }); + setLicenses(prev => [res.data, ...prev.filter(l => l.id !== res.data.id)]); + setUserIdentifier(''); + } catch (err: any) { + alert(err.response?.data || 'Failed to grant license to user.'); + } finally { + setSubmitting(false); + } + }; + + const handleToggleRevoke = async (licenseId: number, currentRevoked: boolean) => { + const action = currentRevoked ? 'unrevoke' : 'revoke'; + if (!window.confirm(`Are you sure you want to ${action} this license key?`)) return; + + try { + await api.put(`/plugins/${plugin.id}/licenses/${licenseId}/revoke`, { + revoked: !currentRevoked + }); + setLicenses(prev => + prev.map(l => l.id === licenseId ? { ...l, revoked: !currentRevoked } : l) + ); + } catch (err) { + alert(`Failed to ${action} license.`); + } + }; + + const handleDelete = async (licenseId: number) => { + if (!window.confirm('Are you sure you want to permanently delete this license key record?')) return; + + try { + await api.delete(`/plugins/${plugin.id}/licenses/${licenseId}`); + setLicenses(prev => prev.filter(l => l.id !== licenseId)); + } catch (err) { + alert('Failed to delete license record.'); + } + }; + + const handleCopyKey = (key: string) => { + navigator.clipboard.writeText(key); + setCopiedKey(key); + setTimeout(() => setCopiedKey(null), 2000); + }; + + return ( +
+
+

License Key Management

+

View, grant, revoke, and manage license keys for your paid plugin buyers and testers.

+
+ +
+ {/* Grant License Card */} +
+
+ Grant Manual License +
+

+ Issue a valid license key to a user by entering their username, email address, or account ID. +

+
+
+ + setUserIdentifier(e.target.value)} + required + /> +
+ + +
+
+ + {/* Licenses Table / List */} +
+
+ + Active & Issued Licenses ({licenses.length}) + +
+ + {loading ? ( +

Loading licenses…

+ ) : licenses.length === 0 ? ( +
+ +

No license keys issued yet for this plugin.

+

Licenses will automatically appear here when customers purchase your plugin.

+
+ ) : ( +
+ + + + + + + + + + + + {licenses.map(lic => ( + + + + + + + + ))} + +
UserLicense KeyIssued DateStatusActions
+
+ + {lic.username} +
+ {lic.email && ( +
{lic.email}
+ )} +
+
+ {lic.license_key} + +
+
+ {new Date(lic.created_at).toLocaleDateString()} + + {lic.revoked ? ( + + Revoked + + ) : ( + + Active + + )} + +
+ + +
+
+
+ )} +
+
+
+ ); +}; + +export default Licenses; diff --git a/src/pages/dashboard/plugin/ManagePlugin.tsx b/src/pages/dashboard/plugin/ManagePlugin.tsx index 8828860..5327fde 100644 --- a/src/pages/dashboard/plugin/ManagePlugin.tsx +++ b/src/pages/dashboard/plugin/ManagePlugin.tsx @@ -3,13 +3,15 @@ import { useNavigate, useParams } from 'react-router-dom'; import api from '../../../api'; import { LayoutGrid, Tag, Upload, DollarSign, Trash2, - Circle, BarChart3 + Circle, BarChart3, Key, Star } from 'lucide-react'; import StoreListing from './StoreListing'; import PublishUpdate from './PublishUpdate'; import Pricing from './Pricing'; import Coupons from './Coupons'; +import Licenses from './Licenses'; +import PluginReviewsTab from './PluginReviewsTab'; import DangerZone from './DangerZone'; import PluginAnalyticsTab from './PluginAnalyticsTab'; import './ManagePlugin.css'; @@ -37,7 +39,9 @@ const NAV_ITEMS = [ { key: 'listing', label: 'Store Listing', icon: LayoutGrid }, { key: 'update', label: 'Publish Update', icon: Upload }, { key: 'analytics', label: 'Analytics', icon: BarChart3 }, + { key: 'reviews', label: 'Reviews', icon: Star }, { key: 'pricing', label: 'Pricing', icon: DollarSign }, + { key: 'licenses', label: 'Licenses', icon: Key }, { key: 'coupons', label: 'Coupons', icon: Tag }, { key: 'danger', label: 'Danger Zone', icon: Trash2 }, ]; @@ -136,9 +140,15 @@ const ManagePlugin = () => { {activeTab === 'analytics' && ( )} + {activeTab === 'reviews' && ( + + )} {activeTab === 'pricing' && ( )} + {activeTab === 'licenses' && ( + + )} {activeTab === 'coupons' && ( )} diff --git a/src/pages/dashboard/plugin/PluginReviewsTab.tsx b/src/pages/dashboard/plugin/PluginReviewsTab.tsx new file mode 100644 index 0000000..fe90784 --- /dev/null +++ b/src/pages/dashboard/plugin/PluginReviewsTab.tsx @@ -0,0 +1,266 @@ +import { useState, useEffect, useMemo } from 'react'; +import { Star, MessageSquare, CornerDownRight, Send, CheckCircle2 } from 'lucide-react'; +import api from '../../../api'; +import type { PluginData } from './ManagePlugin'; + +interface Review { + id: number; + user_id: number; + plugin_id: number; + rating: number; + comment: string; + developer_reply?: string | null; + replied_at?: string | null; + created_at: string; + username: string; +} + +type Props = { + plugin: PluginData; +}; + +const PluginReviewsTab = ({ plugin }: Props) => { + const [reviews, setReviews] = useState([]); + const [loading, setLoading] = useState(true); + const [replyText, setReplyText] = useState<{ [reviewId: number]: string }>({}); + const [submitting, setSubmitting] = useState<{ [reviewId: number]: boolean }>({}); + + useEffect(() => { + fetchPluginReviews(); + }, [plugin.id]); + + const fetchPluginReviews = async () => { + setLoading(true); + try { + const res = await api.get(`/plugins/${plugin.id}`); + const fetchedReviews: Review[] = res.data.reviews || []; + setReviews(fetchedReviews); + } catch (err) { + console.error('Failed to fetch plugin reviews', err); + } finally { + setLoading(false); + } + }; + + // Calculate rating statistics & distribution + const stats = useMemo(() => { + if (reviews.length === 0) { + return { + average: 0, + total: 0, + distribution: { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 } + }; + } + + const total = reviews.length; + const sum = reviews.reduce((acc, r) => acc + r.rating, 0); + const average = Number((sum / total).toFixed(1)); + + const distribution: { [key: number]: number } = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }; + reviews.forEach(r => { + if (distribution[r.rating] !== undefined) { + distribution[r.rating]++; + } + }); + + return { average, total, distribution }; + }, [reviews]); + + const handleSendReply = async (reviewId: number) => { + const text = replyText[reviewId]?.trim(); + if (!text) return; + + setSubmitting(s => ({ ...s, [reviewId]: true })); + try { + await api.post(`/plugins/${plugin.id}/reviews/${reviewId}/reply`, { reply: text }); + setReviews(prev => + prev.map(r => + r.id === reviewId + ? { ...r, developer_reply: text, replied_at: new Date().toISOString() } + : r + ) + ); + setReplyText(t => ({ ...t, [reviewId]: '' })); + } catch (err: any) { + alert(err.response?.data?.error || err.response?.data || 'Failed to submit reply.'); + } finally { + setSubmitting(s => ({ ...s, [reviewId]: false })); + } + }; + + const renderStars = (rating: number, size = 14) => ( +
+ {[1, 2, 3, 4, 5].map(star => ( + + ))} +
+ ); + + return ( +
+ {/* Header */} +
+

Reviews & Ratings

+

Monitor user feedback, rating trends, and reply directly to customer reviews.

+
+ + {loading ? ( +
Loading plugin reviews…
+ ) : ( + <> + {/* Rating Overview Cards */} +
+ {/* Summary Box */} +
+
+ {stats.average > 0 ? stats.average : 'N/A'} +
+
+ {renderStars(Math.round(stats.average), 18)} +
+
+ Based on {stats.total} {stats.total === 1 ? 'review' : 'reviews'} +
+
+ + {/* Breakdown Bars */} +
+
+ Rating Breakdown +
+
+ {[5, 4, 3, 2, 1].map(starsCount => { + const count = stats.distribution[starsCount] || 0; + const pct = stats.total > 0 ? Math.round((count / stats.total) * 100) : 0; + return ( +
+
+ {starsCount} + +
+
+
+
+
+ {pct}% +
+
+ ); + })} +
+
+
+ + {/* Reviews List */} +
+
+ All Reviews ({reviews.length}) +
+ + {reviews.length === 0 ? ( +
+ +

No reviews yet

+

When users review this plugin, their feedback and ratings will appear here.

+
+ ) : ( +
+ {reviews.map(rev => ( +
+
+
+
+ {rev.username} +
+
+ {renderStars(rev.rating)} +
+
+
+ {new Date(rev.created_at).toLocaleDateString()} +
+
+ +

+ {rev.comment} +

+ + {/* Developer Reply Box or Input */} + {rev.developer_reply ? ( +
+
+ Developer Response + {rev.replied_at && ( + + {new Date(rev.replied_at).toLocaleDateString()} + + )} +
+

+ {rev.developer_reply} +

+
+ ) : ( +
+
+ Respond to {rev.username}: +
+
+