diff --git a/package-lock.json b/package-lock.json index e5d00a1..0590d47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,10 @@ "file-saver": "^2.0.5", "lodash": "^4.17.21", "moment": "^2.30.1", - "react": "^18.2.0", - "react-copy-to-clipboard": "^5.1.0", + "qapp-core": "^1.0.22", + "react": "^19.1.0", "react-countdown-circle-timer": "^3.2.1", - "react-dom": "^18.2.0", + "react-dom": "^19.1.0", "react-ga4": "^2.1.0", "react-loader-spinner": "^6.1.6", "react-qr-code": "^2.0.15", @@ -33,8 +33,8 @@ }, "devDependencies": { "@types/lodash": "^4.17.5", - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -1791,15 +1791,15 @@ } }, "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -1808,6 +1808,11 @@ "stylis": "4.2.0" } }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -1841,11 +1846,6 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, - "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" - }, "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", @@ -1865,17 +1865,17 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -1915,16 +1915,16 @@ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { - "version": "11.11.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", - "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.2", - "@emotion/serialize": "^1.1.4", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -1936,15 +1936,29 @@ } } }, + "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/styled/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", "peerDependencies": { "react": ">=16.8.0" } @@ -1955,9 +1969,9 @@ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", @@ -3109,6 +3123,31 @@ "string.prototype.matchall": "^4.0.6" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", + "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", + "dependencies": { + "@tanstack/virtual-core": "3.13.6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", + "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3179,21 +3218,22 @@ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/react": { - "version": "18.2.79", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", - "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.25", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz", - "integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==", + "version": "19.1.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz", + "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", "dev": true, - "dependencies": { - "@types/react": "*" + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/@types/react-transition-group": { @@ -3210,6 +3250,11 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/seedrandom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz", + "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==" + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -3219,13 +3264,14 @@ "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true + "devOptional": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.7.0", @@ -3610,6 +3656,14 @@ "node": ">= 4.0.0" } }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -3703,6 +3757,33 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3714,6 +3795,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bloom-filters": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bloom-filters/-/bloom-filters-3.0.4.tgz", + "integrity": "sha512-BdnPWo2OpYhlvuP2fRzJBdioMCkm7Zp0HCf8NJgF5Mbyqy7VQ/CnTiVWMMyq4EZCBHwj0Kq6098gW2/3RsZsrA==", + "dependencies": { + "@types/seedrandom": "^3.0.8", + "base64-arraybuffer": "^1.0.2", + "is-buffer": "^2.0.5", + "lodash": "^4.17.21", + "long": "^5.2.0", + "reflect-metadata": "^0.1.13", + "seedrandom": "^3.0.5", + "xxhashjs": "^0.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/blueimp-canvas-to-blob": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3766,6 +3870,29 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3815,6 +3942,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3933,6 +4061,15 @@ "node": ">=4.0.0" } }, + "node_modules/compressorjs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz", + "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==", + "dependencies": { + "blueimp-canvas-to-blob": "^3.29.0", + "is-blob": "^2.1.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3945,14 +4082,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/copy-to-clipboard": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", - "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "dependencies": { - "toggle-selection": "^1.0.6" - } - }, "node_modules/core-js-compat": { "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", @@ -3995,6 +4124,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -4008,6 +4142,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", "engines": { "node": ">=4" } @@ -4016,6 +4151,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", @@ -4027,6 +4163,11 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==" + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -4078,6 +4219,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -4179,11 +4325,20 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -4778,6 +4933,22 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/file-selector/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -5110,6 +5281,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5232,6 +5411,25 @@ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "dev": true }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5344,6 +5542,17 @@ "node": ">=8" } }, + "node_modules/is-blob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", + "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -5360,6 +5569,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, "node_modules/is-builtin-module": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", @@ -5901,6 +6132,11 @@ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6208,9 +6444,10 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -6233,9 +6470,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -6250,10 +6487,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6262,7 +6500,8 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -6314,6 +6553,33 @@ "node": ">=6" } }, + "node_modules/qapp-core": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.22.tgz", + "integrity": "sha512-3q8Ebr9lpyDW7lTo91Rlak2EGIAXrU9DYhUBuLsYZMuRyI/bKoc0E4hHrrAo946eJ/5AxqNGmZDkYSJq5tOTZg==", + "license": "MIT", + "dependencies": { + "@tanstack/react-virtual": "^3.13.2", + "bloom-filters": "^3.0.4", + "buffer": "^6.0.3", + "compressorjs": "^1.2.1", + "crypto-js": "^4.2.0", + "dayjs": "^1.11.13", + "dompurify": "^3.2.4", + "react-dropzone": "^14.3.8", + "react-hot-toast": "^2.5.2", + "react-intersection-observer": "^9.16.0", + "short-unique-id": "^5.2.0", + "zustand": "^4.3.2" + }, + "peerDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.1", + "@mui/material": "^7.0.1", + "react": "^19.0.0" + } + }, "node_modules/qr.js": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", @@ -6349,28 +6615,14 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/react-copy-to-clipboard": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", - "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", - "dependencies": { - "copy-to-clipboard": "^3.3.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": "^15.3.0 || 16 || 17 || 18" - } - }, "node_modules/react-countdown-circle-timer": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/react-countdown-circle-timer/-/react-countdown-circle-timer-3.2.1.tgz", @@ -6380,15 +6632,31 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.26.0" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^19.1.0" + } + }, + "node_modules/react-dropzone": { + "version": "14.3.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", + "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" } }, "node_modules/react-ga4": { @@ -6396,6 +6664,36 @@ "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-intersection-observer": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz", + "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6484,6 +6782,7 @@ "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -6506,6 +6805,11 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -6782,12 +7086,15 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "node_modules/semver": { "version": "7.6.0", @@ -6866,7 +7173,8 @@ "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -6966,9 +7274,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7122,16 +7431,17 @@ } }, "node_modules/styled-components": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", - "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.17.tgz", + "integrity": "sha512-97D7DwWanI7nN24v0D4SvbfjLE9656umNSJZkBkDIWL37aZqG/wRQ+Y9pWtXyBIM/NSfcBzHLErEsqHmJNSVUg==", + "license": "MIT", "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", "@types/stylis": "4.2.5", "css-to-react-native": "3.2.0", "csstype": "3.1.3", - "postcss": "8.4.38", + "postcss": "8.4.49", "shallowequal": "1.1.0", "stylis": "4.3.2", "tslib": "2.6.2" @@ -7151,7 +7461,8 @@ "node_modules/styled-components/node_modules/stylis": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" }, "node_modules/stylis": { "version": "4.2.0", @@ -7301,11 +7612,6 @@ "node": ">=8.0" } }, - "node_modules/toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" - }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -7330,7 +7636,8 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -7567,6 +7874,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "5.2.9", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", @@ -8087,6 +8402,14 @@ "node": ">=0.4.0" } }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dependencies": { + "cuint": "^0.2.2" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -8112,6 +8435,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index ede95c3..89c87c0 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "file-saver": "^2.0.5", "lodash": "^4.17.21", "moment": "^2.30.1", - "react": "^18.2.0", - "react-copy-to-clipboard": "^5.1.0", + "qapp-core": "^1.0.22", + "react": "^19.1.0", "react-countdown-circle-timer": "^3.2.1", - "react-dom": "^18.2.0", + "react-dom": "^19.1.0", "react-ga4": "^2.1.0", "react-loader-spinner": "^6.1.6", "react-qr-code": "^2.0.15", @@ -35,8 +35,8 @@ }, "devDependencies": { "@types/lodash": "^4.17.5", - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -46,5 +46,11 @@ "typescript": "^5.4.5", "vite": "^5.2.0", "vite-plugin-pwa": "^0.20.5" + }, + "overrides": { + "react-loader-spinner": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } } } diff --git a/src/App.css b/src/App.css index 6639473..8f95a1b 100644 --- a/src/App.css +++ b/src/App.css @@ -90,7 +90,7 @@ .ag-theme-alpine-dark .ag-row-odd, .ag-theme-alpine-dark .ag-row-even { width: 100%; - background-color: #292929 !important; /* Replace with your desired color */ + background-color: #292929; /* Replace with your desired color */ } .ag-theme-alpine-dark { @@ -143,4 +143,5 @@ border-radius: 8px; background-clip: content-box; border: 4px solid transparent; -} \ No newline at end of file +} + diff --git a/src/App.tsx b/src/App.tsx index 142b1b5..581520f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import ReactGA from "react-ga4"; import "./App.css"; -import socketService from "./services/socketService"; import GameContext, { IContextProps, UserNameAvatar, @@ -22,9 +21,7 @@ import axios from "axios"; import { executeEvent } from "./utils/events"; import { useIndexedDBContext } from "./contexts/indexedDBContext"; import { useGetOngoingTransactions } from "./components/DbComponents/OngoingTransactions"; - - - +import { GlobalProvider } from "qapp-core"; export async function sendRequestToExtension( requestType: string, @@ -66,26 +63,32 @@ export async function sendRequestToExtension( }); } - - function App() { const [userInfo, setUserInfo] = useState(null); const [qortBalance, setQortBalance] = useState(null); const [balances, setBalances] = useState({}); const [selectedCoin, setSelectedCoin] = useState("LITECOIN"); - - const foreignCoinBalance = useMemo(()=> { - if(balances[selectedCoin] === 0) return 0 - return balances[selectedCoin] || null - }, [balances, selectedCoin]) + + const foreignCoinBalance = useMemo(() => { + if (balances[selectedCoin] === 0) return 0; + return balances[selectedCoin] || null; + }, [balances, selectedCoin]); const [isAuthenticated, setIsAuthenticated] = useState(false); const [OAuthLoading, setOAuthLoading] = useState(false); const db = useIndexedDBContext(); - const [isUsingGateway, setIsUsingGateway] = useState(null) + const [isUsingGateway, setIsUsingGateway] = useState(null); const [isSocketUp, setIsSocketUp] = useState(false); // const [onGoingTrades, setOngoingTrades] = useState([]) - const {onGoingTrades, fetchOngoingTransactions, updateTransactionInDB, deleteTemporarySellOrder, updateTemporaryFailedTradeBots, fetchTemporarySellOrders, sellOrders} = useGetOngoingTransactions({qortAddress: userInfo?.address}) + const { + onGoingTrades, + fetchOngoingTransactions, + updateTransactionInDB, + deleteTemporarySellOrder, + updateTemporaryFailedTradeBots, + fetchTemporarySellOrders, + sellOrders, + } = useGetOngoingTransactions({ qortAddress: userInfo?.address }); const [userNameAvatar, setUserNameAvatar] = useState< Record >({}); @@ -101,27 +104,23 @@ function App() { setLoadingSlider, }; - const getIsUsingGateway = async ()=> { + const getIsUsingGateway = async () => { try { const res = await qortalRequest({ - action: "IS_USING_PUBLIC_NODE" - }) - setIsUsingGateway(res) - } catch (error) { - - } - } + action: "IS_USING_PUBLIC_NODE", + }); + setIsUsingGateway(res); + } catch (error) {} + }; - - useEffect(()=> { - getIsUsingGateway() - }, []) + useEffect(() => { + getIsUsingGateway(); + }, []); const resetNotification = () => { setNotification({ alertType: "", msg: "" }); }; - const userContextValue: UserContextProps = { avatar, setAvatar, @@ -164,53 +163,44 @@ function App() { askForAccountInformation(); }, [askForAccountInformation]); - - - - - - - - - const getQortBalance = async ()=> { + const getQortBalance = async () => { const balanceUrl: string = `/addresses/balance/${userInfo?.address}`; const balanceResponse = await axios(balanceUrl); - setQortBalance(balanceResponse.data?.value) - } + setQortBalance(balanceResponse.data?.value); + }; const getLTCBalance = async (coin) => { try { const response = await qortalRequest({ action: "GET_WALLET_BALANCE", - coin: getCoinLabel(coin) + coin: getCoinLabel(coin), }); - if(!response?.error){ - setBalances((prev)=> { + if (!response?.error) { + setBalances((prev) => { return { ...prev, - [coin]: +response - } - }) + [coin]: +response, + }; + }); } } catch (error) { - // + // } - } + }; useEffect(() => { - if(!userInfo?.address || !selectedCoin) return + if (!userInfo?.address || !selectedCoin) return; const intervalGetTradeInfo = setInterval(() => { - fetchOngoingTransactions() - getLTCBalance(selectedCoin) - getQortBalance() - }, 150000) - getLTCBalance(selectedCoin) - getQortBalance() + fetchOngoingTransactions(); + getLTCBalance(selectedCoin); + getQortBalance(); + }, 150000); + getLTCBalance(selectedCoin); + getQortBalance(); return () => { - clearInterval(intervalGetTradeInfo) - } - }, [userInfo?.address, isAuthenticated, selectedCoin]) - + clearInterval(intervalGetTradeInfo); + }; + }, [userInfo?.address, isAuthenticated, selectedCoin]); const handleMessage = async (event: any) => { if (event.data.type === "LOGOUT") { @@ -218,31 +208,29 @@ function App() { setUserInfo(null); setAvatar(""); setIsAuthenticated(false); - setQortBalance(null) + setQortBalance(null); localStorage.setItem("token", ""); - } else if(event.data.type === "RESPONSE_FOR_TRADES"){ - - - const response = event.data.payload - if (response?.extra?.atAddresses - ) { + } else if (event.data.type === "RESPONSE_FOR_TRADES") { + const response = event.data.payload; + if (response?.extra?.atAddresses) { try { - const status = response.callResponse === true ? 'trade-ongoing' : 'trade-failed' + const status = + response.callResponse === true ? "trade-ongoing" : "trade-failed"; const token = localStorage.getItem("token"); - // Prepare transaction data - const transactionData = { - qortalAtAddresses: response.extra.atAddresses, - qortAddress: userInfo.address, - status: status, - message: response.extra.message, - }; + // Prepare transaction data + const transactionData = { + qortalAtAddresses: response.extra.atAddresses, + qortAddress: userInfo.address, + status: status, + message: response.extra.message, + }; - // Update transactions in IndexedDB - const result = await updateTransactionInDB(transactionData); - fetchOngoingTransactions() - executeEvent("execute-get-new-block-trades", {}) + // Update transactions in IndexedDB + const result = await updateTransactionInDB(transactionData); + fetchOngoingTransactions(); + executeEvent("execute-get-new-block-trades", {}); } catch (error) { - console.log({error}) + console.log({ error }); } } } @@ -256,36 +244,33 @@ function App() { }; }, [userInfo?.address]); - const getCoinLabel = useCallback((coin?: string)=> { - switch(coin || selectedCoin){ - case "LITECOIN":{ - - return 'LTC' + const getCoinLabel = useCallback( + (coin?: string) => { + switch (coin || selectedCoin) { + case "LITECOIN": { + return "LTC"; + } + case "DOGECOIN": { + return "DOGE"; + } + case "BITCOIN": { + return "BTC"; + } + case "DIGIBYTE": { + return "DGB"; + } + case "RAVENCOIN": { + return "RVN"; + } + case "PIRATECHAIN": { + return "ARRR"; + } + default: + return null; } - case "DOGECOIN":{ - - return 'DOGE' - } - case "BITCOIN":{ - - return 'BTC' - } - case "DIGIBYTE":{ - - return 'DGB' - } - case "RAVENCOIN":{ - - return 'RVN' - } - case "PIRATECHAIN":{ - - return 'ARRR' - } - default: - return null - } - }, [selectedCoin]) + }, + [selectedCoin] + ); const gameContextValue: IContextProps = { userInfo, @@ -296,37 +281,54 @@ function App() { fetchOngoingTransactions, foreignCoinBalance, qortBalance, - isAuthenticated, + isAuthenticated, setIsAuthenticated, - OAuthLoading, + OAuthLoading, setOAuthLoading, updateTransactionInDB, sellOrders, - deleteTemporarySellOrder, updateTemporaryFailedTradeBots, fetchTemporarySellOrders, isUsingGateway, selectedCoin, setSelectedCoin, getCoinLabel + deleteTemporarySellOrder, + updateTemporaryFailedTradeBots, + fetchTemporarySellOrders, + isUsingGateway, + selectedCoin, + setSelectedCoin, + getCoinLabel, }; - return ( - - - - - - - - - } - /> - - - - - - + } /> + + + + + + + ); } diff --git a/src/components/DbComponents/OngoingTransactions.tsx b/src/components/DbComponents/OngoingTransactions.tsx index 378e74e..81a7630 100644 --- a/src/components/DbComponents/OngoingTransactions.tsx +++ b/src/components/DbComponents/OngoingTransactions.tsx @@ -5,7 +5,7 @@ import { useIndexedDBContext } from "../../contexts/indexedDBContext"; const fetchTradeInfo = async (qortalAtAddress) => { - const checkIfOfferingRes = await fetch(`/crosschain/trade/${qortalAtAddress}`) + const checkIfOfferingRes = await fetch(`http://devnet-nodes.qortal.link:11112/crosschain/trade/${qortalAtAddress}`) const data = await checkIfOfferingRes.json() return data }; diff --git a/src/components/Grids/Table-styles.tsx b/src/components/Grids/Table-styles.tsx index 4ea70fa..14f6cf3 100644 --- a/src/components/Grids/Table-styles.tsx +++ b/src/components/Grids/Table-styles.tsx @@ -24,7 +24,6 @@ export const BuyContainer = styled(Box)(({ theme }) => ({ justifyContent: "space-between", alignItems: "center", bottom: "0px", - height: "100px", padding: "18px 14px 12px 14px", background: "#323336", zIndex: 3, diff --git a/src/components/Grids/TradeOffers.tsx b/src/components/Grids/TradeOffers.tsx index 9fb80b1..41239f2 100644 --- a/src/components/Grids/TradeOffers.tsx +++ b/src/components/Grids/TradeOffers.tsx @@ -10,12 +10,13 @@ import { AgGridReact } from "ag-grid-react"; import { ColDef, RowClassParams, + RowNode, RowStyle, SizeColumnsToContentStrategy, } from "ag-grid-community"; import "ag-grid-community/styles/ag-grid.css"; import "ag-grid-community/styles/ag-theme-alpine.css"; -import InfoOutlineIcon from '@mui/icons-material/InfoOutline'; +import InfoOutlineIcon from "@mui/icons-material/InfoOutline"; import { Alert, Box, @@ -26,6 +27,7 @@ import { DialogContent, DialogContentText, DialogTitle, + FormControlLabel, IconButton, Snackbar, SnackbarCloseReason, @@ -48,9 +50,8 @@ import { MainContainer, } from "./Table-styles"; -export const baseLocalHost = window.location.host; -// export const baseLocalHost = "127.0.0.1:12391"; - +// export const baseLocalHost = window.location.host; +export const baseLocalHost = "devnet-nodes.qortal.link:11111"; import CloseIcon from "@mui/icons-material/Close"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; @@ -79,8 +80,12 @@ export const autoSizeStrategy: SizeColumnsToContentStrategy = { type: "fitCellContents", }; -export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { +export const TradeOffers: React.FC = ({ + foreignCoinBalance, + fee, +}: any) => { const [offers, setOffers] = useState([]); + const [signedUnlockingFees, setSignedUnlockingFees] = useState(null); const [qortalNames, setQortalNames] = useState({}); const { fetchOngoingTransactions, @@ -90,6 +95,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { getCoinLabel, selectedCoin, } = useContext(gameContext); + const isRemoveOrdersWithoutUnlockingFees = useRef(false); const listOfOngoingTradesAts = useMemo(() => { return ( onGoingTrades @@ -105,6 +111,14 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { message: messageInfo, } = useModal(); + const { + isShow: isShowTradesUnknownFee, + onCancel: onCancelTradesUnknownFee, + onOk: onOkTradesUnknownFee, + show: showTradesUnknownFee, + message: messageTradesUnknownFee, + } = useModal(); + const offersWithoutOngoing = useMemo(() => { return offers.filter( (item) => !listOfOngoingTradesAts.includes(item.qortalAtAddress) @@ -122,13 +136,39 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { const offeringTrades = useRef([]); const blockedTradesList = useRef([]); const gridRef = useRef(null); - const [openShowOfferDetails, setOpenShowOfferDetails] = useState(null) + const [openShowOfferDetails, setOpenShowOfferDetails] = useState(null); const [open, setOpen] = useState(false); const [info, setInfo] = useState(null); const BuyButton = () => { return BUY; }; + const intervalGetSignedUnlockingFees = useRef(null); + + const signedUnlockingFeesRef = useRef(signedUnlockingFees); + const feeRef = useRef(fee); + + const knownFees = useMemo(() => { + const offersWithKnownFees = []; + + selectedOffers.forEach((offer) => { + const feeEntry = signedUnlockingFees.find( + (item) => item?.atAddress === offer.qortalAtAddress + ); + + if (feeEntry && typeof feeEntry.fee === "number") { + offersWithKnownFees.push({ ...offer, fee: feeEntry.fee }); + } + }); + const totalKnownFees = offersWithKnownFees.reduce((sum, offer) => { + return sum + (offer.fee || 0); + }, 0); + const feeInLtc = totalKnownFees / 1e8; + return +feeInLtc.toFixed(8); + }, [selectedOffers, signedUnlockingFees]); + + console.log("knownFees", knownFees); + const defaultColDef = { resizable: true, // Make columns resizable by default sortable: true, // Make columns sortable by default @@ -176,8 +216,6 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { } }; - - const columnDefs: ColDef[] = useMemo(() => { return [ { @@ -189,12 +227,25 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { pinned: "left", // Optional, to pin this column on the left resizable: false, suppressRowClickSelection: true, - - cellRenderer: (params) => - { - setOpenShowOfferDetails(params?.node?.data) - }} />, - // suppressRowClickSelection: true, // prevent whole row selection on click + + cellRenderer: (params) => ( + { + const hasSignedFee = signedUnlockingFees?.find( + (item) => + item?.atAddress === params?.node?.data?.qortalAtAddress + ); + let fee = null; + if (hasSignedFee) { + fee = hasSignedFee.fee; + } + + setOpenShowOfferDetails({ ...(params?.node?.data || {}), fee }); + }} + /> + ), + // suppressRowClickSelection: true, // prevent whole row selection on click }, { headerName: "QORT AMOUNT", @@ -220,6 +271,22 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { minWidth: 150, // Ensure it doesn't shrink too much resizable: true, }, + { + headerName: `Unlocking fee`, + flex: 1, // Flex makes this column responsive + minWidth: 150, // Ensure it doesn't shrink too much + resizable: true, + valueGetter: (params) => { + if (params?.data?.qortalAtAddress) { + console.log("22signedUnlockingFees", signedUnlockingFees); + const hasSignedFee = signedUnlockingFees?.find( + (item) => item?.atAddress === params.data.qortalAtAddress + ); + if (!hasSignedFee) return "Unknown"; + return hasSignedFee.fee; + } else return "Unknown"; + }, + }, { headerName: "Seller", field: "qortalCreator", @@ -241,7 +308,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { }, }, ]; - }, [qortalNames, getCoinLabel]); + }, [qortalNames, getCoinLabel, signedUnlockingFees]); // const onRowClicked = (event: any) => { // if(listOfOngoingTradesAts.includes(event.data.qortalAtAddress)) return @@ -256,7 +323,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { const getNewBlockedTrades = async () => { const unconfirmedTransactionsList = async () => { - const unconfirmedTransactionslUrl = `/transactions/unconfirmed?txType=MESSAGE&limit=0&reverse=true`; + const unconfirmedTransactionslUrl = `http://devnet-nodes.qortal.link:11112/transactions/unconfirmed?txType=MESSAGE&limit=0&reverse=true`; var addBlockedTrades = JSON.parse( localStorage.getItem("failedTrades") || "[]" @@ -468,7 +535,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { }; useEffect(() => { - if(isUsingGateway === null) return + if (isUsingGateway === null) return; blockedTradesList.current = JSON.parse( localStorage.getItem("failedTrades") || "[]" ); @@ -489,8 +556,36 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { }; }, [isUsingGateway]); + const getSignedUnlockingFees = useCallback(async () => { + try { + const response = await fetch( + `http://devnet-nodes.qortal.link:11112/crosschain/signedfees` + ); + const data = await response.json(); + if (data && Array.isArray(data)) { + setSignedUnlockingFees(data); + } + } catch (error) { + console.error(error); + } + }, []); + + console.log("signed", signedUnlockingFees); + useEffect(() => { - if(isUsingGateway === null) return + getSignedUnlockingFees(); + intervalGetSignedUnlockingFees.current = setInterval(() => { + getSignedUnlockingFees(); + }, 150000); + return () => { + if (intervalGetSignedUnlockingFees.current) { + clearInterval(intervalGetSignedUnlockingFees.current); + } + }; + }, [getSignedUnlockingFees]); + + useEffect(() => { + if (isUsingGateway === null) return; if (selectedCoin === null) return; restartTradeOffers(); setTimeout(() => { @@ -504,13 +599,17 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { }, [isUsingGateway, selectedCoin]); const selectedTotalLTC = useMemo(() => { - return selectedOffers.reduce((acc: number, curr: any) => { + const total = selectedOffers.reduce((acc: number, curr: any) => { return acc + (+curr.foreignAmount || 0); // Ensure qortAmount is defined }, 0); - }, [selectedOffers]); + + const totalWithKnownFees = +total + +knownFees; + return totalWithKnownFees; + }, [selectedOffers, knownFees]); const buyOrder = async () => { try { + isRemoveOrdersWithoutUnlockingFees.current = false; if (+foreignCoinBalance < +selectedTotalLTC.toFixed(4)) { setOpen(true); setInfo({ @@ -521,6 +620,36 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { } if (selectedOffers?.length < 1) return; + let offersWithKnownFees = []; + const offersWithUnknownFees = []; + + selectedOffers.forEach((offer) => { + const feeEntry = signedUnlockingFees.find( + (item) => item?.atAddress === offer.qortalAtAddress + ); + + if (feeEntry && typeof feeEntry.fee === "number") { + offersWithKnownFees.push({ ...offer, fee: feeEntry.fee }); + } else { + offersWithUnknownFees.push(offer); + } + }); + console.log("offersWithKnownFees", offersWithKnownFees); + console.log("offersWithUnknownFees", offersWithUnknownFees); + if (offersWithUnknownFees?.length > 0) { + await showTradesUnknownFee({ + message: "", + }); + + if (!isRemoveOrdersWithoutUnlockingFees.current) { + offersWithKnownFees = [ + ...offersWithKnownFees, + ...offersWithUnknownFees, + ]; + } + } + + console.log("offersWithKnownFees", offersWithKnownFees); setIsShowBuyInProgress({ status: "buying" }); @@ -529,7 +658,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { // type: 'info', // message: "Attempting to submit buy order. Please wait..." // }) - const listOfATs = selectedOffers; + const listOfATs = offersWithKnownFees; const response = await qortalRequestWithTimeout( { action: "CREATE_TRADE_BUY_ORDER", @@ -609,6 +738,18 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { if (params.data.qortalAtAddress === selectedOffer?.qortalAtAddress) { return { background: "#6D94F533" }; } + + const hasSignedFee = signedUnlockingFees?.find( + (item) => item?.atAddress === params.data.qortalAtAddress + ); + console.log("hasSignedFee", hasSignedFee); + if (hasSignedFee) { + console.log("fee2fee", fee); + if (fee && hasSignedFee?.fee > fee) { + return { backgroundColor: "#ff6347" }; + } + } + return undefined; }; // const onGridReady = (params) => { @@ -639,9 +780,10 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { }, []); const selectedTotalQORT = useMemo(() => { - return selectedOffers.reduce((acc: number, curr: any) => { + const total = selectedOffers.reduce((acc: number, curr: any) => { return acc + (+curr.qortAmount || 0); // Ensure qortAmount is defined }, 0); + return total; }, [selectedOffers]); const onGridReady = useCallback((params: any) => { @@ -664,6 +806,32 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { setInfo(null); }; + useEffect(() => { + signedUnlockingFeesRef.current = signedUnlockingFees; + feeRef.current = fee; + + if (gridRef.current?.api) { + console.log("Total rows:", gridRef.current.api.getDisplayedRowCount()); + + gridRef.current.api.forEachNode((rowNode: RowNode) => { + const qortalAtAddress = rowNode.data?.qortalAtAddress; + const hasSignedFee = signedUnlockingFeesRef.current?.find( + (item) => item?.atAddress === qortalAtAddress + ); + + const isSelectable = + !hasSignedFee || hasSignedFee.fee <= feeRef.current; + + rowNode.setRowSelectable(isSelectable); // ✅ apply logic per row + }); + + // Optional: refresh selection/checkbox visuals + gridRef.current.api.refreshCells({ force: true }); + } + }, [signedUnlockingFees, fee]); + console.log("signedUnlockingFees", signedUnlockingFees, fee); + if (!signedUnlockingFees || !fee) return null; + return (
= ({ foreignCoinBalance }: any) => { onGridReady={onGridReady} // domLayout='autoHeight' getRowId={(params) => params.data.qortalAtAddress} // Ensure rows have unique IDs + gridOptions={{ + isRowSelectable: (params) => { + let selectable = true; + const hasSignedFee = signedUnlockingFeesRef.current?.find( + (item) => item?.atAddress === params.data.qortalAtAddress + ); + if (!hasSignedFee) selectable = true; + console.log( + "fee", + feeRef.current, + signedUnlockingFeesRef.current + ); + if (hasSignedFee && hasSignedFee?.fee > feeRef.current) + selectable = false; + return selectable; + }, + }} /> {/* {selectedOffer && ( @@ -696,7 +881,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => {
@@ -716,7 +901,7 @@ export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { width: "calc(100% - 75px)", }} > - {selectedTotalQORT?.toFixed(3)} QORT + {selectedTotalQORT?.toFixed(8)} QORT = ({ foreignCoinBalance }: any) => { foreignCoinBalance ? "red" : "white", + backgroundColor: + selectedTotalLTC > foreignCoinBalance ? "red" : "unset", + color: "white", }} > - {selectedTotalLTC?.toFixed(4)}{" "} + {selectedTotalLTC?.toFixed(8)}{" "} {`${getCoinLabel()} `} + >{`${getCoinLabel()} (with known fees)`} = ({ foreignCoinBalance }: any) => { color: "white", }} > - {foreignCoinBalance?.toFixed(4)}{" "} + {foreignCoinBalance?.toFixed(8)}{" "} = ({ foreignCoinBalance }: any) => { )} + {isShowTradesUnknownFee && ( + + Warning + + + Some of your buy orders have unknown unlocking fees. + + + + You may proceed with your purchases without removing these orders, + but there is a higher risk that the trades may not go through. In + such cases, you will be refunded. + + + + + (isRemoveOrdersWithoutUnlockingFees.current = + e.target.checked) + } + edge="start" + tabIndex={-1} + disableRipple + sx={{ + "&.Mui-checked": { + color: "white", + }, + "& .MuiSvgIcon-root": { + color: "white", + }, + }} + /> + } + label={ + + + Remove orders with unknown unlocking fees + + + } + /> + + + + + + + )} {isShowBuyInProgress && ( = ({ foreignCoinBalance }: any) => { )} - + - - - Buy {openShowOfferDetails?.qortAmount} QORT @ {openShowOfferDetails?.foreignAmount} {getCoinLabel()} - - - - + Buy {openShowOfferDetails?.qortAmount} QORT @{" "} + {openShowOfferDetails?.foreignAmount} {getCoinLabel()} + + + setOpenShowOfferDetails(null)} + onClick={() => setOpenShowOfferDetails(null)} sx={{ position: "absolute", right: 8, top: 8, color: "#fff" }} > - - - - - - - - + {fee && + openShowOfferDetails?.fee && + +fee < +openShowOfferDetails?.fee && ( + + + The unlocking fee on this node is lower than the amount + required for this order. + + + + If you're using your own node, you can change the fee by + clicking the "Fee" button next to the coin selector. + + + )} + + + + + {openShowOfferDetails?.fee && ( + + )} + + + + + - - + Close + + + ); }; @@ -1008,13 +1321,13 @@ const TradeRow = ({ value, extra, enableSlice, - enableCopy + enableCopy, }: { label: string; value: string; extra?: string; - enableSlice?: boolean - enableCopy?: boolean + enableSlice?: boolean; + enableCopy?: boolean; }) => ( - {enableSlice && value?.length > 18 ? value?.slice(0, 6) + "..." + value.slice(-4) : value} + {enableSlice && value?.length > 18 + ? value?.slice(0, 6) + "..." + value.slice(-4) + : value} {enableCopy && ( - + copyToClipboard(value)}> @@ -1054,34 +1369,30 @@ const TradeRow = ({ ); - -const SelectWithInfoCell = ({selectTradeForDetails}) => { - +const SelectWithInfoCell = ({ selectTradeForDetails }) => { const handleInfoClick = (e: React.MouseEvent) => { e.stopPropagation(); // Prevents row selection - selectTradeForDetails() + selectTradeForDetails(); // alert(`Info for ${data.qortalAtAddress}`); // Replace with your own UI }; - - return ( -
- - - { - e.stopPropagation(); - handleInfoClick(e) +
+ { + e.stopPropagation(); + handleInfoClick(e); }} - onMouseDown={(e) => e.stopPropagation()} // 👈 this is key - sx={{ minWidth: 0, padding: "0 4px" }} - > - - - + onMouseDown={(e) => e.stopPropagation()} // 👈 this is key + sx={{ minWidth: 0, padding: "0 4px" }} + > + +
); -}; \ No newline at end of file +}; diff --git a/src/components/header/Header-styles.tsx b/src/components/header/Header-styles.tsx index d98d007..ce648c1 100644 --- a/src/components/header/Header-styles.tsx +++ b/src/components/header/Header-styles.tsx @@ -214,10 +214,12 @@ export const CoinReceiveBtn = styled(Button)(({ theme }) => ({ export const CoinSelectRow = styled(Box)({ display: "flex", - flexDirection: "column", + flexDirection: "row", gap: "5px", - alignSelf: "flex-start", - marginBottom: "5px" + justifyContent: "flex-start", + marginBottom: "5px", + width: '100%', + flexWrap: 'wrap' }); export const CoinActionContainer = styled(Box)({ diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx index d22a16b..ab1eab6 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.tsx @@ -1,4 +1,11 @@ -import { useState, useEffect, useRef, useContext, ChangeEvent, useMemo } from "react"; +import { + useState, + useEffect, + useRef, + useContext, + ChangeEvent, + useMemo, +} from "react"; import { BubbleCardColored1, CoinActionContainer, @@ -26,15 +33,15 @@ import { cropAddress } from "../../utils/cropAddress"; import qtradeLogo from "../../components/common/icons/qtradeLogo.png"; import qortIcon from "../../assets/img/qort.png"; import ErrorIcon from "@mui/icons-material/Error"; -import { CopyToClipboard } from "react-copy-to-clipboard"; import Copy from "../../assets/SVG/Copy.svg"; -import {AddressQRCode} from './AddressQRCode' -import {FallingLines} from 'react-loader-spinner' +import { AddressQRCode } from "./AddressQRCode"; +import { FallingLines } from "react-loader-spinner"; import { Alert, AppBar, Avatar, Box, + ButtonBase, Card, CardContent, FormControl, @@ -58,6 +65,8 @@ import arrrIcon from "../../assets/img/arrr.png"; import { Spacer } from "../common/Spacer"; import { ReusableModal } from "../common/reusable-modal/ReusableModal"; import { NotificationContext } from "../../contexts/notificationContext"; +import UnsignedFees from "../sell/UnsignedFees"; +import { FeeManager } from "../sell/FeeManager"; const checkIfLocal = async () => { try { @@ -140,7 +149,13 @@ const SelectRow = ({ coin }) => { ); }; -export const Header = ({ qortBalance, foreignCoinBalance }: any) => { +export const Header = ({ + qortBalance, + foreignCoinBalance, + qortAddress, + fee, + setFee +}: any) => { const [openDropdown, setOpenDropdown] = useState(false); const dropdownRef = useRef(null); const buttonRef = useRef(null); @@ -167,7 +182,6 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => { useContext(gameContext); const { setNotification } = useContext(NotificationContext); - const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ padding: 8, "& .MuiSwitch-track": { @@ -239,31 +253,32 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => { // } // }, [userInfo]); - const sendCoin = async ()=> { + const sendCoin = async () => { try { - const coin = openCoinActionModal.coin === "QORT" ? 'QORT' : getCoinLabel() - if(!coin) return + const coin = + openCoinActionModal.coin === "QORT" ? "QORT" : getCoinLabel(); + if (!coin) return; setOpen(true); setInfo({ type: "info", message: "Sending Coin...", - autoHideDurationOff: true + autoHideDurationOff: true, }); const response = await qortalRequest({ action: "SEND_COIN", coin, destinationAddress: senderAddress, - amount: +amount - }); - if(response?.error){ - throw new Error(response?.error || "Failed to send coin.") - } - setOpen(true); + amount: +amount, + }); + if (response?.error) { + throw new Error(response?.error || "Failed to send coin."); + } + setOpen(true); setInfo({ type: "success", message: "Coin sent", }); - setAmount('') + setAmount(""); } catch (error) { setOpen(true); setInfo({ @@ -271,7 +286,7 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => { message: error?.error || error?.message, }); } - } + }; return ( <> @@ -428,12 +443,10 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => { }} /> {foreignCoinBalance === null ? ( - - ) : foreignCoinBalance}{" "} + + ) : ( + foreignCoinBalance + )}{" "} {getCoinLabel()} @@ -467,7 +480,10 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => { + {!isUsingGateway && selectedCoin !== 'PIRATECHAIN' && ( + <> + + + + )} { > {info?.type && ( - {info?.message} - + onClose={handleClose} + severity={info?.type} + variant="filled" + sx={{ width: "100%" }} + > + {info?.message} + )} - {openCoinActionModal && ( { setOpenCoinActionModal(null); - setAmount('') - setSenderAddress('') + setAmount(""); + setSenderAddress(""); }} backdrop > - {openCoinActionModal.type === "send" ? <> - - - {openCoinActionModal.type === "send" && - openCoinActionModal.coin === "QORT" ? ( - <> - Send {openCoinActionModal.coin} - - - ) : openCoinActionModal.type === "send" && - openCoinActionModal.coin !== "QORT" ? ( - <> - Send {openCoinActionModal.coin} - - - ) : null} - - - - - { - if (openCoinActionModal.type === "send") { - setSenderAddress(e.target.value); - } else { - setReceiverAddress(e.target.value); - } - }} - /> - - - {openCoinActionModal.type === "send" && ( - - - { - setAmount(e.target.value) - }} - /> - - - )} - : ( + {openCoinActionModal.type === "send" ? ( <> - + + + {openCoinActionModal.type === "send" && + openCoinActionModal.coin === "QORT" ? ( + <> + Send {openCoinActionModal.coin} + + + ) : openCoinActionModal.type === "send" && + openCoinActionModal.coin !== "QORT" ? ( + <> + Send {openCoinActionModal.coin} + + + ) : null} + + + + + { + if (openCoinActionModal.type === "send") { + setSenderAddress(e.target.value); + } else { + setReceiverAddress(e.target.value); + } + }} + /> + + + {openCoinActionModal.type === "send" && ( + + + { + setAmount(e.target.value); + }} + /> + + + )} + + ) : ( + <> + )} - {openCoinActionModal.type === 'send' && ( - - {/* setOpenCoinActionModal(null)}> + {openCoinActionModal.type === "send" && ( + + {/* setOpenCoinActionModal(null)}> Cancel */} - { - if(openCoinActionModal.type === 'send'){ - sendCoin() - } - setNotification({ - alertType: "alertInfo", - msg: "Sending...", - }); - }} - > - {openCoinActionModal.type === "send" ? "Send" : "Receive"} - - + { + if (openCoinActionModal.type === "send") { + sendCoin(); + } + setNotification({ + alertType: "alertInfo", + msg: "Sending...", + }); + }} + > + {openCoinActionModal.type === "send" ? "Send" : "Receive"} + + )} - - - )} - ); }; export const AddressBox = styled(Box)` -display: flex; -border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5)); -justify-content: space-between; -align-items: center; -width: auto; -word-break: break-word; -padding: 5px 15px 5px 15px; -gap: 5px; -border-radius: 100px; -font-family: Inter; -font-size: 12px; -font-weight: 600; -line-height: 14.52px; -text-align: left; -color: var(--50-white, rgba(255, 255, 255, 0.5)); -cursor: pointer; -transition: all 0.2s; -&:hover { + display: flex; + border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5)); + justify-content: space-between; + align-items: center; + width: auto; + word-break: break-word; + padding: 5px 15px 5px 15px; + gap: 5px; + border-radius: 100px; + font-family: Inter; + font-size: 12px; + font-weight: 600; + line-height: 14.52px; + text-align: left; + color: var(--50-white, rgba(255, 255, 255, 0.5)); + cursor: pointer; + transition: all 0.2s; + &:hover { background-color: rgba(41, 41, 43, 1); color: white; svg path { fill: white; // Fill color changes to white on hover } } +`; -` +const ReceiveCoin = ({ + coinAddresses, + setCoinAddresses, + selectedCoin, + setOpen, + setInfo, +}) => { + const [errorMsg, setErrorMsg] = useState(""); + const foreignAddress = useMemo(() => { + return coinAddresses[selectedCoin] || null; + }, [coinAddresses, selectedCoin]); - -const ReceiveCoin = ({coinAddresses, setCoinAddresses, selectedCoin, setOpen, setInfo})=> { - const [errorMsg, setErrorMsg] = useState('') - const foreignAddress = useMemo(()=> { - return coinAddresses[selectedCoin] || null - }, [coinAddresses, selectedCoin]) - - const getForeignAddress = async (coin)=> { + const getForeignAddress = async (coin) => { try { setOpen(true); - setInfo({ - type: "info", - message: "Retrieving address...", - }); + setInfo({ + type: "info", + message: "Retrieving address...", + }); const response = await qortalRequest({ action: "GET_USER_WALLET", - coin - }); - if(response?.address){ - setCoinAddresses((prev)=> { - return { - ...prev, - [coin]: response.address - } - }) - } - if(response?.error){ - throw new Error(response?.error || "Failed to send coin.") - } + coin, + }); + if (response?.address) { + setCoinAddresses((prev) => { + return { + ...prev, + [coin]: response.address, + }; + }); + } + if (response?.error) { + throw new Error(response?.error || "Failed to send coin."); + } } catch (error) { - setErrorMsg(error?.message) + setErrorMsg(error?.message); } finally { setOpen(false); setInfo(null); } - } + }; - useEffect(()=> { - if(!selectedCoin) return - if(!coinAddresses[selectedCoin]){ - getForeignAddress(selectedCoin) + useEffect(() => { + if (!selectedCoin) return; + if (!coinAddresses[selectedCoin]) { + getForeignAddress(selectedCoin); } - }, [selectedCoin, coinAddresses]) + }, [selectedCoin, coinAddresses]); return ( - - {`Send ${selectedCoin} to your address below`} - + + {`Send ${selectedCoin} to your address below`} + {foreignAddress && ( - { - setOpen(true); - setInfo({ - type: "info", - message: "Address copied!", - }); - }}> - - {foreignAddress} - - + { + navigator.clipboard + .writeText(foreignAddress) + .then(() => { + setOpen(true); + setInfo({ + type: "info", + message: "Address copied!", + }); + }) + .catch((err) => { + console.error("Failed to copy LTC address:", err); + }); + }} + > + + {foreignAddress} + + )} {foreignAddress && ( <> - + )} {errorMsg && ( <> - - {errorMsg} + + + {errorMsg} + )} - ) -} \ No newline at end of file + ); +}; diff --git a/src/components/sell/CreateSell.tsx b/src/components/sell/CreateSell.tsx index 3c6e471..635703d 100644 --- a/src/components/sell/CreateSell.tsx +++ b/src/components/sell/CreateSell.tsx @@ -23,8 +23,8 @@ import TradeBotList from "./TradeBotList"; export const CustomLabel = styled(InputLabel)` font-weight: 400; font-family: Inter; - font-size: 10px; - line-height: 12px; + font-size: 14px; + line-height: 1.2; color: rgba(255, 255, 255, 0.5); `; @@ -61,12 +61,12 @@ export const CustomInput = styled(TextField)({ // backgroundColor: "rgba(30, 30, 32, 1)", outline: "none", input: { - fontSize: 10, + fontSize: '14px', fontFamily: "Inter", fontWeight: 400, color: "white", "&::placeholder": { - fontSize: 16, + fontSize: '14px', color: "rgba(255, 255, 255, 0.2)", }, outline: "none", @@ -263,7 +263,7 @@ export const CreateSell = ({ qortAddress, show }) => { onChange={(e) => setQortAmount(+e.target.value)} autoComplete="off" /> - + {`Price of Each QORT (in ${getCoinLabel()})`} @@ -275,7 +275,7 @@ export const CreateSell = ({ qortAddress, show }) => { onChange={(e) => setForeignAmount(+e.target.value)} autoComplete="off" /> - + {`${qortAmount * foreignAmount} ${getCoinLabel()}`} for{" "} {qortAmount} QORT diff --git a/src/components/sell/FeeManager.tsx b/src/components/sell/FeeManager.tsx new file mode 100644 index 0000000..484beea --- /dev/null +++ b/src/components/sell/FeeManager.tsx @@ -0,0 +1,374 @@ +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import gameContext from "../../contexts/gameContext"; +import { + Alert, + Box, + ButtonBase, + Snackbar, + SnackbarCloseReason, + ToggleButton, + ToggleButtonGroup, + Typography, +} from "@mui/material"; +import ChangeCircleIcon from "@mui/icons-material/ChangeCircle"; +import { ReusableModal } from "../common/reusable-modal/ReusableModal"; +import QuestionMarkIcon from "@mui/icons-material/QuestionMark"; +import { + CoinActionContainer, + CoinActionRow, + HeaderRow, +} from "../header/Header-styles"; +import { CustomInput, CustomLabel } from "./CreateSell"; +import { Spacer } from "../common/Spacer"; +import { usePublish, Service, QortalGetMetadata } from "qapp-core"; +import { SetLeftFeature } from "ag-grid-community"; + +function calculateFeeFromRate(feePerKb, sizeInBytes) { + const fee = (feePerKb / 1000) * sizeInBytes; + return fee?.toFixed(0); +} + +function calculateRateFromFee(totalFee, sizeInBytes) { + return (totalFee / sizeInBytes) * 1000; +} + +export const FeeManager = ({ selectedCoin, setFee, fee }) => { + const [feeLocation, setFeeLocation] = useState({ + name: "", + identifier: "", + service: "JSON", + }); + const { resource } = usePublish(3, "JSON", feeLocation); + const [editFee, setEditFee] = useState(""); + const [openModal, setOpenModal] = useState(false); + const [recommendedFee, setRecommendedFee] = useState("m"); + const [openAlert, setOpenAlert] = useState(false); + const [info, setInfo] = useState(null); + const { getCoinLabel } = useContext(gameContext); + console.log("editFee", editFee); + const handleCloseAlert = ( + event?: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason + ) => { + if (reason === "clickaway") { + return; + } + + setOpenAlert(false); + setInfo(null); + }; + const coin = useMemo(() => { + return getCoinLabel(selectedCoin)?.toLowerCase(); + }, [selectedCoin, getCoinLabel]); + + const establishUpdateFeeForm = useCallback(async (coin) => { + setFee(""); + // if the coin or type is not set, then abort + if (!coin) { + return; + } + console.log("selectedCoin", coin); + // const coinRequest = coin.current.toLowerCase(); + const typeRequest = "feerequired"; + + try { + const response = await qortalRequestWithTimeout( + { + action: "GET_FOREIGN_FEE", + coin: coin, + type: typeRequest, + }, + 1800000 + ); + if (response && !isNaN(+response)) { + setFee(response); + } + + console.log("response", response); + } catch (error) { + setFee(""); + console.error(error); + } + }, []); + + useEffect(() => { + establishUpdateFeeForm(coin); + console.log("editFee or fetch changed"); + }, [coin, establishUpdateFeeForm]); + + const recommendedFeeData = useMemo(() => { + if (!resource?.data) return null; + if ( + !resource?.data?.["BTC"] || + !resource?.data?.["LTC"] || + !resource?.data?.["DOGE"] + ) + return null; + return resource.data; + }, [resource?.data]); + + const recommendedFeeDisplay = useMemo(() => { + if (!selectedCoin || !recommendedFeeData) return; + const coin = getCoinLabel(selectedCoin)?.toUpperCase(); + return recommendedFeeData[coin][recommendedFee]; + }, [recommendedFeeData, recommendedFee, selectedCoin]); + + const updateFee = async () => { + const typeRequest = "feerequired"; + + try { + let feeToSave = editFee + if(recommendedFee !== 'custom'){ + feeToSave = calculateFeeFromRate(recommendedFeeDisplay, 300) + } + console.log('feeToSave', feeToSave) + const response = await qortalRequestWithTimeout( + { + action: "UPDATE_FOREIGN_FEE", + coin: coin, + type: typeRequest, + value: feeToSave, + }, + 1800000 + ); + + if (response && !isNaN(+response)) { + setFee(response); + setOpenAlert(true); + setInfo({ + type: "success", + message: "Fee updated!", + }); + setOpenModal(false); + } else throw new Error("Unable to update fee"); + } catch (error) { + setOpenAlert(true); + setInfo({ + type: "error", + message: error?.message || "Unable to update fee", + }); + } + }; + + const handleChange = ( + event: React.MouseEvent, + newAlignment: string + ) => { + if(newAlignment){ + setRecommendedFee(newAlignment); + } + }; + + + + const getLatestFees = useCallback(async () => { + try { + const res = await fetch( + `http://devnet-nodes.qortal.link:11112/arbitrary/resources/searchsimple?service=JSON&identifier=foreign-fee&name=Foreign-Fee-Publisher&prefix=true&limit=1&reverse=true` + ); + const data = await res.json(); + if (data && data?.length > 0) { + setFeeLocation(data[0]); + } + } catch (error) {} + }, []); + + useEffect(() => { + getLatestFees(); + }, [getLatestFees]); + + if (!fee) return; + return ( + <> + { + setOpenModal(true); + setEditFee(fee); + }} + sx={{ + minHeight: "42px", + border: "1px solid gray", + color: "white", + display: "flex", + alignItems: "center", + padding: "5px 5px", + gap: "10px", + borderRadius: "5px", + "&:hover": { + border: "1px solid white", // Border color on hover + }, + }} + > + Fee: {fee} + + + + {openModal && ( + { + setOpenModal(false); + setEditFee(fee); + }} + backdrop + > + + + + + Update unlocking fee for {selectedCoin} + + + + + + + + + Recommended fee selection + + + + + Low + Medium + High + Custom + + + {recommendedFeeDisplay && ( + <> + + + New fee:{" "} + {calculateFeeFromRate(recommendedFeeDisplay, 300)} + + + + + + + This recommended fee is derived from{" "} + {recommendedFeeDisplay} per kb, for a transaction that + is approximately 300 kB in size. + + + + )} + + + + {recommendedFee === "custom" && ( + + + + + Custom fee + + + setEditFee(e.target.value)} + autoComplete="off" + /> + + + + )} + + + + Update fee + + + + )} + + + {info?.message} + + + + ); +}; diff --git a/src/components/sell/UnsignedFees.tsx b/src/components/sell/UnsignedFees.tsx new file mode 100644 index 0000000..cf5de27 --- /dev/null +++ b/src/components/sell/UnsignedFees.tsx @@ -0,0 +1,245 @@ +import React, { + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { baseLocalHost } from "../Grids/TradeOffers"; +import { + Alert, + Box, + ButtonBase, + Snackbar, + SnackbarCloseReason, + Typography, +} from "@mui/material"; +import TouchAppIcon from "@mui/icons-material/TouchApp"; +import { ReusableModal } from "../common/reusable-modal/ReusableModal"; +import { + CoinActionContainer, + CoinActionRow, + CoinConfirmSendBtn, + HeaderRow, +} from "../header/Header-styles"; + +export default function UnsignedFees({ qortAddress }) { + const [isPositive, setIsPositive] = useState(null); + const [openModal, setOpenModal] = useState(false); + const [openAlert, setOpenAlert] = useState(false); + const [info, setInfo] = useState(null); + console.log("isPositive", isPositive); + const qortAddressRef = useRef(null); + + const handleCloseAlert = ( + event?: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason + ) => { + if (reason === "clickaway") { + return; + } + + setOpenAlert(false); + setInfo(null); + }; + + useEffect(() => { + if (qortAddress) { + qortAddressRef.current = qortAddress; + } + }, [qortAddress]); + + const restartUnsignedFeeSocket = () => { + setTimeout(() => initUnsignedFeeSocket(true), 50); + }; + + const restartFeeData = () => { + if (socketRef.current) { + socketRef.current.close(1000, "forced"); // Close with a custom reason + socketRef.current = null; + } + + setIsPositive(null); + }; + + const socketRef = useRef(null); + + const initUnsignedFeeSocket = (restarted = false) => { + let socketTimeout: any; + const socketLink = `${ + window.location.protocol === "https:" ? "wss:" : "ws:" + }//${baseLocalHost}/websockets/crosschain/unsignedfees`; + socketRef.current = new WebSocket(socketLink); + socketRef.current.onopen = () => { + setTimeout(pingSocket, 50); + }; + socketRef.current.onmessage = (e) => { + restarted = false; + const data = JSON.parse(e.data); + if (qortAddressRef.current === data.address) { + if (data.positive) { + setIsPositive(true); + setOpenModal(true); + } else { + setIsPositive(false); + } + } + }; + socketRef.current.onclose = (event) => { + clearTimeout(socketTimeout); + if (event.reason === "forced") { + return; + } + restartUnsignedFeeSocket(); + }; + socketRef.current.onerror = (e) => { + clearTimeout(socketTimeout); + }; + const pingSocket = () => { + socketRef.current.send("ping"); + socketTimeout = setTimeout(pingSocket, 295000); + }; + }; + + const getUnsignedFees = useCallback(async (address)=> { + try { + const url = `http://devnet-nodes.qortal.link:11112/crosschain/unsignedfees/${address}` + const res = await fetch(url) + const data = await res.json() + if(data && data.length > 0){ + setIsPositive(true) + } else { + setIsPositive(false) + } + } catch (error) { + console.error(error) + } + }, []) + + useEffect(() => { + if (!qortAddress) return; + restartFeeData(); + getUnsignedFees(qortAddress) + setTimeout(() => { + initUnsignedFeeSocket(); + }, 500); + return () => { + if (socketRef.current) { + socketRef.current.close(1000, "forced"); + } + }; + }, [qortAddress, getUnsignedFees]); + + const signForeignFees = async () => { + try { + await qortalRequest({ + action: "SIGN_FOREIGN_FEES", + }); + } catch (error) { + console.error(error); + } + }; + + if (!isPositive) return null; + return ( + <> + + + Action required! You have unsigned fees + + {openModal && ( + { + setOpenModal(false); + }} + backdrop + > + + + + + Action required! + + + + + + + In order for buyers to use the correct unlocking fee, please + sign your fees. + + + + + + Sign Fees + + + + )} + + + {info?.message} + + + + ); +} diff --git a/src/main.tsx b/src/main.tsx index 59e47e5..341580a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,5 @@ import { BrowserRouter } from 'react-router-dom' -import ReactDOM from 'react-dom/client' +import { createRoot } from 'react-dom/client' import App from './App.tsx' import "./index.scss"; import { IndexedDBProvider } from './contexts/indexedDBContext.tsx'; @@ -11,7 +11,7 @@ interface CustomWindow extends Window { const customWindow = window as unknown as CustomWindow; const baseUrl = customWindow?._qdnBase || ""; -ReactDOM.createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById('root')!).render( diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 6ee3fc1..828536f 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -24,6 +24,8 @@ export const HomePage = () => { } = useContext(gameContext); const { setNotification } = useContext(NotificationContext); const [mode, setMode] = useState("buy"); + const [fee, setFee] = useState(""); + const filteredOngoingTrades = useMemo(() => { return onGoingTrades?.filter( (item) => item?.tradeInfo?.foreignBlockchain === selectedCoin @@ -50,6 +52,9 @@ export const HomePage = () => {
@@ -108,7 +113,8 @@ export const HomePage = () => { - +
diff --git a/src/services/socketService/index.ts b/src/services/socketService/index.ts deleted file mode 100644 index bf17c87..0000000 --- a/src/services/socketService/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { io, Socket } from "socket.io-client"; -import { DefaultEventsMap } from '@socket.io/component-emitter'; - -class SocketService { - public socket: Socket | null = null; - - public connect( - url: string, - token: string - ): Promise> { - return new Promise((resolve, reject) => { - this.socket = io(url, { - auth: { - token: token - } - }); - - if (!this.socket) return reject("Socket initialization failed"); - - this.socket.on("connect", () => { - console.log('connected'); - resolve(this.socket as Socket); - }); - - this.socket.on("connect_error", (err) => { - console.log("Connection error: ", err); - reject(err); - }); - }); - } - - public disconnect() { - if (this.socket) { - this.socket.disconnect(); - this.socket = null; // Optionally reset the socket to null after disconnecting - console.log('Disconnected'); - } - } - } - - export default new SocketService(); - \ No newline at end of file