diff --git a/package-lock.json b/package-lock.json index b3be890..d47d39d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,6 +143,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2477,6 +2478,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2520,6 +2522,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -3651,6 +3654,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz", "integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "@mui/core-downloads-tracker": "^7.3.6", @@ -4634,6 +4638,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -4743,6 +4748,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -5194,8 +5200,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5321,6 +5326,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5579,6 +5585,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -5636,6 +5643,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5646,6 +5654,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5865,6 +5874,7 @@ "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", @@ -5895,6 +5905,7 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -6800,6 +6811,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6907,6 +6919,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7209,7 +7222,6 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -7229,7 +7241,6 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -7252,7 +7263,6 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7265,7 +7275,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7286,8 +7295,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/archiver-utils/node_modules/minimatch": { "version": "3.1.2", @@ -7295,7 +7303,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7309,7 +7316,6 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7325,8 +7331,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -7334,7 +7339,6 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -8112,6 +8116,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8919,7 +8924,6 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -9230,7 +9234,6 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -9244,7 +9247,6 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -10080,6 +10082,7 @@ "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "25.1.8", "builder-util": "25.1.7", @@ -10150,8 +10153,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-converter": { "version": "0.2.0", @@ -10353,6 +10355,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", @@ -10958,6 +10961,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -11074,6 +11078,7 @@ "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.21.0", @@ -11110,6 +11115,7 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -11480,6 +11486,7 @@ "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.7", "aria-query": "^5.1.3", @@ -11561,6 +11568,7 @@ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -11647,6 +11655,7 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -11870,6 +11879,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -12101,6 +12111,7 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -13071,8 +13082,7 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fs-extra": { "version": "10.1.0", @@ -15269,6 +15279,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -16616,7 +16627,6 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -16629,8 +16639,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lazystream/node_modules/readable-stream": { "version": "2.3.8", @@ -16638,7 +16647,6 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16654,8 +16662,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -16663,7 +16670,6 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -16784,8 +16790,7 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.difference": { "version": "4.5.0", @@ -16805,8 +16810,7 @@ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -16820,8 +16824,7 @@ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -16842,8 +16845,7 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -16927,7 +16929,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -19375,6 +19376,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -19979,6 +19981,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -20019,7 +20022,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -20035,7 +20037,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -20048,8 +20049,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/proc-log": { "version": "2.0.1", @@ -20342,6 +20342,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20351,6 +20352,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -20406,6 +20408,7 @@ "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20511,7 +20514,6 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -20522,7 +20524,6 @@ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -21278,6 +21279,7 @@ "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -21385,6 +21387,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -22824,7 +22827,6 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -23101,6 +23103,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -23394,6 +23397,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -23528,7 +23532,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -23582,6 +23587,7 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -23687,6 +23693,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23970,6 +23977,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -24350,6 +24358,7 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -24588,6 +24597,7 @@ "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -25193,7 +25203,6 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -25209,7 +25218,6 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", @@ -25232,7 +25240,6 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -25245,7 +25252,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -25267,7 +25273,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, diff --git a/src/renderer/pages/FakePaymentPage.tsx b/src/renderer/pages/FakePaymentPage.tsx index 960ad61..89b5ede 100644 --- a/src/renderer/pages/FakePaymentPage.tsx +++ b/src/renderer/pages/FakePaymentPage.tsx @@ -289,6 +289,7 @@ export default function TopUpPage() { navigate('/'); }} sx={{ + fontFamily: 'Benzin-Bold', borderRadius: 999, px: 3, borderColor: 'rgba(255,255,255,0.18)', @@ -440,11 +441,11 @@ export default function TopUpPage() { > setCoins(Number(e.target.value))} inputProps={{ min: 0, step: 1 }} fullWidth + sx={{'& .MuiFormLabel-root': {fontFamily: 'Benzin-Bold'}}} /> void; } +const glowPulse = keyframes` + 0% { transform: translate3d(0,0,0) scale(1); opacity: .85; filter: saturate(1.0); } + 50% { transform: translate3d(0,-6px,0) scale(1.02); opacity: 1; filter: saturate(1.15); } + 100% { transform: translate3d(0,0,0) scale(1); opacity: .85; filter: saturate(1.0); } +`; + +const borderShimmer = keyframes` + 0% { background-position: 0% 50%; opacity: .35; } + 50% { background-position: 100% 50%; opacity: .55; } + 100% { background-position: 0% 50%; opacity: .35; } +`; + +const GRADIENT = + 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)'; + +const GlassPaper = styled(Paper)(() => ({ + position: 'relative', + overflow: 'hidden', + borderRadius: 28, + background: 'rgba(0,0,0,0.35)', + border: '1px solid rgba(255,255,255,0.10)', + backdropFilter: 'blur(14px)', + boxShadow: '0 20px 60px rgba(0,0,0,0.45)', +})); + +const Glow = styled('div')(() => ({ + position: 'absolute', + inset: -2, + background: + 'radial-gradient(800px 300px at 20% 10%, rgba(242,113,33,0.22), transparent 60%),' + + 'radial-gradient(800px 300px at 80% 0%, rgba(233,64,205,0.18), transparent 55%),' + + 'radial-gradient(900px 420px at 50% 110%, rgba(138,35,135,0.20), transparent 60%)', + pointerEvents: 'none', + animation: `${glowPulse} 6s ease-in-out infinite`, +})); + +const GradientTitle = styled(Typography)(() => ({ + fontWeight: 900, + backgroundImage: + 'linear-gradient(136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + fontFamily: 'Benzin-Bold, sans-serif', +})); + +const GradientButton = styled(Button)(() => ({ + background: GRADIENT, + fontFamily: 'Benzin-Bold, sans-serif', + borderRadius: 999, + textTransform: 'none', + transition: 'transform 0.25s ease, filter 0.25s ease, box-shadow 0.25s ease', + boxShadow: '0 12px 30px rgba(0,0,0,0.35)', + '&:hover': { + transform: 'scale(1.04)', + filter: 'brightness(1.06)', + boxShadow: '0 16px 42px rgba(0,0,0,0.48)', + background: GRADIENT, + }, + '&:disabled': { + background: 'rgba(255,255,255,0.08)', + color: 'rgba(255,255,255,0.35)', + boxShadow: 'none', + }, +})); + +const Segmented = styled(ToggleButtonGroup)(() => ({ + borderRadius: 999, + overflow: 'hidden', + border: '1px solid rgba(255,255,255,0.10)', + background: 'rgba(255,255,255,0.06)', + '& .MuiToggleButton-root': { + border: 'none', + color: 'rgba(255,255,255,0.75)', + fontFamily: 'Benzin-Bold, sans-serif', + letterSpacing: '0.02em', + paddingTop: 10, + paddingBottom: 10, + paddingLeft: 18, + paddingRight: 18, + transition: 'transform 0.2s ease, background 0.2s ease, color 0.2s ease', + }, + '& .MuiToggleButton-root:hover': { + background: 'rgba(255,255,255,0.08)', + transform: 'scale(1.02)', + }, + '& .MuiToggleButton-root.Mui-selected': { + color: '#fff', + background: GRADIENT, + }, + '& .MuiToggleButton-root.Mui-selected:hover': { + background: GRADIENT, + }, +})); + const Login = ({ onLoginSuccess }: LoginProps) => { const navigate = useNavigate(); const { config, saveConfig, handleInputChange } = useConfig(); @@ -40,6 +153,9 @@ const Login = ({ onLoginSuccess }: LoginProps) => { const [qrUrl, setQrUrl] = useState(''); const qrRef = useRef(null); const pollTimerRef = useRef(null); + const [qrState, setQrState] = useState<'idle' | 'ready' | 'polling' | 'expired'>('idle'); + + const [pendingCount, setPendingCount] = useState(0); // хранит один инстанс QRCodeStyling const qrInstanceRef = useRef(null); @@ -67,6 +183,24 @@ const Login = ({ onLoginSuccess }: LoginProps) => { horizontal: 'center', }); + useEffect(() => { + const list = loadPending(); + setPendingCount(list.length); + }, []); + + const handleContinuePending = () => { + const list = loadPending(); + if (!list.length) return; + + const last = list[0]; + + // чтобы Registration подхватил и сразу открыл verification + // можно также записать в launcher_config — удобно + saveConfig({ username: last.username, password: last.password ?? '' }); + + navigate('/registration', { replace: true }); + }; + const showNotification = ( message: React.ReactNode, severity: 'success' | 'info' | 'warning' | 'error' = 'info', @@ -145,13 +279,16 @@ const Login = ({ onLoginSuccess }: LoginProps) => { const startQrLogin = async () => { setQrLoading(true); + setQrState('idle'); setQrUrl(''); stopQrPolling(); try { const init = await qrInit(deviceId); setQrUrl(init.qr_url); + setQrState('ready'); + setQrState('polling'); pollTimerRef.current = window.setInterval(async () => { try { const res = await qrStatus(init.token, deviceId); @@ -178,6 +315,7 @@ const Login = ({ onLoginSuccess }: LoginProps) => { if (res.status === 'expired') { stopQrPolling(); + setQrState('expired'); showNotification('QR-код истёк. Нажми “Обновить QR”.', 'warning'); } } catch { @@ -320,147 +458,421 @@ const Login = ({ onLoginSuccess }: LoginProps) => { return ( {loading ? ( ) : ( - <> + + - {/* ===== QR экран по умолчанию ===== */} - {!showPasswordLogin ? ( - - - - Вход через Telegram - + + {/* header */} + + - + {!showPasswordLogin ? ( + + + Вход через Telegram + - {/* QR контейнер */} -
- - - {qrLoading && } - - Обновить QR - - - Войти по логину и паролю - - navigate('/registration')} - sx={gradientTextSx} + - Зарегистрироваться + {qrState === 'polling' ? 'ожидание' : qrState === 'expired' ? 'истёк' : 'готов'} + + + ) : ( + + + Вход по логину и паролю + + + )} + + {/* segmented */} + {pendingCount > 0 ? ( + + {/* */} + + У вас {pendingCount} неподтвержденный аккаунт. Подтвердить сейчас - - ) : ( - /* ===== экран логин/пароль (в стиле Registration.tsx) ===== */ - - { + if (!v) return; + if (v === 'password') goToPasswordLogin(); + else backToQr(); }} + sx={{ mb: '1vw' }} > - Вход по логину и паролю - + + + + Telegram QR + + - + + + + Логин + пароль + + + + )} + - - - + + {/* content */} + {!showPasswordLogin ? ( - navigate('/registration')} - sx={gradientTextSx} + {/* QR card */} + - Зарегистрироваться - - - Назад к QR - + {/* subtle top glow like marketplace cards */} + + + + {/* IMPORTANT: relative wrapper so expired overlay is positioned correctly */} + + +
+ + {qrState === 'expired' && ( + + + QR-код истёк + + + Нажми “Обновить QR”, чтобы получить новый + + + )} + + + + {qrState === 'polling' && 'Ожидаем подтверждение…'} + {qrState === 'ready' && 'Сканируй QR в Telegram'} + {qrState === 'expired' && 'Нужно обновить QR'} + {qrState === 'idle' && 'Подготавливаем вход…'} + + + + + {/* actions */} + + + Вход через Telegram + + + + 1) Открой бота
+ 2) Сканируй QR
+ 3) Подтверди вход +
+ + qrUrl && window.open(qrUrl, '_blank')} + disabled={!qrUrl} + startIcon={} + sx={{ py: 1.2, fontSize: 'clamp(12px, 0.95vw, 14px)' }} + > + Открыть бота + + + + + + + + + {qrLoading && } +
- - )} - + ) : ( + /* password */ + + + + Вход по логину и паролю + + + + + + + + + Войти + + + + + + + + + + + )} + + )} { if (!username || !selectedServer || !removeItem) return; + if (!canBuyOnSelectedServer) { + showNotification( + 'Для снятия товара с продажи нужно быть на сервере.', + 'error', + ); + return; + } + try { await cancelMarketplaceItemSale(username, removeItem.id); - showNotification('Товар снят с продажи', 'success', { - vertical: 'bottom', - horizontal: 'left', - }); + showNotification('Товар снят с продажи', 'success'); setRemoveOpen(false); setRemoveItem(null); @@ -274,7 +272,6 @@ export default function Marketplace() { showNotification( e instanceof Error ? e.message : 'Ошибка при снятии товара', 'error', - { vertical: 'bottom', horizontal: 'left' }, ); } }; @@ -332,7 +329,6 @@ export default function Marketplace() { showNotification( 'Чтобы покупать предметы, нужно находиться на сервере игры.', 'warning', - { vertical: 'bottom', horizontal: 'left' }, ); return; } @@ -346,7 +342,6 @@ export default function Marketplace() { {translateServer(selectedServer?.name ?? '')}. , 'info', - { vertical: 'bottom', horizontal: 'left' }, ); return; } @@ -359,7 +354,6 @@ export default function Marketplace() { showNotification( result.message || 'Предмет успешно куплен! Он будет добавлен в ваш инвентарь.', 'success', - { vertical: 'bottom', horizontal: 'left' }, ); // обновляем список предметов @@ -369,7 +363,6 @@ export default function Marketplace() { showNotification( error instanceof Error ? error.message : 'Ошибка при покупке предмета', 'error', - { vertical: 'bottom', horizontal: 'left' }, ); } }; @@ -1167,10 +1160,7 @@ export default function Marketplace() { serverIp={playerServer.ip} onSellSuccess={() => { if (selectedServer) loadMarketItems(selectedServer.ip, 1); - showNotification('Предмет успешно выставлен на продажу!', 'success', { - vertical: 'bottom', - horizontal: 'left', - }); + showNotification('Предмет успешно выставлен на продажу!', 'success'); }} /> ) : ( diff --git a/src/renderer/pages/Registration.tsx b/src/renderer/pages/Registration.tsx index 79102d2..2cd3eb2 100644 --- a/src/renderer/pages/Registration.tsx +++ b/src/renderer/pages/Registration.tsx @@ -1,7 +1,7 @@ import Stepper from '@mui/material/Stepper'; import Step from '@mui/material/Step'; import StepLabel from '@mui/material/StepLabel'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { StepConnector, stepConnectorClasses, @@ -10,16 +10,29 @@ import { Typography, Box, Button, + Paper, + Stack, + Divider, } from '@mui/material'; +import { alpha, keyframes } from '@mui/material/styles'; + import LoginRoundedIcon from '@mui/icons-material/LoginRounded'; import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded'; import AssignmentIndRoundedIcon from '@mui/icons-material/AssignmentIndRounded'; +import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded'; +import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded'; + import QRCodeStyling from 'qr-code-styling'; + +import useAuth from '../hooks/useAuth'; +import useConfig from '../hooks/useConfig'; + import { generateVerificationCode, registerUser, getVerificationStatus, } from '../api'; + import popalogo from '../../../assets/icons/popa-popa.svg'; import GradientTextField from '../components/GradientTextField'; import { FullScreenLoader } from '../components/FullScreenLoader'; @@ -30,15 +43,96 @@ import { getNotifPositionFromSettings, } from '../utils/notifications'; import { useNavigate } from 'react-router-dom'; +import { upsertPending, removePending, loadPending } from '../utils/pendingVerification'; + +/* ======================= + Shared “premium” styling + ======================= */ + +const GRADIENT = + 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)'; + +const glowPulse = keyframes` + 0% { transform: translate3d(0,0,0) scale(1); opacity: .85; filter: saturate(1.0); } + 50% { transform: translate3d(0,-6px,0) scale(1.02); opacity: 1; filter: saturate(1.15); } + 100% { transform: translate3d(0,0,0) scale(1); opacity: .85; filter: saturate(1.0); } +`; + +const borderShimmer = keyframes` + 0% { background-position: 0% 50%; opacity: .35; } + 50% { background-position: 100% 50%; opacity: .55; } + 100% { background-position: 0% 50%; opacity: .35; } +`; + +const GlassPaper = styled(Paper)(() => ({ + position: 'relative', + overflow: 'hidden', + borderRadius: 28, + background: 'rgba(0,0,0,0.35)', + border: '1px solid rgba(255,255,255,0.10)', + backdropFilter: 'blur(14px)', + boxShadow: '0 20px 60px rgba(0,0,0,0.45)', +})); + +const Glow = styled('div')(() => ({ + position: 'absolute', + inset: -2, + background: + 'radial-gradient(800px 300px at 20% 10%, rgba(242,113,33,0.22), transparent 60%),' + + 'radial-gradient(800px 300px at 80% 0%, rgba(233,64,205,0.18), transparent 55%),' + + 'radial-gradient(900px 420px at 50% 110%, rgba(138,35,135,0.20), transparent 60%)', + pointerEvents: 'none', + animation: `${glowPulse} 6s ease-in-out infinite`, +})); + +const GradientTitle = styled(Typography)(() => ({ + fontWeight: 900, + backgroundImage: + 'linear-gradient(136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + fontFamily: 'Benzin-Bold, sans-serif', +})); + +const GradientButton = styled(Button)(() => ({ + background: GRADIENT, + fontFamily: 'Benzin-Bold, sans-serif', + borderRadius: 999, + textTransform: 'none', + transition: 'transform 0.25s ease, filter 0.25s ease, box-shadow 0.25s ease', + boxShadow: '0 12px 30px rgba(0,0,0,0.35)', + '&:hover': { + transform: 'scale(1.04)', + filter: 'brightness(1.06)', + boxShadow: '0 16px 42px rgba(0,0,0,0.48)', + background: GRADIENT, + }, + '&:disabled': { + background: 'rgba(255,255,255,0.08)', + color: 'rgba(255,255,255,0.35)', + boxShadow: 'none', + }, +})); + +const SoftButton = styled(Button)(() => ({ + borderRadius: 999, + fontFamily: 'Benzin-Bold, sans-serif', + color: '#fff', + border: '1px solid rgba(255,255,255,0.12)', + background: 'rgba(255,255,255,0.06)', + textTransform: 'none', + '&:hover': { background: 'rgba(255,255,255,0.08)' }, +})); + +/* ======================= + Stepper styling (your base) + ======================= */ const ColorlibConnector = styled(StepConnector)(({ theme }) => ({ - [`&.${stepConnectorClasses.alternativeLabel}`]: { - top: 22, - }, + [`&.${stepConnectorClasses.alternativeLabel}`]: { top: 22 }, [`&.${stepConnectorClasses.active}`]: { [`& .${stepConnectorClasses.line}`]: { backgroundImage: - // 'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)', 'linear-gradient( 95deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)', }, }, @@ -89,9 +183,7 @@ const ColorlibStepIconRoot = styled('div')<{ }, { props: ({ ownerState }) => ownerState.completed, - style: { - backgroundImage: '#adadad', - }, + style: { backgroundImage: '#adadad' }, }, ], })); @@ -106,58 +198,37 @@ function ColorlibStepIcon(props: StepIconProps) { }; return ( - + {icons[String(props.icon)]} ); } -const qrCode = new QRCodeStyling({ - width: 300, - height: 300, - image: popalogo, - data: 'https://t.me/popa_popa_popa_bot?start=test', - shape: 'square', - margin: 10, - dotsOptions: { - gradient: { - type: 'linear', - colorStops: [ - { - offset: 0, - color: 'rgb(242,113,33)', - }, - { - offset: 1, - color: 'rgb(233,64,87)', - }, - ], - }, - type: 'extra-rounded', - }, - imageOptions: { - crossOrigin: 'anonymous', - margin: 20, - imageSize: 0.5, - }, - backgroundOptions: { - color: 'transparent', - }, -}); +/* ======================= + Component + ======================= */ export const Registration = () => { + const navigate = useNavigate(); + + const auth = useAuth(); + const { saveConfig } = useConfig(); + const [activeStep, setActiveStep] = useState(0); + const steps = useMemo( + () => ['Создание аккаунта', 'Верификация в Telegram'], + [], + ); + const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [enterpassword, setEnterPassword] = useState(''); - const [verificationCode, setVerificationCode] = useState(null); - const ref = useRef(null); - const [url, setUrl] = useState(''); - const steps = ['Создание аккаунта', 'Верификация аккаунта в телеграмме']; + const [verificationCode, setVerificationCode] = useState(null); + const [url, setUrl] = useState(''); + const [verifyState, setVerifyState] = useState<'idle' | 'waiting' | 'verified'>('idle'); + + // Notifications const [notifOpen, setNotifOpen] = useState(false); const [notifMsg, setNotifMsg] = useState(''); const [notifSeverity, setNotifSeverity] = useState< @@ -169,8 +240,6 @@ export const Registration = () => { horizontal: 'center', }); - const navigate = useNavigate(); - const showNotification = ( message: React.ReactNode, severity: 'success' | 'info' | 'warning' | 'error' = 'info', @@ -183,57 +252,24 @@ export const Registration = () => { setNotifOpen(true); }; - useEffect(() => { - if (ref.current) { - qrCode.append(ref.current); - } - }, []); + // QR + const qrRef = useRef(null); + const qrInstanceRef = useRef(null); + const pollTimerRef = useRef(null); - useEffect(() => { - qrCode.update({ - data: url, - }); - }, [url]); - - const handleCreateAccount = async () => { - if (!username || !password || !enterpassword) { - showNotification('Заполните все поля', 'warning'); - return; - } - - if (password !== enterpassword) { - showNotification('Пароли не совпадают', 'warning'); - return; - } - - try { - const response = await registerUser(username, password); - - if (response.status === 'success') { - setActiveStep(1); - showNotification('Аккаунт создан. Перейдите к верификации.', 'success'); - } else { - showNotification(String(response.status), 'error'); - } - } catch (e: any) { - showNotification('Ошибка регистрации', 'error'); + const stopPolling = () => { + if (pollTimerRef.current) { + window.clearInterval(pollTimerRef.current); + pollTimerRef.current = null; } }; - const handleClose = () => { - setOpen(false); - }; + useEffect(() => () => stopPolling(), []); + // init qr instance once useEffect(() => { - if (activeStep === 1) { - handleGenerateVerificationCode(username); - setUrl(`https://t.me/popa_popa_popa_bot?start=${username}`); - - while (ref.current.firstChild) { - ref.current.removeChild(ref.current.firstChild); - } - - const newQrCode = new QRCodeStyling({ + if (!qrInstanceRef.current) { + qrInstanceRef.current = new QRCodeStyling({ width: 300, height: 300, image: popalogo, @@ -244,14 +280,8 @@ export const Registration = () => { gradient: { type: 'linear', colorStops: [ - { - offset: 0, - color: 'rgb(242,113,33)', - }, - { - offset: 1, - color: 'rgb(233,64,87)', - }, + { offset: 0, color: 'rgb(242,113,33)' }, + { offset: 1, color: 'rgb(233,64,87)' }, ], }, type: 'extra-rounded', @@ -265,56 +295,196 @@ export const Registration = () => { color: 'transparent', }, }); - - newQrCode.update({ - data: `https://t.me/popa_popa_popa_bot?start=${username}`, - }); - - setUrl(`https://t.me/popa_popa_popa_bot?start=${username}`); - - newQrCode.append(ref.current); - - const intervalId = setInterval(() => { - handleVerifyCode(); - }, 5000); - - return () => { - clearInterval(intervalId); - }; } + }, []); + + // append QR when step 1 (verification) + useEffect(() => { + if (activeStep !== 1) return; + if (!qrRef.current || !qrInstanceRef.current) return; + + while (qrRef.current.firstChild) qrRef.current.removeChild(qrRef.current.firstChild); + qrInstanceRef.current.append(qrRef.current); }, [activeStep]); - const handleGenerateVerificationCode = async (username: string) => { - console.log(username); - const response = await generateVerificationCode(username); + useEffect(() => { + if (!qrInstanceRef.current) return; + if (!url) return; + qrInstanceRef.current.update({ data: url }); + }, [url]); + + const handleCreateAccount = async () => { + if (!username || !password || !enterpassword) { + showNotification('Заполните все поля', 'warning'); + return; + } + if (password !== enterpassword) { + showNotification('Пароли не совпадают', 'warning'); + return; + } + + try { + const response = await registerUser(username, password); + + if (response.status === 'success') { + upsertPending({ username, password, createdAt: Date.now() }); + setActiveStep(1); + showNotification('Аккаунт создан. Перейдите к верификации.', 'success'); + } else { + showNotification(String(response.status), 'error'); + } + } catch { + showNotification('Ошибка регистрации', 'error'); + } + }; + + const copyVerificationCode = async () => { + if (!verificationCode) return; + + try { + await navigator.clipboard.writeText(verificationCode); + showNotification('Код скопирован', 'success'); + } catch { + // fallback для старых браузеров / запретов clipboard + try { + const ta = document.createElement('textarea'); + ta.value = verificationCode; + ta.style.position = 'fixed'; + ta.style.left = '-9999px'; + ta.style.top = '-9999px'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + showNotification('Код скопирован', 'success'); + } catch { + showNotification('Не удалось скопировать код', 'error'); + } + } + }; + + const handleGenerateVerificationCode = async (u: string) => { + const response = await generateVerificationCode(u); setVerificationCode(response.code); }; const handleVerifyCode = async () => { - const response = await getVerificationStatus(username); - if (response.is_verified) { + try { + const response = await getVerificationStatus(username); + + if (!response.is_verified) { + setVerifyState('waiting'); + return; + } + + setVerifyState('verified'); + stopPolling(); + showNotification('Аккаунт подтверждён! Входим…', 'success'); + + removePending(username); + + saveConfig({ username, password }); + await auth.authenticateUser(username, password, saveConfig); + + navigate('/', { replace: true }); + } catch (e: any) { + // если верификация ок, но логин не вышел + console.error(e); + showNotification('Аккаунт подтверждён, но вход не удался. Попробуй войти вручную.', 'warning'); navigate('/login', { replace: true }); } }; + useEffect(() => { + // если user вернулся/зашел заново на регистрацию — восстановим pending + const list = loadPending(); + if (!list.length) return; + + // Берём самый свежий + const last = list[0]; + + // Если мы еще на шаге 0 — можно предложить / автоперейти + setUsername(last.username); + if (last.password) setPassword(last.password); + + // сразу на verification + setActiveStep(1); + }, []); + + const startVerificationFlow = async () => { + if (!username) return; + + setVerifyState('idle'); + setVerificationCode(null); + + const newUrl = `https://t.me/popa_popa_popa_bot?start=${username}`; + setUrl(newUrl); + + try { + await handleGenerateVerificationCode(username); + setVerifyState('waiting'); + } catch { + showNotification('Не удалось получить код верификации', 'error'); + } + + stopPolling(); + pollTimerRef.current = window.setInterval(() => { + handleVerifyCode(); + }, 5000); + }; + + // when switch to verification step + useEffect(() => { + if (activeStep !== 1) return; + startVerificationFlow(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeStep]); + const handleOpenBot = () => { window.open(`https://t.me/popa_popa_popa_bot?start=${username}`, '_blank'); }; return ( - <> - } - sx={{ - position: 'absolute', - top: '10%', - }} // чтобы отделить степпер от формы - > - {steps.map((label) => ( - - + + + + + {/* Header */} + + + Регистрация + + + + Создай аккаунт и подтверди его в Telegram — это займёт минуту. + + + + {/* Stepper inside card (not absolute) */} + + } sx={{ '& .MuiStepLabel-label': { color: '#adadad !important', @@ -331,153 +501,408 @@ export const Registration = () => { transition: 'all 1s ease', }, }} - StepIconComponent={ColorlibStepIcon} > - {label} - - - ))} - - - - {activeStep === 0 && ( - - setUsername(e.target.value)} - /> - setPassword(e.target.value)} - /> - setEnterPassword(e.target.value)} - error={Boolean(enterpassword) && password !== enterpassword} - helperText={ - Boolean(enterpassword) && password !== enterpassword - ? 'Пароли не совпадают' - : '⠀' - } - sx={{ mb: '0vw' }} - /> - + {steps.map((label) => ( + + + {label} + + + ))} + - )} - {activeStep === 1 && ( - - -
+ + Данные аккаунта + + + + setUsername(e.target.value)} + sx={{ + '& .MuiInputLabel-root.Mui-focused': { + display: 'none', + }, + '& .MuiInputLabel-root.MuiInputLabel-shrink': { + display: 'none', + }, + }} + /> + + setPassword(e.target.value)} + sx={{ + '& .MuiInputLabel-root.Mui-focused': { + display: 'none', + }, + '& .MuiInputLabel-root.MuiInputLabel-shrink': { + display: 'none', + }, + }} + /> + + setEnterPassword(e.target.value)} + error={Boolean(enterpassword) && password !== enterpassword} + helperText={ + Boolean(enterpassword) && password !== enterpassword + ? 'Пароли не совпадают' + : '⠀' + } + sx={{ + '& .MuiInputLabel-root.Mui-focused': { + display: 'none', + }, + '& .MuiInputLabel-root.MuiInputLabel-shrink': { + display: 'none', + }, + }} + /> + + + Создать аккаунт + + + navigate('/login')} + sx={{ py: 1.1, fontSize: 'clamp(12px, 0.92vw, 14px)' }} + > + Уже есть аккаунт? Войти + + + + + {/* Right: tips card */} + + + Как это работает + + + + + 1) Создаёшь аккаунт + + + Придумай никнейм и пароль. + + + + 2) Подтверждаешь в Telegram + + + Мы покажем QR и код — введёшь код в боте, и всё готово. + + + + + Подсказка: если Telegram не на этом устройстве — нажми “Открыть бота” + и продолжай в браузере/приложении. + + + + + + ) : ( + // Step 1: Verification + - - Введите код верификации в боте - - {verificationCode ? ( - <> - + {/* QR card */} + + + + + + {/* shimmer border */} + + +
+ + + + {verifyState === 'verified' + ? 'Подтверждено — перенаправляем…' : (verificationCode) ? 'Ждём ответ от бота...' + : 'Сканируй QR в Telegram'} + + + + + {/* Actions & code card */} + + + + Верификация + + + + {verifyState === 'waiting' + ? 'ожидание' + : verifyState === 'verified' + ? 'готово' + : 'старт'} + + + + - {verificationCode} + 1) Открой бота
+ 2) Введи код ниже
+ 3) Жди подтверждения
- Ждем ответа от бота - - - ) : ( - - )} - - )} - setNotifOpen(false)} - autoHideDuration={2500} - /> - - + } + disabled={!username} + sx={{ py: 1.2, fontSize: 'clamp(12px, 0.95vw, 14px)' }} + > + Открыть бота + + + } + sx={{ py: 1.1, fontSize: 'clamp(12px, 0.92vw, 14px)' }} + > + Обновить код / перезапустить + + + + + + + Код верификации + + + {verificationCode && ( + { + if (e.key === 'Enter' || e.key === ' ') copyVerificationCode(); + }} + sx={{ + mt: 0.4, + fontSize: 'clamp(28px, 2.4vw, 44px)', + fontFamily: 'Benzin-Bold, sans-serif', + backgroundImage: + 'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + + cursor: 'pointer', + userSelect: 'none', + position: 'relative', + display: 'inline-block', + + // “наведение” + '&:hover': { + transform: 'scale(1.03)', + filter: 'brightness(1.08)', + }, + + // “нажатие” + '&:active': { + transform: 'scale(0.99)', + }, + + // фокус для клавиатуры + outline: 'none', + '&:focus-visible': { + textShadow: '0 0 18px rgba(233,64,205,0.35)', + }, + + transition: 'transform 120ms ease, filter 120ms ease, text-shadow 120ms ease', + }} + > + {verificationCode} + + )} + + Нажми на код, чтобы скопировать + + + + navigate('/login', { replace: true })} + sx={{ py: 1.0, fontSize: 'clamp(12px, 0.92vw, 14px)' }} + > + Перейти ко входу + +
+ + )} + + + + setNotifOpen(false)} + autoHideDuration={2500} + /> + ); }; diff --git a/src/renderer/pages/Shop.tsx b/src/renderer/pages/Shop.tsx index 07c07a5..c7038a8 100644 --- a/src/renderer/pages/Shop.tsx +++ b/src/renderer/pages/Shop.tsx @@ -61,6 +61,9 @@ function getRarityColor(weight?: number): string { } } +const GRADIENT = + 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)'; + export default function Shop() { const [storeCapes, setStoreCapes] = useState([]); const [userCapes, setUserCapes] = useState([]); @@ -120,6 +123,18 @@ export default function Shop() { type: 'success', }); + const showNotification = ( + message: React.ReactNode, + severity: 'success' | 'info' | 'warning' | 'error' = 'info', + position: NotificationPosition = getNotifPositionFromSettings(), + ) => { + if (!isNotificationsEnabled()) return; + setNotifMsg(message); + setNotifSeverity(severity); + setNotifPos(position); + setNotifOpen(true); + }; + const loadBonuses = async (username: string) => { try { setBonusesLoading(true); @@ -133,10 +148,7 @@ export default function Shop() { console.error('Ошибка при получении прокачек:', error); if (!isNotificationsEnabled()) return; - setNotifMsg('Ошибка при загрузке прокачки!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Ошибка при загрузке прокачки!', 'error') } finally { setBonusesLoading(false); } @@ -182,18 +194,12 @@ export default function Shop() { playBuySound(); if (!isNotificationsEnabled()) return; - setNotifMsg('Плащ успешно куплен!'); - setNotifSeverity('success'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Плащ успешно куплен!', 'success') } catch (error) { console.error('Ошибка при покупке плаща:', error); if (!isNotificationsEnabled()) return; - setNotifMsg('Ошибка при покупке плаща!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Ошибка при покупке плаща!', 'error') } }; @@ -275,10 +281,7 @@ export default function Shop() { if (!username) { if (!isNotificationsEnabled()) return; - setNotifMsg('Не найдено имя игрока. Авторизируйтесь в лаунчере!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Не найдено имя игрока. Авторизируйтесь в лаунчере!', 'error') return; } @@ -291,18 +294,12 @@ export default function Shop() { await loadBonuses(username); if (!isNotificationsEnabled()) return; - setNotifMsg('Прокачка успешно куплена!'); - setNotifSeverity('success'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Прокачка успешно куплена!', 'success') } catch (error) { console.error('Ошибка при покупке прокачки:', error); if (!isNotificationsEnabled()) return; - setNotifMsg('Ошибка при прокачке!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Ошибка при прокачке!', 'error') } }); }; @@ -317,18 +314,12 @@ export default function Shop() { await loadBonuses(username); if (!isNotificationsEnabled()) return; - setNotifMsg('Бонус улучшен!'); - setNotifSeverity('success'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Бонус улучшен!', 'success') } catch (error) { console.error('Ошибка при улучшении бонуса:', error); if (!isNotificationsEnabled()) return; - setNotifMsg('Ошибка при улучшении бонуса!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Ошибка при улучшении бонуса!', 'error') } }); }; @@ -343,10 +334,7 @@ export default function Shop() { } catch (error) { console.error('Ошибка при переключении бонуса:', error); if (!isNotificationsEnabled()) return; - setNotifMsg('Ошибка при переключении бонуса!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Ошибка при переключении бонуса!', 'error') } }); }; @@ -389,33 +377,22 @@ const filteredCases = (cases || []).filter((c) => { const handleOpenCase = async (caseData: Case) => { if (!username) { if (!isNotificationsEnabled()) return; - setNotifMsg('Не найдено имя игрока. Авторизуйтесь в лаунчере!'); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Не найдено имя игрока. Авторизуйтесь в лаунчере!', 'error') return; } if (!selectedCaseServerIp) { if (!isNotificationsEnabled()) return; - setNotifMsg('Выберите сервер для открытия кейса!'); - setNotifSeverity('warning'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Выберите сервер для открытия кейса!', 'warning') return; } const allowedIps = caseData.server_ips || []; if (allowedIps.length > 0 && !allowedIps.includes(selectedCaseServerIp)) { if (!isNotificationsEnabled()) return; - setNotifMsg( - `Этот кейс доступен на: ${allowedIps + showNotification(`Этот кейс доступен на: ${allowedIps .map((ip) => translateServer(`Server ${ip}`)) - .join(', ')}`, - ); - setNotifSeverity('warning'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + .join(', ')}`, 'warning') return; } @@ -437,18 +414,12 @@ const filteredCases = (cases || []).filter((c) => { playBuySound(); if (!isNotificationsEnabled()) return; - setNotifMsg('Кейс открыт!'); - setNotifSeverity('success'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification('Кейс открыт!', 'success') } catch (error) { console.error('Ошибка при открытии кейса:', error); if (!isNotificationsEnabled()) return; - setNotifMsg(String(error instanceof Error ? error.message : 'Ошибка при открытии кейса!')); - setNotifSeverity('error'); - setNotifPos({ vertical: 'top', horizontal: 'center' }); - setNotifOpen(true); + showNotification((String(error instanceof Error ? error.message : 'Ошибка при открытии кейса!')), 'error') } finally { setIsOpening(false); } @@ -617,28 +588,99 @@ const filteredCases = (cases || []).filter((c) => { {caseServers.length > 0 && ( - - - Сервер - - setSelectedCaseServerIp(String(e.target.value))} + MenuProps={{ PaperProps: { sx: { + mt: 1, bgcolor: 'rgba(10,10,20,0.96)', border: '1px solid rgba(255,255,255,0.10)', borderRadius: '1vw', backdropFilter: 'blur(14px)', + '& .MuiMenuItem-root': { color: 'rgba(255,255,255,0.9)', fontFamily: 'Benzin-Bold', + fontSize: '1.1vw', }, '& .MuiMenuItem-root.Mui-selected': { - backgroundColor: 'rgba(242,113,33,0.16)', + backgroundColor: 'rgba(242,113,33,0.18)', }, '& .MuiMenuItem-root:hover': { backgroundColor: 'rgba(233,64,205,0.14)', @@ -646,37 +688,75 @@ const filteredCases = (cases || []).filter((c) => { }, }, }} - sx={{ - borderRadius: '999px', - bgcolor: 'rgba(255,255,255,0.04)', - color: 'rgba(255,255,255,0.92)', - fontFamily: 'Benzin-Bold', - '& .MuiSelect-select': { - py: '0.9vw', - px: '1.2vw', - }, - '& .MuiOutlinedInput-notchedOutline': { - borderColor: 'rgba(255,255,255,0.14)', - }, - '&:hover .MuiOutlinedInput-notchedOutline': { - borderColor: 'rgba(242,113,33,0.55)', - }, - '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: 'rgba(233,64,205,0.65)', - borderWidth: '2px', - }, - '& .MuiSelect-icon': { - color: 'rgba(255,255,255,0.75)', - }, - }} - > - {caseServers.map((ip) => ( - - {translateServer(`Server ${ip}`)} - - ))} - - + > + {caseServers.map((ip) => ( + + {translateServer(`Server ${ip}`)} + + ))} + + + + // + // + // Сервер + // + // + // )} diff --git a/src/renderer/utils/pendingVerification.ts b/src/renderer/utils/pendingVerification.ts new file mode 100644 index 0000000..83f91db --- /dev/null +++ b/src/renderer/utils/pendingVerification.ts @@ -0,0 +1,43 @@ +export type PendingVerification = { + username: string; + password?: string; + createdAt: number; +}; + +const PENDING_KEY = 'pending_verifications_v1'; + +export const loadPending = (): PendingVerification[] => { + try { + const raw = localStorage.getItem(PENDING_KEY); + return raw ? (JSON.parse(raw) as PendingVerification[]) : []; + } catch { + return []; + } +}; + +const savePending = (items: PendingVerification[]) => { + localStorage.setItem(PENDING_KEY, JSON.stringify(items)); +}; + +export const upsertPending = (item: PendingVerification) => { + const list = loadPending(); + const next = [ + item, + ...list.filter( + (x) => x.username.toLowerCase() !== item.username.toLowerCase(), + ), + ].slice(0, 5); + + savePending(next); +}; + +export const removePending = (username: string) => { + const list = loadPending(); + savePending( + list.filter((x) => x.username.toLowerCase() !== username.toLowerCase()), + ); +}; + +export const clearPending = () => { + localStorage.removeItem(PENDING_KEY); +}; \ No newline at end of file