manage fees

This commit is contained in:
PhilReact 2025-05-02 18:35:57 +03:00
parent 4bb3ca2685
commit c609fbb054
15 changed files with 1936 additions and 622 deletions

576
package-lock.json generated
View File

@ -18,10 +18,10 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1", "moment": "^2.30.1",
"react": "^18.2.0", "qapp-core": "^1.0.22",
"react-copy-to-clipboard": "^5.1.0", "react": "^19.1.0",
"react-countdown-circle-timer": "^3.2.1", "react-countdown-circle-timer": "^3.2.1",
"react-dom": "^18.2.0", "react-dom": "^19.1.0",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
@ -33,8 +33,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.5", "@types/lodash": "^4.17.5",
"@types/react": "^18.2.66", "@types/react": "^19.1.0",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^19.1.0",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
@ -1791,15 +1791,15 @@
} }
}, },
"node_modules/@emotion/babel-plugin": { "node_modules/@emotion/babel-plugin": {
"version": "11.11.0", "version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
"integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
"dependencies": { "dependencies": {
"@babel/helper-module-imports": "^7.16.7", "@babel/helper-module-imports": "^7.16.7",
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.1", "@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.8.1", "@emotion/memoize": "^0.9.0",
"@emotion/serialize": "^1.1.2", "@emotion/serialize": "^1.3.3",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0", "convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
@ -1808,6 +1808,11 @@
"stylis": "4.2.0" "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": { "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "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", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" "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": { "node_modules/@emotion/hash": {
"version": "0.9.2", "version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
@ -1865,17 +1865,17 @@
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
}, },
"node_modules/@emotion/react": { "node_modules/@emotion/react": {
"version": "11.11.4", "version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.11.0", "@emotion/babel-plugin": "^11.13.5",
"@emotion/cache": "^11.11.0", "@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.1.3", "@emotion/serialize": "^1.3.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
"@emotion/utils": "^1.2.1", "@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.3.1", "@emotion/weak-memoize": "^0.4.0",
"hoist-non-react-statics": "^3.3.1" "hoist-non-react-statics": "^3.3.1"
}, },
"peerDependencies": { "peerDependencies": {
@ -1915,16 +1915,16 @@
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
}, },
"node_modules/@emotion/styled": { "node_modules/@emotion/styled": {
"version": "11.11.5", "version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
"integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.11.0", "@emotion/babel-plugin": "^11.13.5",
"@emotion/is-prop-valid": "^1.2.2", "@emotion/is-prop-valid": "^1.3.0",
"@emotion/serialize": "^1.1.4", "@emotion/serialize": "^1.3.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
"@emotion/utils": "^1.2.1" "@emotion/utils": "^1.4.2"
}, },
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.0.0-rc.0", "@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": { "node_modules/@emotion/unitless": {
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", "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": { "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
"version": "1.0.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
"peerDependencies": { "peerDependencies": {
"react": ">=16.8.0" "react": ">=16.8.0"
} }
@ -1955,9 +1969,9 @@
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="
}, },
"node_modules/@emotion/weak-memoize": { "node_modules/@emotion/weak-memoize": {
"version": "0.3.1", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2", "version": "0.20.2",
@ -3109,6 +3123,31 @@
"string.prototype.matchall": "^4.0.6" "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": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -3179,21 +3218,22 @@
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.2.79", "version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
"integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "18.2.25", "version": "19.1.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz",
"integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==", "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==",
"dev": true, "dev": true,
"dependencies": { "license": "MIT",
"@types/react": "*" "peerDependencies": {
"@types/react": "^19.0.0"
} }
}, },
"node_modules/@types/react-transition-group": { "node_modules/@types/react-transition-group": {
@ -3210,6 +3250,11 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true "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": { "node_modules/@types/semver": {
"version": "7.5.8", "version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@ -3219,13 +3264,14 @@
"node_modules/@types/stylis": { "node_modules/@types/stylis": {
"version": "4.2.5", "version": "4.2.5",
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", "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": { "node_modules/@types/trusted-types": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"dev": true "devOptional": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.7.0", "version": "7.7.0",
@ -3610,6 +3656,14 @@
"node": ">= 4.0.0" "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": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -3703,6 +3757,33 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "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": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -3714,6 +3795,29 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "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": "^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": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -3815,6 +3942,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
"license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -3933,6 +4061,15 @@
"node": ">=4.0.0" "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -3945,14 +4082,6 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true "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": { "node_modules/core-js-compat": {
"version": "3.38.1", "version": "3.38.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
@ -3995,6 +4124,11 @@
"node": ">= 8" "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": { "node_modules/crypto-random-string": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@ -4008,6 +4142,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
"license": "ISC",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@ -4016,6 +4151,7 @@
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"camelize": "^1.0.0", "camelize": "^1.0.0",
"css-color-keywords": "^1.0.0", "css-color-keywords": "^1.0.0",
@ -4027,6 +4163,11 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" "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": { "node_modules/data-view-buffer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", "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" "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": { "node_modules/debug": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@ -4179,11 +4325,20 @@
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.8.7", "@babel/runtime": "^7.8.7",
"csstype": "^3.0.2" "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": { "node_modules/ejs": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "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", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" "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": { "node_modules/filelist": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@ -5110,6 +5281,14 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/gopd": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@ -5232,6 +5411,25 @@
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
"dev": true "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": { "node_modules/ignore": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@ -5344,6 +5542,17 @@
"node": ">=8" "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": { "node_modules/is-boolean-object": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "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" "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": { "node_modules/is-builtin-module": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
@ -5901,6 +6132,11 @@
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
"dev": true "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": { "node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -6208,9 +6444,10 @@
} }
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -6233,9 +6470,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.38", "version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -6250,10 +6487,11 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.1.1",
"source-map-js": "^1.2.0" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
@ -6262,7 +6500,8 @@
"node_modules/postcss-value-parser": { "node_modules/postcss-value-parser": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "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": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
@ -6314,6 +6553,33 @@
"node": ">=6" "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": { "node_modules/qr.js": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
@ -6349,28 +6615,14 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "18.2.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"dependencies": { "license": "MIT",
"loose-envify": "^1.1.0"
},
"engines": { "engines": {
"node": ">=0.10.0" "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": { "node_modules/react-countdown-circle-timer": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/react-countdown-circle-timer/-/react-countdown-circle-timer-3.2.1.tgz", "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": { "node_modules/react-dom": {
"version": "18.2.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "scheduler": "^0.26.0"
"scheduler": "^0.23.0"
}, },
"peerDependencies": { "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": { "node_modules/react-ga4": {
@ -6396,6 +6664,36 @@
"resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
"integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" "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": { "node_modules/react-is": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@ -6484,6 +6782,7 @@
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1", "dom-helpers": "^5.0.1",
@ -6506,6 +6805,11 @@
"node": ">=8.10.0" "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": { "node_modules/regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -6782,12 +7086,15 @@
} }
}, },
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.23.0", "version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"dependencies": { "license": "MIT"
"loose-envify": "^1.1.0" },
} "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": { "node_modules/semver": {
"version": "7.6.0", "version": "7.6.0",
@ -6866,7 +7173,8 @@
"node_modules/shallowequal": { "node_modules/shallowequal": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", "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": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
@ -6966,9 +7274,10 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -7122,16 +7431,17 @@
} }
}, },
"node_modules/styled-components": { "node_modules/styled-components": {
"version": "6.1.13", "version": "6.1.17",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.17.tgz",
"integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", "integrity": "sha512-97D7DwWanI7nN24v0D4SvbfjLE9656umNSJZkBkDIWL37aZqG/wRQ+Y9pWtXyBIM/NSfcBzHLErEsqHmJNSVUg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@emotion/is-prop-valid": "1.2.2", "@emotion/is-prop-valid": "1.2.2",
"@emotion/unitless": "0.8.1", "@emotion/unitless": "0.8.1",
"@types/stylis": "4.2.5", "@types/stylis": "4.2.5",
"css-to-react-native": "3.2.0", "css-to-react-native": "3.2.0",
"csstype": "3.1.3", "csstype": "3.1.3",
"postcss": "8.4.38", "postcss": "8.4.49",
"shallowequal": "1.1.0", "shallowequal": "1.1.0",
"stylis": "4.3.2", "stylis": "4.3.2",
"tslib": "2.6.2" "tslib": "2.6.2"
@ -7151,7 +7461,8 @@
"node_modules/styled-components/node_modules/stylis": { "node_modules/styled-components/node_modules/stylis": {
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", "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": { "node_modules/stylis": {
"version": "4.2.0", "version": "4.2.0",
@ -7301,11 +7612,6 @@
"node": ">=8.0" "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": { "node_modules/tr46": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
@ -7330,7 +7636,8 @@
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "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": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
@ -7567,6 +7874,14 @@
"punycode": "^2.1.0" "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": { "node_modules/vite": {
"version": "5.2.9", "version": "5.2.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz",
@ -8087,6 +8402,14 @@
"node": ">=0.4.0" "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": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@ -8112,6 +8435,33 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "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
}
}
} }
} }
} }

View File

@ -20,10 +20,10 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1", "moment": "^2.30.1",
"react": "^18.2.0", "qapp-core": "^1.0.22",
"react-copy-to-clipboard": "^5.1.0", "react": "^19.1.0",
"react-countdown-circle-timer": "^3.2.1", "react-countdown-circle-timer": "^3.2.1",
"react-dom": "^18.2.0", "react-dom": "^19.1.0",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
@ -35,8 +35,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.5", "@types/lodash": "^4.17.5",
"@types/react": "^18.2.66", "@types/react": "^19.1.0",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^19.1.0",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
@ -46,5 +46,11 @@
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.0", "vite": "^5.2.0",
"vite-plugin-pwa": "^0.20.5" "vite-plugin-pwa": "^0.20.5"
},
"overrides": {
"react-loader-spinner": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
} }
} }

View File

@ -90,7 +90,7 @@
.ag-theme-alpine-dark .ag-row-odd, .ag-theme-alpine-dark .ag-row-odd,
.ag-theme-alpine-dark .ag-row-even { .ag-theme-alpine-dark .ag-row-even {
width: 100%; width: 100%;
background-color: #292929 !important; /* Replace with your desired color */ background-color: #292929; /* Replace with your desired color */
} }
.ag-theme-alpine-dark { .ag-theme-alpine-dark {
@ -143,4 +143,5 @@
border-radius: 8px; border-radius: 8px;
background-clip: content-box; background-clip: content-box;
border: 4px solid transparent; border: 4px solid transparent;
} }

View File

@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import "./App.css"; import "./App.css";
import socketService from "./services/socketService";
import GameContext, { import GameContext, {
IContextProps, IContextProps,
UserNameAvatar, UserNameAvatar,
@ -22,9 +21,7 @@ import axios from "axios";
import { executeEvent } from "./utils/events"; import { executeEvent } from "./utils/events";
import { useIndexedDBContext } from "./contexts/indexedDBContext"; import { useIndexedDBContext } from "./contexts/indexedDBContext";
import { useGetOngoingTransactions } from "./components/DbComponents/OngoingTransactions"; import { useGetOngoingTransactions } from "./components/DbComponents/OngoingTransactions";
import { GlobalProvider } from "qapp-core";
export async function sendRequestToExtension( export async function sendRequestToExtension(
requestType: string, requestType: string,
@ -66,26 +63,32 @@ export async function sendRequestToExtension(
}); });
} }
function App() { function App() {
const [userInfo, setUserInfo] = useState<any>(null); const [userInfo, setUserInfo] = useState<any>(null);
const [qortBalance, setQortBalance] = useState<any>(null); const [qortBalance, setQortBalance] = useState<any>(null);
const [balances, setBalances] = useState<any>({}); const [balances, setBalances] = useState<any>({});
const [selectedCoin, setSelectedCoin] = useState("LITECOIN"); const [selectedCoin, setSelectedCoin] = useState("LITECOIN");
const foreignCoinBalance = useMemo(()=> { const foreignCoinBalance = useMemo(() => {
if(balances[selectedCoin] === 0) return 0 if (balances[selectedCoin] === 0) return 0;
return balances[selectedCoin] || null return balances[selectedCoin] || null;
}, [balances, selectedCoin]) }, [balances, selectedCoin]);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false); const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const [OAuthLoading, setOAuthLoading] = useState<boolean>(false); const [OAuthLoading, setOAuthLoading] = useState<boolean>(false);
const db = useIndexedDBContext(); const db = useIndexedDBContext();
const [isUsingGateway, setIsUsingGateway] = useState(null) const [isUsingGateway, setIsUsingGateway] = useState(null);
const [isSocketUp, setIsSocketUp] = useState<boolean>(false); const [isSocketUp, setIsSocketUp] = useState<boolean>(false);
// const [onGoingTrades, setOngoingTrades] = useState([]) // 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< const [userNameAvatar, setUserNameAvatar] = useState<
Record<string, UserNameAvatar> Record<string, UserNameAvatar>
>({}); >({});
@ -101,27 +104,23 @@ function App() {
setLoadingSlider, setLoadingSlider,
}; };
const getIsUsingGateway = async ()=> { const getIsUsingGateway = async () => {
try { try {
const res = await qortalRequest({ const res = await qortalRequest({
action: "IS_USING_PUBLIC_NODE" action: "IS_USING_PUBLIC_NODE",
}) });
setIsUsingGateway(res) setIsUsingGateway(res);
} catch (error) { } catch (error) {}
};
}
}
useEffect(() => {
useEffect(()=> { getIsUsingGateway();
getIsUsingGateway() }, []);
}, [])
const resetNotification = () => { const resetNotification = () => {
setNotification({ alertType: "", msg: "" }); setNotification({ alertType: "", msg: "" });
}; };
const userContextValue: UserContextProps = { const userContextValue: UserContextProps = {
avatar, avatar,
setAvatar, setAvatar,
@ -164,53 +163,44 @@ function App() {
askForAccountInformation(); askForAccountInformation();
}, [askForAccountInformation]); }, [askForAccountInformation]);
const getQortBalance = async () => {
const getQortBalance = async ()=> {
const balanceUrl: string = `/addresses/balance/${userInfo?.address}`; const balanceUrl: string = `/addresses/balance/${userInfo?.address}`;
const balanceResponse = await axios(balanceUrl); const balanceResponse = await axios(balanceUrl);
setQortBalance(balanceResponse.data?.value) setQortBalance(balanceResponse.data?.value);
} };
const getLTCBalance = async (coin) => { const getLTCBalance = async (coin) => {
try { try {
const response = await qortalRequest({ const response = await qortalRequest({
action: "GET_WALLET_BALANCE", action: "GET_WALLET_BALANCE",
coin: getCoinLabel(coin) coin: getCoinLabel(coin),
}); });
if(!response?.error){ if (!response?.error) {
setBalances((prev)=> { setBalances((prev) => {
return { return {
...prev, ...prev,
[coin]: +response [coin]: +response,
} };
}) });
} }
} catch (error) { } catch (error) {
// //
} }
} };
useEffect(() => { useEffect(() => {
if(!userInfo?.address || !selectedCoin) return if (!userInfo?.address || !selectedCoin) return;
const intervalGetTradeInfo = setInterval(() => { const intervalGetTradeInfo = setInterval(() => {
fetchOngoingTransactions() fetchOngoingTransactions();
getLTCBalance(selectedCoin) getLTCBalance(selectedCoin);
getQortBalance() getQortBalance();
}, 150000) }, 150000);
getLTCBalance(selectedCoin) getLTCBalance(selectedCoin);
getQortBalance() getQortBalance();
return () => { return () => {
clearInterval(intervalGetTradeInfo) clearInterval(intervalGetTradeInfo);
} };
}, [userInfo?.address, isAuthenticated, selectedCoin]) }, [userInfo?.address, isAuthenticated, selectedCoin]);
const handleMessage = async (event: any) => { const handleMessage = async (event: any) => {
if (event.data.type === "LOGOUT") { if (event.data.type === "LOGOUT") {
@ -218,31 +208,29 @@ function App() {
setUserInfo(null); setUserInfo(null);
setAvatar(""); setAvatar("");
setIsAuthenticated(false); setIsAuthenticated(false);
setQortBalance(null) setQortBalance(null);
localStorage.setItem("token", ""); localStorage.setItem("token", "");
} else if(event.data.type === "RESPONSE_FOR_TRADES"){ } else if (event.data.type === "RESPONSE_FOR_TRADES") {
const response = event.data.payload;
if (response?.extra?.atAddresses) {
const response = event.data.payload
if (response?.extra?.atAddresses
) {
try { try {
const status = response.callResponse === true ? 'trade-ongoing' : 'trade-failed' const status =
response.callResponse === true ? "trade-ongoing" : "trade-failed";
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
// Prepare transaction data // Prepare transaction data
const transactionData = { const transactionData = {
qortalAtAddresses: response.extra.atAddresses, qortalAtAddresses: response.extra.atAddresses,
qortAddress: userInfo.address, qortAddress: userInfo.address,
status: status, status: status,
message: response.extra.message, message: response.extra.message,
}; };
// Update transactions in IndexedDB // Update transactions in IndexedDB
const result = await updateTransactionInDB(transactionData); const result = await updateTransactionInDB(transactionData);
fetchOngoingTransactions() fetchOngoingTransactions();
executeEvent("execute-get-new-block-trades", {}) executeEvent("execute-get-new-block-trades", {});
} catch (error) { } catch (error) {
console.log({error}) console.log({ error });
} }
} }
} }
@ -256,36 +244,33 @@ function App() {
}; };
}, [userInfo?.address]); }, [userInfo?.address]);
const getCoinLabel = useCallback((coin?: string)=> { const getCoinLabel = useCallback(
switch(coin || selectedCoin){ (coin?: string) => {
case "LITECOIN":{ switch (coin || selectedCoin) {
case "LITECOIN": {
return 'LTC' 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":{ },
[selectedCoin]
return 'DOGE' );
}
case "BITCOIN":{
return 'BTC'
}
case "DIGIBYTE":{
return 'DGB'
}
case "RAVENCOIN":{
return 'RVN'
}
case "PIRATECHAIN":{
return 'ARRR'
}
default:
return null
}
}, [selectedCoin])
const gameContextValue: IContextProps = { const gameContextValue: IContextProps = {
userInfo, userInfo,
@ -296,37 +281,54 @@ function App() {
fetchOngoingTransactions, fetchOngoingTransactions,
foreignCoinBalance, foreignCoinBalance,
qortBalance, qortBalance,
isAuthenticated, isAuthenticated,
setIsAuthenticated, setIsAuthenticated,
OAuthLoading, OAuthLoading,
setOAuthLoading, setOAuthLoading,
updateTransactionInDB, updateTransactionInDB,
sellOrders, sellOrders,
deleteTemporarySellOrder, updateTemporaryFailedTradeBots, fetchTemporarySellOrders, isUsingGateway, selectedCoin, setSelectedCoin, getCoinLabel deleteTemporarySellOrder,
updateTemporaryFailedTradeBots,
fetchTemporarySellOrders,
isUsingGateway,
selectedCoin,
setSelectedCoin,
getCoinLabel,
}; };
return ( return (
<NotificationContext.Provider value={notificationContextValue}> <GlobalProvider
<LoadingContext.Provider value={loadingContextValue}> config={{
<UserContext.Provider value={userContextValue}> auth: {
<GameContext.Provider value={gameContextValue}> balanceSetting: {
<Notification /> interval: 180000,
<ThemeProvider theme={darkTheme}> onlyOnMount: false,
<Routes> },
<Route authenticateOnMount: false,
path="/" userAccountInfo: {
element={ address: userInfo?.address,
<HomePage publicKey: userInfo?.publicKey,
/> },
} },
/> appName: "q-trade",
</Routes> publicSalt: "SwQR6+uWzwm9Cqw1vqpR8zPKIThqdc5X7CUhWKn+rfc=",
</ThemeProvider> }}
</GameContext.Provider> >
</UserContext.Provider> <NotificationContext.Provider value={notificationContextValue}>
</LoadingContext.Provider> <LoadingContext.Provider value={loadingContextValue}>
</NotificationContext.Provider> <UserContext.Provider value={userContextValue}>
<GameContext.Provider value={gameContextValue}>
<Notification />
<ThemeProvider theme={darkTheme}>
<Routes>
<Route path="/" element={<HomePage />} />
</Routes>
</ThemeProvider>
</GameContext.Provider>
</UserContext.Provider>
</LoadingContext.Provider>
</NotificationContext.Provider>
</GlobalProvider>
); );
} }

View File

@ -5,7 +5,7 @@ import { useIndexedDBContext } from "../../contexts/indexedDBContext";
const fetchTradeInfo = async (qortalAtAddress) => { 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() const data = await checkIfOfferingRes.json()
return data return data
}; };

View File

@ -24,7 +24,6 @@ export const BuyContainer = styled(Box)(({ theme }) => ({
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
bottom: "0px", bottom: "0px",
height: "100px",
padding: "18px 14px 12px 14px", padding: "18px 14px 12px 14px",
background: "#323336", background: "#323336",
zIndex: 3, zIndex: 3,

View File

@ -10,12 +10,13 @@ import { AgGridReact } from "ag-grid-react";
import { import {
ColDef, ColDef,
RowClassParams, RowClassParams,
RowNode,
RowStyle, RowStyle,
SizeColumnsToContentStrategy, SizeColumnsToContentStrategy,
} from "ag-grid-community"; } from "ag-grid-community";
import "ag-grid-community/styles/ag-grid.css"; import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.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 { import {
Alert, Alert,
Box, Box,
@ -26,6 +27,7 @@ import {
DialogContent, DialogContent,
DialogContentText, DialogContentText,
DialogTitle, DialogTitle,
FormControlLabel,
IconButton, IconButton,
Snackbar, Snackbar,
SnackbarCloseReason, SnackbarCloseReason,
@ -48,9 +50,8 @@ import {
MainContainer, MainContainer,
} from "./Table-styles"; } from "./Table-styles";
export const baseLocalHost = window.location.host; // export const baseLocalHost = window.location.host;
// export const baseLocalHost = "127.0.0.1:12391"; export const baseLocalHost = "devnet-nodes.qortal.link:11111";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import ContentCopyIcon from "@mui/icons-material/ContentCopy";
@ -79,8 +80,12 @@ export const autoSizeStrategy: SizeColumnsToContentStrategy = {
type: "fitCellContents", type: "fitCellContents",
}; };
export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => { export const TradeOffers: React.FC<any> = ({
foreignCoinBalance,
fee,
}: any) => {
const [offers, setOffers] = useState<any[]>([]); const [offers, setOffers] = useState<any[]>([]);
const [signedUnlockingFees, setSignedUnlockingFees] = useState(null);
const [qortalNames, setQortalNames] = useState({}); const [qortalNames, setQortalNames] = useState({});
const { const {
fetchOngoingTransactions, fetchOngoingTransactions,
@ -90,6 +95,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
getCoinLabel, getCoinLabel,
selectedCoin, selectedCoin,
} = useContext(gameContext); } = useContext(gameContext);
const isRemoveOrdersWithoutUnlockingFees = useRef(false);
const listOfOngoingTradesAts = useMemo(() => { const listOfOngoingTradesAts = useMemo(() => {
return ( return (
onGoingTrades onGoingTrades
@ -105,6 +111,14 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
message: messageInfo, message: messageInfo,
} = useModal(); } = useModal();
const {
isShow: isShowTradesUnknownFee,
onCancel: onCancelTradesUnknownFee,
onOk: onOkTradesUnknownFee,
show: showTradesUnknownFee,
message: messageTradesUnknownFee,
} = useModal();
const offersWithoutOngoing = useMemo(() => { const offersWithoutOngoing = useMemo(() => {
return offers.filter( return offers.filter(
(item) => !listOfOngoingTradesAts.includes(item.qortalAtAddress) (item) => !listOfOngoingTradesAts.includes(item.qortalAtAddress)
@ -122,13 +136,39 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
const offeringTrades = useRef<any[]>([]); const offeringTrades = useRef<any[]>([]);
const blockedTradesList = useRef([]); const blockedTradesList = useRef([]);
const gridRef = useRef<any>(null); const gridRef = useRef<any>(null);
const [openShowOfferDetails, setOpenShowOfferDetails] = useState(null) const [openShowOfferDetails, setOpenShowOfferDetails] = useState(null);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [info, setInfo] = useState<any>(null); const [info, setInfo] = useState<any>(null);
const BuyButton = () => { const BuyButton = () => {
return <BuyOrderBtn onClick={buyOrder}>BUY</BuyOrderBtn>; return <BuyOrderBtn onClick={buyOrder}>BUY</BuyOrderBtn>;
}; };
const intervalGetSignedUnlockingFees = useRef<number | null>(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 = { const defaultColDef = {
resizable: true, // Make columns resizable by default resizable: true, // Make columns resizable by default
sortable: true, // Make columns sortable by default sortable: true, // Make columns sortable by default
@ -176,8 +216,6 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
} }
}; };
const columnDefs: ColDef[] = useMemo(() => { const columnDefs: ColDef[] = useMemo(() => {
return [ return [
{ {
@ -189,12 +227,25 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
pinned: "left", // Optional, to pin this column on the left pinned: "left", // Optional, to pin this column on the left
resizable: false, resizable: false,
suppressRowClickSelection: true, suppressRowClickSelection: true,
cellRenderer: (params) => cellRenderer: (params) => (
<SelectWithInfoCell {...params} selectTradeForDetails={()=> { <SelectWithInfoCell
setOpenShowOfferDetails(params?.node?.data) {...params}
}} />, selectTradeForDetails={() => {
// suppressRowClickSelection: true, // prevent whole row selection on click 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", headerName: "QORT AMOUNT",
@ -220,6 +271,22 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
minWidth: 150, // Ensure it doesn't shrink too much minWidth: 150, // Ensure it doesn't shrink too much
resizable: true, 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", headerName: "Seller",
field: "qortalCreator", field: "qortalCreator",
@ -241,7 +308,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
}, },
}, },
]; ];
}, [qortalNames, getCoinLabel]); }, [qortalNames, getCoinLabel, signedUnlockingFees]);
// const onRowClicked = (event: any) => { // const onRowClicked = (event: any) => {
// if(listOfOngoingTradesAts.includes(event.data.qortalAtAddress)) return // if(listOfOngoingTradesAts.includes(event.data.qortalAtAddress)) return
@ -256,7 +323,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
const getNewBlockedTrades = async () => { const getNewBlockedTrades = async () => {
const unconfirmedTransactionsList = 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( var addBlockedTrades = JSON.parse(
localStorage.getItem("failedTrades") || "[]" localStorage.getItem("failedTrades") || "[]"
@ -468,7 +535,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
}; };
useEffect(() => { useEffect(() => {
if(isUsingGateway === null) return if (isUsingGateway === null) return;
blockedTradesList.current = JSON.parse( blockedTradesList.current = JSON.parse(
localStorage.getItem("failedTrades") || "[]" localStorage.getItem("failedTrades") || "[]"
); );
@ -489,8 +556,36 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
}; };
}, [isUsingGateway]); }, [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(() => { 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; if (selectedCoin === null) return;
restartTradeOffers(); restartTradeOffers();
setTimeout(() => { setTimeout(() => {
@ -504,13 +599,17 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
}, [isUsingGateway, selectedCoin]); }, [isUsingGateway, selectedCoin]);
const selectedTotalLTC = useMemo(() => { 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 return acc + (+curr.foreignAmount || 0); // Ensure qortAmount is defined
}, 0); }, 0);
}, [selectedOffers]);
const totalWithKnownFees = +total + +knownFees;
return totalWithKnownFees;
}, [selectedOffers, knownFees]);
const buyOrder = async () => { const buyOrder = async () => {
try { try {
isRemoveOrdersWithoutUnlockingFees.current = false;
if (+foreignCoinBalance < +selectedTotalLTC.toFixed(4)) { if (+foreignCoinBalance < +selectedTotalLTC.toFixed(4)) {
setOpen(true); setOpen(true);
setInfo({ setInfo({
@ -521,6 +620,36 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
} }
if (selectedOffers?.length < 1) return; 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" }); setIsShowBuyInProgress({ status: "buying" });
@ -529,7 +658,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
// type: 'info', // type: 'info',
// message: "Attempting to submit buy order. Please wait..." // message: "Attempting to submit buy order. Please wait..."
// }) // })
const listOfATs = selectedOffers; const listOfATs = offersWithKnownFees;
const response = await qortalRequestWithTimeout( const response = await qortalRequestWithTimeout(
{ {
action: "CREATE_TRADE_BUY_ORDER", action: "CREATE_TRADE_BUY_ORDER",
@ -609,6 +738,18 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
if (params.data.qortalAtAddress === selectedOffer?.qortalAtAddress) { if (params.data.qortalAtAddress === selectedOffer?.qortalAtAddress) {
return { background: "#6D94F533" }; 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; return undefined;
}; };
// const onGridReady = (params) => { // const onGridReady = (params) => {
@ -639,9 +780,10 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
}, []); }, []);
const selectedTotalQORT = useMemo(() => { 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 return acc + (+curr.qortAmount || 0); // Ensure qortAmount is defined
}, 0); }, 0);
return total;
}, [selectedOffers]); }, [selectedOffers]);
const onGridReady = useCallback((params: any) => { const onGridReady = useCallback((params: any) => {
@ -664,6 +806,32 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
setInfo(null); 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 ( return (
<MainContainer> <MainContainer>
<div <div
@ -688,6 +856,23 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
onGridReady={onGridReady} onGridReady={onGridReady}
// domLayout='autoHeight' // domLayout='autoHeight'
getRowId={(params) => params.data.qortalAtAddress} // Ensure rows have unique IDs 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 && ( {/* {selectedOffer && (
<Button onClick={buyOrder}>Buy</Button> <Button onClick={buyOrder}>Buy</Button>
@ -696,7 +881,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
</div> </div>
<div <div
style={{ style={{
height: "120px", height: "150px",
}} }}
/> />
<BuyContainer> <BuyContainer>
@ -716,7 +901,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
width: "calc(100% - 75px)", width: "calc(100% - 75px)",
}} }}
> >
{selectedTotalQORT?.toFixed(3)} QORT {selectedTotalQORT?.toFixed(8)} QORT
</Typography> </Typography>
<Box <Box
sx={{ sx={{
@ -729,15 +914,17 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
<Typography <Typography
sx={{ sx={{
fontSize: "16px", fontSize: "16px",
color: selectedTotalLTC > foreignCoinBalance ? "red" : "white", backgroundColor:
selectedTotalLTC > foreignCoinBalance ? "red" : "unset",
color: "white",
}} }}
> >
<span>{selectedTotalLTC?.toFixed(4)}</span>{" "} <span>{selectedTotalLTC?.toFixed(8)}</span>{" "}
<span <span
style={{ style={{
marginLeft: "auto", marginLeft: "auto",
}} }}
>{`${getCoinLabel()} `}</span> >{`${getCoinLabel()} (with known fees)`}</span>
</Typography> </Typography>
</Box> </Box>
<Typography <Typography
@ -746,7 +933,7 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
color: "white", color: "white",
}} }}
> >
<span>{foreignCoinBalance?.toFixed(4)}</span>{" "} <span>{foreignCoinBalance?.toFixed(8)}</span>{" "}
<span <span
style={{ style={{
marginLeft: "auto", marginLeft: "auto",
@ -814,6 +1001,81 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
)} )}
{isShowTradesUnknownFee && (
<Dialog
open={isShowTradesUnknownFee}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Warning</DialogTitle>
<DialogContent>
<DialogContentText
id="alert-dialog-description"
sx={{ color: "white" }}
>
Some of your buy orders have unknown unlocking fees.
</DialogContentText>
<Spacer height="20px" />
<DialogContentText
id="alert-dialog-description"
sx={{ color: "white" }}
>
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.
</DialogContentText>
<Spacer height="20px" />
<FormControlLabel
sx={{
margin: 0,
}}
control={
<Checkbox
onChange={(e) =>
(isRemoveOrdersWithoutUnlockingFees.current =
e.target.checked)
}
edge="start"
tabIndex={-1}
disableRipple
sx={{
"&.Mui-checked": {
color: "white",
},
"& .MuiSvgIcon-root": {
color: "white",
},
}}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography sx={{ fontSize: "14px" }}>
Remove orders with unknown unlocking fees
</Typography>
</Box>
}
/>
</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={onCancelTradesUnknownFee}
autoFocus
>
Close
</Button>
<Button
variant="contained"
onClick={onOkTradesUnknownFee}
autoFocus
>
Continue
</Button>
</DialogActions>
</Dialog>
)}
{isShowBuyInProgress && ( {isShowBuyInProgress && (
<Dialog <Dialog
open={isShowBuyInProgress} open={isShowBuyInProgress}
@ -945,60 +1207,111 @@ export const TradeOffers: React.FC<any> = ({ foreignCoinBalance }: any) => {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
)} )}
<Dialog <Dialog
open={!!openShowOfferDetails} open={!!openShowOfferDetails}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
PaperProps={{ PaperProps={{
style: { style: {
backgroundColor: "rgb(39, 40, 44)", backgroundColor: "rgb(39, 40, 44)",
background: "rgb(39, 40, 44)", background: "rgb(39, 40, 44)",
}, },
}}
>
<DialogTitle
sx={{
maxHeight: "calc(90vh - 55px)",
maxWidth: "90%",
background: "rgb(39, 40, 44)",
overflow: "auto",
}} }}
> >
<DialogTitle <Typography variant="subtitle1">
sx={{ Buy {openShowOfferDetails?.qortAmount} QORT @{" "}
maxHeight: "calc(90vh - 55px)", {openShowOfferDetails?.foreignAmount} {getCoinLabel()}
maxWidth: "90%", </Typography>
background: "rgb(39, 40, 44)", </DialogTitle>
overflow: "auto", <IconButton
}}
>
<Typography variant="subtitle1">
Buy {openShowOfferDetails?.qortAmount} QORT @ {openShowOfferDetails?.foreignAmount} {getCoinLabel()}
</Typography>
</DialogTitle>
<IconButton
aria-label="close" aria-label="close"
onClick={()=> setOpenShowOfferDetails(null)} onClick={() => setOpenShowOfferDetails(null)}
sx={{ position: "absolute", right: 8, top: 8, color: "#fff" }} sx={{ position: "absolute", right: 8, top: 8, color: "#fff" }}
> >
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
<DialogContent dividers sx={{ borderColor: "#333" }}> <DialogContent dividers sx={{ borderColor: "#333" }}>
<TradeRow enableSlice enableCopy label="Seller" value={openShowOfferDetails?.qortalCreator} extra={qortalNames[openShowOfferDetails?.qortalCreator]} /> {fee &&
<TradeRow label="Amount" value={`${openShowOfferDetails?.qortAmount} QORT`} /> openShowOfferDetails?.fee &&
<TradeRow label="Total" value={`${openShowOfferDetails?.foreignAmount} ${getCoinLabel()}`} /> +fee < +openShowOfferDetails?.fee && (
<TradeRow label="Price" value={`${+openShowOfferDetails?.foreignAmount / +openShowOfferDetails?.qortAmount } ${getCoinLabel()}/QORT`} /> <Box
<TradeRow enableSlice enableCopy label="AT Address" value={openShowOfferDetails?.qortalAtAddress} /> sx={{
</DialogContent> background: "red",
<DialogActions padding: "10px",
sx={{ borderRadius: "5px",
background: "rgb(39, 40, 44)", }}
>
<Typography sx={{ color: "white" }}>
The unlocking fee on this node is lower than the amount
required for this order.
</Typography>
<Spacer height="10px" />
<Typography sx={{ color: "white" }}>
If you're using your own node, you can change the fee by
clicking the "Fee" button next to the coin selector.
</Typography>
</Box>
)}
<TradeRow
enableSlice
enableCopy
label="Seller"
value={openShowOfferDetails?.qortalCreator}
extra={qortalNames[openShowOfferDetails?.qortalCreator]}
/>
<TradeRow
label="Amount"
value={`${openShowOfferDetails?.qortAmount} QORT`}
/>
<TradeRow
label="Total"
value={`${openShowOfferDetails?.foreignAmount} ${getCoinLabel()}`}
/>
<TradeRow
label="Price"
value={`${
+openShowOfferDetails?.foreignAmount /
+openShowOfferDetails?.qortAmount
} ${getCoinLabel()}/QORT`}
/>
{openShowOfferDetails?.fee && (
<TradeRow
label="Unlocking fee"
value={`${openShowOfferDetails?.fee} sats`}
/>
)}
<TradeRow
enableSlice
enableCopy
label="AT Address"
value={openShowOfferDetails?.qortalAtAddress}
/>
</DialogContent>
<DialogActions
sx={{
background: "rgb(39, 40, 44)",
}}
>
<Button
variant="outlined"
onClick={() => {
setOpenShowOfferDetails(null);
}} }}
autoFocus
> >
<Button Close
variant="outlined" </Button>
onClick={() => { </DialogActions>
setOpenShowOfferDetails(null) </Dialog>
}}
autoFocus
>
Close
</Button>
</DialogActions>
</Dialog>
</MainContainer> </MainContainer>
); );
}; };
@ -1008,13 +1321,13 @@ const TradeRow = ({
value, value,
extra, extra,
enableSlice, enableSlice,
enableCopy enableCopy,
}: { }: {
label: string; label: string;
value: string; value: string;
extra?: string; extra?: string;
enableSlice?: boolean enableSlice?: boolean;
enableCopy?: boolean enableCopy?: boolean;
}) => ( }) => (
<Box <Box
sx={{ sx={{
@ -1036,10 +1349,12 @@ const TradeRow = ({
}} }}
> >
<Typography variant="body2" sx={{ fontWeight: 500 }}> <Typography variant="body2" sx={{ fontWeight: 500 }}>
{enableSlice && value?.length > 18 ? value?.slice(0, 6) + "..." + value.slice(-4) : value} {enableSlice && value?.length > 18
? value?.slice(0, 6) + "..." + value.slice(-4)
: value}
</Typography> </Typography>
{enableCopy && ( {enableCopy && (
<Tooltip title="Copy"> <Tooltip title="Copy">
<IconButton size="small" onClick={() => copyToClipboard(value)}> <IconButton size="small" onClick={() => copyToClipboard(value)}>
<ContentCopyIcon fontSize="small" /> <ContentCopyIcon fontSize="small" />
</IconButton> </IconButton>
@ -1054,34 +1369,30 @@ const TradeRow = ({
</Box> </Box>
); );
const SelectWithInfoCell = ({ selectTradeForDetails }) => {
const SelectWithInfoCell = ({selectTradeForDetails}) => {
const handleInfoClick = (e: React.MouseEvent) => { const handleInfoClick = (e: React.MouseEvent) => {
e.stopPropagation(); // Prevents row selection e.stopPropagation(); // Prevents row selection
selectTradeForDetails() selectTradeForDetails();
// alert(`Info for ${data.qortalAtAddress}`); // Replace with your own UI // alert(`Info for ${data.qortalAtAddress}`); // Replace with your own UI
}; };
return ( return (
<div className="ag-cell-ignore-selection" style={{ display: "flex", alignItems: "center", gap: 6 }}> <div
className="ag-cell-ignore-selection"
style={{ display: "flex", alignItems: "center", gap: 6 }}
<IconButton >
size="small" <IconButton
onClick={handleInfoClick} size="small"
onClickCapture={(e) => { onClick={handleInfoClick}
e.stopPropagation(); onClickCapture={(e) => {
handleInfoClick(e) e.stopPropagation();
handleInfoClick(e);
}} }}
onMouseDown={(e) => e.stopPropagation()} // 👈 this is key onMouseDown={(e) => e.stopPropagation()} // 👈 this is key
sx={{ minWidth: 0, padding: "0 4px" }} sx={{ minWidth: 0, padding: "0 4px" }}
> >
<InfoOutlineIcon /> <InfoOutlineIcon />
</IconButton> </IconButton>
</div> </div>
); );
}; };

View File

@ -214,10 +214,12 @@ export const CoinReceiveBtn = styled(Button)(({ theme }) => ({
export const CoinSelectRow = styled(Box)({ export const CoinSelectRow = styled(Box)({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "row",
gap: "5px", gap: "5px",
alignSelf: "flex-start", justifyContent: "flex-start",
marginBottom: "5px" marginBottom: "5px",
width: '100%',
flexWrap: 'wrap'
}); });
export const CoinActionContainer = styled(Box)({ export const CoinActionContainer = styled(Box)({

View File

@ -1,4 +1,11 @@
import { useState, useEffect, useRef, useContext, ChangeEvent, useMemo } from "react"; import {
useState,
useEffect,
useRef,
useContext,
ChangeEvent,
useMemo,
} from "react";
import { import {
BubbleCardColored1, BubbleCardColored1,
CoinActionContainer, CoinActionContainer,
@ -26,15 +33,15 @@ import { cropAddress } from "../../utils/cropAddress";
import qtradeLogo from "../../components/common/icons/qtradeLogo.png"; import qtradeLogo from "../../components/common/icons/qtradeLogo.png";
import qortIcon from "../../assets/img/qort.png"; import qortIcon from "../../assets/img/qort.png";
import ErrorIcon from "@mui/icons-material/Error"; import ErrorIcon from "@mui/icons-material/Error";
import { CopyToClipboard } from "react-copy-to-clipboard";
import Copy from "../../assets/SVG/Copy.svg"; import Copy from "../../assets/SVG/Copy.svg";
import {AddressQRCode} from './AddressQRCode' import { AddressQRCode } from "./AddressQRCode";
import {FallingLines} from 'react-loader-spinner' import { FallingLines } from "react-loader-spinner";
import { import {
Alert, Alert,
AppBar, AppBar,
Avatar, Avatar,
Box, Box,
ButtonBase,
Card, Card,
CardContent, CardContent,
FormControl, FormControl,
@ -58,6 +65,8 @@ import arrrIcon from "../../assets/img/arrr.png";
import { Spacer } from "../common/Spacer"; import { Spacer } from "../common/Spacer";
import { ReusableModal } from "../common/reusable-modal/ReusableModal"; import { ReusableModal } from "../common/reusable-modal/ReusableModal";
import { NotificationContext } from "../../contexts/notificationContext"; import { NotificationContext } from "../../contexts/notificationContext";
import UnsignedFees from "../sell/UnsignedFees";
import { FeeManager } from "../sell/FeeManager";
const checkIfLocal = async () => { const checkIfLocal = async () => {
try { 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<boolean>(false); const [openDropdown, setOpenDropdown] = useState<boolean>(false);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLDivElement>(null); const buttonRef = useRef<HTMLDivElement>(null);
@ -167,7 +182,6 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
useContext(gameContext); useContext(gameContext);
const { setNotification } = useContext(NotificationContext); const { setNotification } = useContext(NotificationContext);
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
padding: 8, padding: 8,
"& .MuiSwitch-track": { "& .MuiSwitch-track": {
@ -239,31 +253,32 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
// } // }
// }, [userInfo]); // }, [userInfo]);
const sendCoin = async ()=> { const sendCoin = async () => {
try { try {
const coin = openCoinActionModal.coin === "QORT" ? 'QORT' : getCoinLabel() const coin =
if(!coin) return openCoinActionModal.coin === "QORT" ? "QORT" : getCoinLabel();
if (!coin) return;
setOpen(true); setOpen(true);
setInfo({ setInfo({
type: "info", type: "info",
message: "Sending Coin...", message: "Sending Coin...",
autoHideDurationOff: true autoHideDurationOff: true,
}); });
const response = await qortalRequest({ const response = await qortalRequest({
action: "SEND_COIN", action: "SEND_COIN",
coin, coin,
destinationAddress: senderAddress, destinationAddress: senderAddress,
amount: +amount amount: +amount,
}); });
if(response?.error){ if (response?.error) {
throw new Error(response?.error || "Failed to send coin.") throw new Error(response?.error || "Failed to send coin.");
} }
setOpen(true); setOpen(true);
setInfo({ setInfo({
type: "success", type: "success",
message: "Coin sent", message: "Coin sent",
}); });
setAmount('') setAmount("");
} catch (error) { } catch (error) {
setOpen(true); setOpen(true);
setInfo({ setInfo({
@ -271,7 +286,7 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
message: error?.error || error?.message, message: error?.error || error?.message,
}); });
} }
} };
return ( return (
<> <>
@ -428,12 +443,10 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
}} }}
/> />
{foreignCoinBalance === null ? ( {foreignCoinBalance === null ? (
<FallingLines <FallingLines color="white" width="30" visible={true} />
color="white" ) : (
width="30" foreignCoinBalance
visible={true} )}{" "}
/>
) : foreignCoinBalance}{" "}
{getCoinLabel()} {getCoinLabel()}
</Box> </Box>
<CoinActionsRow> <CoinActionsRow>
@ -467,7 +480,10 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
<Select <Select
size="small" size="small"
value={selectedCoin} value={selectedCoin}
onChange={(e) => setSelectedCoin(e.target.value)} onChange={(e) => {
setFee(null)
setSelectedCoin(e.target.value)
}}
> >
<MenuItem value={"LITECOIN"}> <MenuItem value={"LITECOIN"}>
<SelectRow coin="LTC" /> <SelectRow coin="LTC" />
@ -488,6 +504,16 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
<SelectRow coin="ARRR" /> <SelectRow coin="ARRR" />
</MenuItem> </MenuItem>
</Select> </Select>
{!isUsingGateway && selectedCoin !== 'PIRATECHAIN' && (
<>
<FeeManager selectedCoin={selectedCoin} fee={fee}
setFee={setFee} />
<UnsignedFees
qortAddress={qortAddress}
selectedCoin={selectedCoin}
/>
</>
)}
</CoinSelectRow> </CoinSelectRow>
<Snackbar <Snackbar
anchorOrigin={{ vertical: "bottom", horizontal: "center" }} anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
@ -497,252 +523,286 @@ export const Header = ({ qortBalance, foreignCoinBalance }: any) => {
> >
{info?.type && ( {info?.type && (
<Alert <Alert
onClose={handleClose} onClose={handleClose}
severity={info?.type} severity={info?.type}
variant="filled" variant="filled"
sx={{ width: "100%" }} sx={{ width: "100%" }}
> >
{info?.message} {info?.message}
</Alert> </Alert>
)} )}
</Snackbar> </Snackbar>
{openCoinActionModal && ( {openCoinActionModal && (
<ReusableModal <ReusableModal
onClickClose={() => { onClickClose={() => {
setOpenCoinActionModal(null); setOpenCoinActionModal(null);
setAmount('') setAmount("");
setSenderAddress('') setSenderAddress("");
}} }}
backdrop backdrop
> >
<CoinActionContainer> <CoinActionContainer>
{openCoinActionModal.type === "send" ? <> {openCoinActionModal.type === "send" ? (
<CoinActionRow>
<HeaderRow>
{openCoinActionModal.type === "send" &&
openCoinActionModal.coin === "QORT" ? (
<>
<SendFont>Send {openCoinActionModal.coin}</SendFont>
<img
src={qortIcon}
style={{
height: "25px",
width: "auto",
}}
/>
</>
) : openCoinActionModal.type === "send" &&
openCoinActionModal.coin !== "QORT" ? (
<>
<SendFont>Send {openCoinActionModal.coin}</SendFont>
<img
src={getCoinIcon(getCoinLabel())}
style={{
height: "25px",
width: "auto",
}}
/>
</>
) : null}
</HeaderRow>
</CoinActionRow>
<CoinActionRow>
<FormControl fullWidth>
<CustomInputField
style={{ flexGrow: 1 }}
name={
openCoinActionModal.type === "send"
? `${openCoinActionModal.coin === "QORT" ? 'Recipient Address or Name' : 'Recipient Address'}`
: "Receive Address"
}
label={
openCoinActionModal.type === "send"
? `${openCoinActionModal.coin === "QORT" ? 'Recipient Address or Name' : 'Recipient Address'}`
: "Receive Address"
}
variant="filled"
value={
openCoinActionModal.type === "send"
? senderAddress
: receiverAddress
}
required
onChange={(e) => {
if (openCoinActionModal.type === "send") {
setSenderAddress(e.target.value);
} else {
setReceiverAddress(e.target.value);
}
}}
/>
</FormControl>
</CoinActionRow>
{openCoinActionModal.type === "send" && (
<CoinActionRow>
<FormControl fullWidth>
<CustomInputField
style={{ flexGrow: 1 }}
name="Amount"
label="Amount"
variant="filled"
type="number"
value={
amount
}
required
onChange={(e) => {
setAmount(e.target.value)
}}
/>
</FormControl>
</CoinActionRow>
)}
</> : (
<> <>
<ReceiveCoin setOpen={setOpen} setInfo={setInfo} coinAddresses={coinAddresses} setCoinAddresses={setCoinAddresses} selectedCoin={openCoinActionModal.coin === "QORT" ? 'QORT' :getCoinLabel()} /> <CoinActionRow>
<HeaderRow>
{openCoinActionModal.type === "send" &&
openCoinActionModal.coin === "QORT" ? (
<>
<SendFont>Send {openCoinActionModal.coin}</SendFont>
<img
src={qortIcon}
style={{
height: "25px",
width: "auto",
}}
/>
</>
) : openCoinActionModal.type === "send" &&
openCoinActionModal.coin !== "QORT" ? (
<>
<SendFont>Send {openCoinActionModal.coin}</SendFont>
<img
src={getCoinIcon(getCoinLabel())}
style={{
height: "25px",
width: "auto",
}}
/>
</>
) : null}
</HeaderRow>
</CoinActionRow>
<CoinActionRow>
<FormControl fullWidth>
<CustomInputField
style={{ flexGrow: 1 }}
name={
openCoinActionModal.type === "send"
? `${
openCoinActionModal.coin === "QORT"
? "Recipient Address or Name"
: "Recipient Address"
}`
: "Receive Address"
}
label={
openCoinActionModal.type === "send"
? `${
openCoinActionModal.coin === "QORT"
? "Recipient Address or Name"
: "Recipient Address"
}`
: "Receive Address"
}
variant="filled"
value={
openCoinActionModal.type === "send"
? senderAddress
: receiverAddress
}
required
onChange={(e) => {
if (openCoinActionModal.type === "send") {
setSenderAddress(e.target.value);
} else {
setReceiverAddress(e.target.value);
}
}}
/>
</FormControl>
</CoinActionRow>
{openCoinActionModal.type === "send" && (
<CoinActionRow>
<FormControl fullWidth>
<CustomInputField
style={{ flexGrow: 1 }}
name="Amount"
label="Amount"
variant="filled"
type="number"
value={amount}
required
onChange={(e) => {
setAmount(e.target.value);
}}
/>
</FormControl>
</CoinActionRow>
)}
</>
) : (
<>
<ReceiveCoin
setOpen={setOpen}
setInfo={setInfo}
coinAddresses={coinAddresses}
setCoinAddresses={setCoinAddresses}
selectedCoin={
openCoinActionModal.coin === "QORT"
? "QORT"
: getCoinLabel()
}
/>
</> </>
)} )}
{openCoinActionModal.type === 'send' && ( {openCoinActionModal.type === "send" && (
<CoinActionRow style={{gap: "10px"}}> <CoinActionRow style={{ gap: "10px" }}>
{/* <CoinCancelBtn onClick={() => setOpenCoinActionModal(null)}> {/* <CoinCancelBtn onClick={() => setOpenCoinActionModal(null)}>
Cancel Cancel
</CoinCancelBtn> */} </CoinCancelBtn> */}
<CoinConfirmSendBtn <CoinConfirmSendBtn
onClick={() => { onClick={() => {
if(openCoinActionModal.type === 'send'){ if (openCoinActionModal.type === "send") {
sendCoin() sendCoin();
} }
setNotification({ setNotification({
alertType: "alertInfo", alertType: "alertInfo",
msg: "Sending...", msg: "Sending...",
}); });
}} }}
> >
{openCoinActionModal.type === "send" ? "Send" : "Receive"} {openCoinActionModal.type === "send" ? "Send" : "Receive"}
</CoinConfirmSendBtn> </CoinConfirmSendBtn>
</CoinActionRow> </CoinActionRow>
)} )}
</CoinActionContainer> </CoinActionContainer>
</ReusableModal> </ReusableModal>
)} )}
</HeaderNav> </HeaderNav>
</> </>
); );
}; };
export const AddressBox = styled(Box)` export const AddressBox = styled(Box)`
display: flex; display: flex;
border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5)); border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5));
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: auto; width: auto;
word-break: break-word; word-break: break-word;
padding: 5px 15px 5px 15px; padding: 5px 15px 5px 15px;
gap: 5px; gap: 5px;
border-radius: 100px; border-radius: 100px;
font-family: Inter; font-family: Inter;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
line-height: 14.52px; line-height: 14.52px;
text-align: left; text-align: left;
color: var(--50-white, rgba(255, 255, 255, 0.5)); color: var(--50-white, rgba(255, 255, 255, 0.5));
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
&:hover { &:hover {
background-color: rgba(41, 41, 43, 1); background-color: rgba(41, 41, 43, 1);
color: white; color: white;
svg path { svg path {
fill: white; // Fill color changes to white on hover 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 getForeignAddress = async (coin) => {
const ReceiveCoin = ({coinAddresses, setCoinAddresses, selectedCoin, setOpen, setInfo})=> {
const [errorMsg, setErrorMsg] = useState('')
const foreignAddress = useMemo(()=> {
return coinAddresses[selectedCoin] || null
}, [coinAddresses, selectedCoin])
const getForeignAddress = async (coin)=> {
try { try {
setOpen(true); setOpen(true);
setInfo({ setInfo({
type: "info", type: "info",
message: "Retrieving address...", message: "Retrieving address...",
}); });
const response = await qortalRequest({ const response = await qortalRequest({
action: "GET_USER_WALLET", action: "GET_USER_WALLET",
coin coin,
}); });
if(response?.address){ if (response?.address) {
setCoinAddresses((prev)=> { setCoinAddresses((prev) => {
return { return {
...prev, ...prev,
[coin]: response.address [coin]: response.address,
} };
}) });
} }
if(response?.error){ if (response?.error) {
throw new Error(response?.error || "Failed to send coin.") throw new Error(response?.error || "Failed to send coin.");
} }
} catch (error) { } catch (error) {
setErrorMsg(error?.message) setErrorMsg(error?.message);
} finally { } finally {
setOpen(false); setOpen(false);
setInfo(null); setInfo(null);
} }
} };
useEffect(()=> { useEffect(() => {
if(!selectedCoin) return if (!selectedCoin) return;
if(!coinAddresses[selectedCoin]){ if (!coinAddresses[selectedCoin]) {
getForeignAddress(selectedCoin) getForeignAddress(selectedCoin);
} }
}, [selectedCoin, coinAddresses]) }, [selectedCoin, coinAddresses]);
return ( return (
<Box sx={{ <Box
display: 'flex', sx={{
flexDirection: 'column', display: "flex",
alignItems: 'center' flexDirection: "column",
}}> alignItems: "center",
<Typography sx={{ }}
color: 'white' >
}}>{`Send ${selectedCoin} to your address below`}</Typography> <Typography
<Spacer height="20px" /> sx={{
color: "white",
}}
>{`Send ${selectedCoin} to your address below`}</Typography>
<Spacer height="20px" />
{foreignAddress && ( {foreignAddress && (
<CopyToClipboard text={foreignAddress} onCopy={()=> { <ButtonBase
setOpen(true); onClick={() => {
setInfo({ navigator.clipboard
type: "info", .writeText(foreignAddress)
message: "Address copied!", .then(() => {
}); setOpen(true);
}}> setInfo({
<AddressBox> type: "info",
{foreignAddress} <img src={Copy} /> message: "Address copied!",
</AddressBox> });
</CopyToClipboard> })
.catch((err) => {
console.error("Failed to copy LTC address:", err);
});
}}
>
<AddressBox>
{foreignAddress} <img src={Copy} />
</AddressBox>
</ButtonBase>
)} )}
{foreignAddress && ( {foreignAddress && (
<> <>
<AddressQRCode targetAddress={foreignAddress} /> <AddressQRCode targetAddress={foreignAddress} />
</> </>
)} )}
{errorMsg && ( {errorMsg && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<Typography sx={{ <Typography
color: 'white' sx={{
}}>{errorMsg}</Typography> color: "white",
}}
>
{errorMsg}
</Typography>
</> </>
)} )}
</Box> </Box>
) );
} };

View File

@ -23,8 +23,8 @@ import TradeBotList from "./TradeBotList";
export const CustomLabel = styled(InputLabel)` export const CustomLabel = styled(InputLabel)`
font-weight: 400; font-weight: 400;
font-family: Inter; font-family: Inter;
font-size: 10px; font-size: 14px;
line-height: 12px; line-height: 1.2;
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
`; `;
@ -61,12 +61,12 @@ export const CustomInput = styled(TextField)({
// backgroundColor: "rgba(30, 30, 32, 1)", // backgroundColor: "rgba(30, 30, 32, 1)",
outline: "none", outline: "none",
input: { input: {
fontSize: 10, fontSize: '14px',
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: 400, fontWeight: 400,
color: "white", color: "white",
"&::placeholder": { "&::placeholder": {
fontSize: 16, fontSize: '14px',
color: "rgba(255, 255, 255, 0.2)", color: "rgba(255, 255, 255, 0.2)",
}, },
outline: "none", outline: "none",
@ -263,7 +263,7 @@ export const CreateSell = ({ qortAddress, show }) => {
onChange={(e) => setQortAmount(+e.target.value)} onChange={(e) => setQortAmount(+e.target.value)}
autoComplete="off" autoComplete="off"
/> />
<Spacer height="6px" /> <Spacer height="15px" />
<CustomLabel htmlFor="standard-adornment-amount"> <CustomLabel htmlFor="standard-adornment-amount">
{`Price of Each QORT (in ${getCoinLabel()})`} {`Price of Each QORT (in ${getCoinLabel()})`}
</CustomLabel> </CustomLabel>
@ -275,7 +275,7 @@ export const CreateSell = ({ qortAddress, show }) => {
onChange={(e) => setForeignAmount(+e.target.value)} onChange={(e) => setForeignAmount(+e.target.value)}
autoComplete="off" autoComplete="off"
/> />
<Spacer height="6px" /> <Spacer height="15px" />
<Typography> <Typography>
{`${qortAmount * foreignAmount} ${getCoinLabel()}`} for{" "} {`${qortAmount * foreignAmount} ${getCoinLabel()}`} for{" "}
{qortAmount} QORT {qortAmount} QORT

View File

@ -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<QortalGetMetadata>({
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<any>(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<HTMLElement>,
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 (
<>
<ButtonBase
onClick={() => {
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
},
}}
>
<Typography>Fee: {fee}</Typography>
<ChangeCircleIcon
sx={{
color: "white",
}}
/>
</ButtonBase>
{openModal && (
<ReusableModal
onClickClose={() => {
setOpenModal(false);
setEditFee(fee);
}}
backdrop
>
<CoinActionContainer sx={{
width: '450px',
maxWidth: '95vw'
}}>
<CoinActionRow>
<HeaderRow>
<Typography
variant="h4"
sx={{
color: "white",
}}
>
Update unlocking fee for {selectedCoin}
</Typography>
</HeaderRow>
</CoinActionRow>
<CoinActionRow>
<HeaderRow>
<Box sx={{
width: '100%'
}}>
<Box sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<CustomLabel htmlFor="standard-adornment-name">
Recommended fee selection
</CustomLabel>
<Spacer height="10px" />
<ToggleButtonGroup
color="primary"
value={recommendedFee}
exclusive
onChange={handleChange}
aria-label="Platform"
>
<ToggleButton value="l">Low</ToggleButton>
<ToggleButton value="m">Medium</ToggleButton>
<ToggleButton value="h">High</ToggleButton>
<ToggleButton value="custom">Custom</ToggleButton>
</ToggleButtonGroup>
</Box>
{recommendedFeeDisplay && (
<>
<Spacer height="15px" />
<Typography
sx={{
color: "white",
fontSize: "18px",
}}
>
New fee:{" "}
{calculateFeeFromRate(recommendedFeeDisplay, 300)}
</Typography>
<Spacer height="10px" />
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
}}
>
<QuestionMarkIcon
sx={{
color: "white",
}}
/>
<Typography
sx={{
color: "white",
}}
>
This recommended fee is derived from{" "}
{recommendedFeeDisplay} per kb, for a transaction that
is approximately 300 kB in size.
</Typography>
</Box>
</>
)}
</Box>
</HeaderRow>
</CoinActionRow>
{recommendedFee === "custom" && (
<CoinActionRow>
<HeaderRow>
<Box>
<CustomLabel htmlFor="standard-adornment-name">
Custom fee
</CustomLabel>
<Spacer height="5px" />
<CustomInput
id="standard-adornment-name"
type="number"
value={editFee}
onChange={(e) => setEditFee(e.target.value)}
autoComplete="off"
/>
</Box>
</HeaderRow>
</CoinActionRow>
)}
<ButtonBase
onClick={updateFee}
disabled={(recommendedFee === 'custom' && !editFee)}
sx={{
minHeight: "42px",
border: "1px solid gray",
color: "white",
display: "flex",
alignItems: "center",
padding: "5px 20px",
gap: "10px",
borderRadius: "5px",
"&:hover": {
border: "1px solid white", // Border color on hover
},
}}
>
<ChangeCircleIcon
sx={{
color: "white",
}}
/>
<Typography>Update fee</Typography>
</ButtonBase>
</CoinActionContainer>
</ReusableModal>
)}
<Snackbar
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
open={openAlert}
onClose={handleCloseAlert}
autoHideDuration={6000}
>
<Alert
onClose={handleCloseAlert}
severity={info?.type}
variant="filled"
sx={{ width: "100%" }}
>
{info?.message}
</Alert>
</Snackbar>
</>
);
};

View File

@ -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<any>(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 (
<>
<ButtonBase
onClick={signForeignFees}
sx={{
minHeight: "42px",
background: "red",
color: "white",
display: "flex",
alignItems: "center",
padding: "5px 20px",
gap: "20px",
borderRadius: "5px",
"&:hover": {
background: "darkred", // Border color on hover
},
}}
>
<TouchAppIcon
sx={{
color: "white",
}}
/>
<Typography>Action required! You have unsigned fees</Typography>
</ButtonBase>
{openModal && (
<ReusableModal
onClickClose={() => {
setOpenModal(false);
}}
backdrop
>
<CoinActionContainer>
<CoinActionRow>
<HeaderRow>
<Typography
variant="h2"
sx={{
color: "white",
}}
>
Action required!
</Typography>
</HeaderRow>
</CoinActionRow>
<CoinActionRow>
<HeaderRow>
<Typography
variant="h4"
sx={{
color: "white",
}}
>
In order for buyers to use the correct unlocking fee, please
sign your fees.
</Typography>
</HeaderRow>
</CoinActionRow>
<ButtonBase
onClick={signForeignFees}
sx={{
minHeight: "42px",
background: "red",
color: "white",
display: "flex",
alignItems: "center",
padding: "5px 20px",
gap: "20px",
borderRadius: "5px",
"&:hover": {
background: "darkred", // Border color on hover
},
}}
>
<TouchAppIcon
sx={{
color: "white",
}}
/>
<Typography>Sign Fees</Typography>
</ButtonBase>
</CoinActionContainer>
</ReusableModal>
)}
<Snackbar
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
open={openAlert}
onClose={handleCloseAlert}
autoHideDuration={6000}
>
<Alert
onClose={handleCloseAlert}
severity={info?.type}
variant="filled"
sx={{ width: "100%" }}
>
{info?.message}
</Alert>
</Snackbar>
</>
);
}

View File

@ -1,5 +1,5 @@
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import ReactDOM from 'react-dom/client' import { createRoot } from 'react-dom/client'
import App from './App.tsx' import App from './App.tsx'
import "./index.scss"; import "./index.scss";
import { IndexedDBProvider } from './contexts/indexedDBContext.tsx'; import { IndexedDBProvider } from './contexts/indexedDBContext.tsx';
@ -11,7 +11,7 @@ interface CustomWindow extends Window {
const customWindow = window as unknown as CustomWindow; const customWindow = window as unknown as CustomWindow;
const baseUrl = customWindow?._qdnBase || ""; const baseUrl = customWindow?._qdnBase || "";
ReactDOM.createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<BrowserRouter basename={baseUrl}> <BrowserRouter basename={baseUrl}>
<IndexedDBProvider> <IndexedDBProvider>
<App /> <App />

View File

@ -24,6 +24,8 @@ export const HomePage = () => {
} = useContext(gameContext); } = useContext(gameContext);
const { setNotification } = useContext(NotificationContext); const { setNotification } = useContext(NotificationContext);
const [mode, setMode] = useState("buy"); const [mode, setMode] = useState("buy");
const [fee, setFee] = useState("");
const filteredOngoingTrades = useMemo(() => { const filteredOngoingTrades = useMemo(() => {
return onGoingTrades?.filter( return onGoingTrades?.filter(
(item) => item?.tradeInfo?.foreignBlockchain === selectedCoin (item) => item?.tradeInfo?.foreignBlockchain === selectedCoin
@ -50,6 +52,9 @@ export const HomePage = () => {
<Header <Header
qortBalance={qortBalance} qortBalance={qortBalance}
foreignCoinBalance={foreignCoinBalance} foreignCoinBalance={foreignCoinBalance}
qortAddress={userInfo?.address}
fee={fee}
setFee={setFee}
/> />
<AppContainer> <AppContainer>
@ -108,7 +113,8 @@ export const HomePage = () => {
</TextTableTitle> </TextTableTitle>
</Box> </Box>
<Spacer height="10px" /> <Spacer height="10px" />
<TradeOffers foreignCoinBalance={foreignCoinBalance} /> <TradeOffers fee={fee}
setFee={setFee} foreignCoinBalance={foreignCoinBalance} />
</div> </div>
<CreateSell show={mode === "sell"} qortAddress={userInfo?.address} /> <CreateSell show={mode === "sell"} qortAddress={userInfo?.address} />

View File

@ -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<Socket<DefaultEventsMap, DefaultEventsMap>> {
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();