From f32cd685541fd38ee5eb8f6da1d50f2a1d42952d Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 4 May 2025 20:31:43 +0300 Subject: [PATCH] fixes --- package-lock.json | 59 ++++-- package.json | 2 +- src/components/Grids/TradeOffers.tsx | 126 +++++++++++- src/components/sell/FeeManager.tsx | 269 +++++++++++++------------- src/components/sell/Settings.tsx | 278 ++++++++++++++++++++------- src/hooks/useRecommendedFees.tsx | 76 ++++++++ src/hooks/useUpdateFee.tsx | 61 ++++++ src/pages/Home/Home.tsx | 4 +- 8 files changed, 650 insertions(+), 225 deletions(-) create mode 100644 src/hooks/useRecommendedFees.tsx create mode 100644 src/hooks/useUpdateFee.tsx diff --git a/package-lock.json b/package-lock.json index 44f6d18..17c4332 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "jotai": "^2.12.3", "lodash": "^4.17.21", "moment": "^2.30.1", - "qapp-core": "^1.0.24", + "qapp-core": "^1.0.25", "react": "^19.1.0", "react-countdown-circle-timer": "^3.2.1", "react-dom": "^19.1.0", @@ -3128,6 +3128,7 @@ "version": "3.13.6", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", + "license": "MIT", "dependencies": { "@tanstack/virtual-core": "3.13.6" }, @@ -3144,6 +3145,7 @@ "version": "3.13.6", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -3254,7 +3256,8 @@ "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==" + "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==", + "license": "MIT" }, "node_modules/@types/semver": { "version": "7.5.8", @@ -3661,6 +3664,7 @@ "version": "2.2.5", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -3762,6 +3766,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -3783,7 +3788,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -3800,6 +3806,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/bloom-filters/-/bloom-filters-3.0.4.tgz", "integrity": "sha512-BdnPWo2OpYhlvuP2fRzJBdioMCkm7Zp0HCf8NJgF5Mbyqy7VQ/CnTiVWMMyq4EZCBHwj0Kq6098gW2/3RsZsrA==", + "license": "MIT", "dependencies": { "@types/seedrandom": "^3.0.8", "base64-arraybuffer": "^1.0.2", @@ -3817,7 +3824,8 @@ "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==" + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "2.0.1", @@ -3889,6 +3897,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -4066,6 +4075,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz", "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==", + "license": "MIT", "dependencies": { "blueimp-canvas-to-blob": "^3.29.0", "is-blob": "^2.1.0" @@ -4128,7 +4138,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==" + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" }, "node_modules/crypto-random-string": { "version": "2.0.0", @@ -4167,7 +4178,8 @@ "node_modules/cuint": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==" + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "license": "MIT" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -4223,7 +4235,8 @@ "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" }, "node_modules/debug": { "version": "4.3.7", @@ -4342,6 +4355,7 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -4944,6 +4958,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", "dependencies": { "tslib": "^2.7.0" }, @@ -4954,7 +4969,8 @@ "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==" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/filelist": { "version": "1.0.4", @@ -5292,6 +5308,7 @@ "version": "2.1.16", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", "peerDependencies": { "csstype": "^3.0.10" } @@ -5435,7 +5452,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.1", @@ -5553,6 +5571,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", + "license": "MIT", "engines": { "node": ">=6" }, @@ -5594,6 +5613,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "engines": { "node": ">=4" } @@ -6162,7 +6182,8 @@ "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==" + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -6581,9 +6602,9 @@ } }, "node_modules/qapp-core": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.24.tgz", - "integrity": "sha512-KnrwiysaHlTR1rUnPGwN79gQgcjvtLr4xRH3EGGQcbXKCcbrklV7lv4KLUfFR4UwhskbgOXpJY5PECWdrlGXSw==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.25.tgz", + "integrity": "sha512-JhdQliLPjapgzbCjkuSpR5hAk8ewT0FbQxIW+oxVWJyT1e0swAKXJqvX1PHQzTbiV5IQI12nbIxy6SjBam3K2A==", "license": "MIT", "dependencies": { "@tanstack/react-virtual": "^3.13.2", @@ -6675,6 +6696,7 @@ "version": "14.3.8", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "license": "MIT", "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", @@ -6696,6 +6718,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" @@ -6712,6 +6735,7 @@ "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==", + "license": "MIT", "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6836,7 +6860,8 @@ "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==" + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0" }, "node_modules/regenerate": { "version": "1.4.2", @@ -7122,7 +7147,8 @@ "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==" + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" }, "node_modules/semver": { "version": "7.6.0", @@ -7906,6 +7932,7 @@ "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==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -8434,6 +8461,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "license": "MIT", "dependencies": { "cuint": "^0.2.2" } @@ -8468,6 +8496,7 @@ "version": "4.5.6", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "license": "MIT", "dependencies": { "use-sync-external-store": "^1.2.2" }, diff --git a/package.json b/package.json index e4d50e6..5fc424f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "jotai": "^2.12.3", "lodash": "^4.17.21", "moment": "^2.30.1", - "qapp-core": "^1.0.24", + "qapp-core": "^1.0.25", "react": "^19.1.0", "react-countdown-circle-timer": "^3.2.1", "react-dom": "^19.1.0", diff --git a/src/components/Grids/TradeOffers.tsx b/src/components/Grids/TradeOffers.tsx index c540a05..961e2c8 100644 --- a/src/components/Grids/TradeOffers.tsx +++ b/src/components/Grids/TradeOffers.tsx @@ -61,6 +61,7 @@ import CloseIcon from "@mui/icons-material/Close"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import moment from "moment"; import { RequestQueueWithPromise } from "qapp-core"; +import { useUpdateFee } from "../../hooks/useUpdateFee"; const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text); @@ -91,6 +92,7 @@ export const autoSizeStrategy: SizeColumnsToContentStrategy = { export const TradeOffers: React.FC = ({ foreignCoinBalance, fee, + setFee }: any) => { const [offers, setOffers] = useState([]); const [signedUnlockingFees, setSignedUnlockingFees] = useState(null); @@ -105,6 +107,7 @@ export const TradeOffers: React.FC = ({ } = useContext(gameContext); const isRemoveOrdersWithoutUnlockingFees = useRef(false); const [isRemoveOrders, setIsRemoveOrders] = useState('remove'); + const updateFee = useUpdateFee({setFee, selectedCoin}) const listOfOngoingTradesAts = useMemo(() => { return ( onGoingTrades @@ -128,6 +131,14 @@ export const TradeOffers: React.FC = ({ message: messageTradesUnknownFee, } = useModal(); + const { + isShow: isShowAskToUpdateFee, + onCancel: onCancelAskToUpdateFee, + onOk: onOkAskToUpdateFee, + show: showAskToUpdateFee, + message: messageAskToUpdateFee, + } = useModal(); + const offersWithoutOngoing = useMemo(() => { return offers.filter( (item) => !listOfOngoingTradesAts.includes(item.qortalAtAddress) @@ -228,6 +239,20 @@ export const TradeOffers: React.FC = ({ } }; + const rowTooltip = (params) => { + let selectable = true; + const hasSignedFee = signedUnlockingFees?.find( + (item) => item?.atAddress === params.data.qortalAtAddress + ); + if (!hasSignedFee) selectable = true; + + if (hasSignedFee && hasSignedFee?.fee > feeRef.current) + selectable = false; + if(!selectable)return 'Your unlocking fee is to low to buy this order, Increase your fee to purchase' + + return '' + }; + const columnDefs: ColDef[] = useMemo(() => { return [ { @@ -239,7 +264,7 @@ export const TradeOffers: React.FC = ({ pinned: "left", // Optional, to pin this column on the left resizable: false, suppressRowClickSelection: true, - + tooltipValueGetter: rowTooltip, cellRenderer: (params) => ( = ({ flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, + tooltipValueGetter: rowTooltip }, { headerName: `${getCoinLabel()}/QORT`, @@ -275,6 +301,7 @@ export const TradeOffers: React.FC = ({ flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, + tooltipValueGetter: rowTooltip }, { headerName: `Total ${getCoinLabel()} Value`, @@ -282,12 +309,14 @@ export const TradeOffers: React.FC = ({ flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, + tooltipValueGetter: rowTooltip }, { headerName: `Unlocking fee`, flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, + tooltipValueGetter: rowTooltip, valueGetter: (params) => { if (params?.data?.qortalAtAddress) { const hasSignedFee = signedUnlockingFees?.find( @@ -304,6 +333,7 @@ export const TradeOffers: React.FC = ({ flex: 1, // Flex makes this column responsive minWidth: 300, // Ensure it doesn't shrink too much resizable: true, + tooltipValueGetter: rowTooltip, valueGetter: (params) => { if (params?.data?.qortalCreator) { if (qortalNames[params?.data?.qortalCreator]) { @@ -660,6 +690,20 @@ export const TradeOffers: React.FC = ({ offersWithKnownFees = selectedOffers; } + const highestFee = offersWithKnownFees.length + ? Math.max( + ...offersWithKnownFees + .filter(o => typeof o.fee === 'number') + .map(o => o.fee as number) + ) + : 0; + if(highestFee < fee){ + + await showAskToUpdateFee({ + message: highestFee + }) + } + setIsShowBuyInProgress({ status: "buying" }); @@ -864,6 +908,7 @@ export const TradeOffers: React.FC = ({ onGridReady={onGridReady} // domLayout='autoHeight' getRowId={(params) => params.data.qortalAtAddress} // Ensure rows have unique IDs + enableBrowserTooltips={true} gridOptions={{ isRowSelectable: (params) => { let selectable = true; @@ -1001,7 +1046,7 @@ export const TradeOffers: React.FC = ({ - @@ -1012,9 +1057,19 @@ export const TradeOffers: React.FC = ({ open={isShowTradesUnknownFee} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" + PaperProps={{ + style: { + backgroundColor: "rgb(39, 40, 44)", + background: "rgb(39, 40, 44)", + }, + }} > - Warning - + Warning + = ({ - + + + + + )} {isShowBuyInProgress && ( { @@ -68,12 +72,19 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { service: "JSON", }); const { resource } = usePublish(3, "JSON", feeLocation); - const [selectedFeePublisher, setSelectedFeePublisher] = useAtom(selectedFeePublisherAtom) - const [isEnabledCustomLockingFee, setIsEnabledCustomLockingFee] = useAtom(isEnabledCustomLockingFeeAtom) - + const [selectedFeePublisher, setSelectedFeePublisher] = useAtom( + selectedFeePublisherAtom + ); + const [isEnabledCustomLockingFee, setIsEnabledCustomLockingFee] = useAtom( + isEnabledCustomLockingFeeAtom + ); + const [editFee, setEditFee] = useState(""); const [openModal, setOpenModal] = useState(false); - const [recommendedFee, setRecommendedFee] = useState("m"); + const [recommendedFee, setRecommendedFee] = useState("medium_fee_per_kb"); + +const {hideRecommendations, recommendedFeeDisplay} = useRecommendedFees({selectedCoin, recommendedFee}) + const [openAlert, setOpenAlert] = useState(false); const [info, setInfo] = useState(null); const { getCoinLabel } = useContext(gameContext); @@ -89,16 +100,18 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { setInfo(null); }; const coin = useMemo(() => { - const coinLabel = getCoinLabel(selectedCoin) - if(typeof coinLabel !== 'string') return null + const coinLabel = getCoinLabel(selectedCoin); + if (typeof coinLabel !== "string") return null; return coinLabel?.toLowerCase(); }, [selectedCoin, getCoinLabel]); - - const feeTimestamp = useMemo(()=> { - if(!resource?.qortalMetadata?.identifier?.includes(`${coin.toUpperCase()}`)) return - return resource?.data?.time || null - }, [resource, coin]) + const feeTimestamp = useMemo(() => { + if ( + !resource?.qortalMetadata?.identifier?.includes(`${coin.toUpperCase()}`) + ) + return; + return resource?.data?.time || null; + }, [resource, coin]); const establishUpdateFeeForm = useCallback(async (coin) => { setFee(""); // if the coin or type is not set, then abort @@ -117,10 +130,9 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { }, 1800000 ); - if ((response !== null && response !== undefined) && !isNaN(+response)) { + if (response !== null && response !== undefined && !isNaN(+response)) { setFee(response); } - } catch (error) { setFee(""); console.error(error); @@ -131,42 +143,29 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { establishUpdateFeeForm(coin); }, [coin, establishUpdateFeeForm]); - const recommendedFeeData = useMemo(() => { - if(!resource?.qortalMetadata?.identifier?.includes(`${coin.toUpperCase()}`)) return - if (!resource?.data) return null; - const isValid = isValidFeeEstimate(resource.data) - if(!isValid) return null - return resource.data; - }, [resource, coin]); - - const recommendedFeeDisplay = useMemo(() => { - if (!recommendedFeeData) return null; - - if(!recommendedFeeData) return null - return recommendedFeeData[recommendedFee] || null; - }, [recommendedFeeData, recommendedFee]); - - const hideRecommendations = useMemo(()=> { - if(recommendedFeeData) return false - return true - }, [recommendedFeeData]) - useEffect(()=> { - if(hideRecommendations){ - setRecommendedFee('custom') + + + useEffect(() => { + if (hideRecommendations) { + setRecommendedFee("custom"); } - }, [hideRecommendations]) + }, [hideRecommendations]); const updateFee = async () => { const typeRequest = "feerequired"; const typeRequestLocking = "feekb"; try { - let feeToSave = editFee - if(recommendedFee !== 'custom'){ - feeToSave = calculateFeeFromRate(recommendedFeeDisplay, 300) - } + + let feeToSave = editFee; + if (recommendedFee !== "custom") { + feeToSave = calculateFeeFromRate(recommendedFeeDisplay, 300); + } + if(+fee === +feeToSave){ + return + } const response = await qortalRequestWithTimeout( { action: "UPDATE_FOREIGN_FEE", @@ -177,8 +176,8 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { 1800000 ); - if(!isEnabledCustomLockingFee){ - await qortalRequestWithTimeout( + if (!isEnabledCustomLockingFee) { + await qortalRequestWithTimeout( { action: "UPDATE_FOREIGN_FEE", coin: coin, @@ -187,7 +186,6 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { }, 1800000 ); - } if (response && !isNaN(+response)) { @@ -212,34 +210,11 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { event: React.MouseEvent, newAlignment: string ) => { - if(newAlignment){ - setRecommendedFee(newAlignment); + if (newAlignment) { + setRecommendedFee(newAlignment); } }; - - - const getLatestFees = useCallback(async () => { - try { - const coinLabel = getCoinLabel(selectedCoin) - if(typeof coinLabel !== 'string') return - const coin = coinLabel?.toUpperCase(); - const identifier = `coinInfo-${coin}` - const res = await fetch( - `/arbitrary/resources/searchsimple?service=JSON&identifier=${identifier}&name=${selectedFeePublisher}&prefix=true&limit=1&reverse=true` - ); - const data = await res.json(); - if (data && data?.length > 0) { - setFeeLocation(data[0]); - } - } catch (error) { - console.error(error) - } - }, [selectedFeePublisher, selectedCoin]); - - useEffect(() => { - getLatestFees(); - }, [getLatestFees]); if (fee === null || fee === undefined) return; @@ -281,12 +256,12 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { open={openModal} backdrop styles={{ - width: '450px', - maxWidth: '95vw', - padding: '15px' + width: "450px", + maxWidth: "95vw", + padding: "15px", }} > - + { - - - - Recommended fee selection (in sats) - - - - + - {!hideRecommendations && ( + + Recommended fee selection (in sats) + + + + + {!hideRecommendations && ( <> - Low - Medium - High + + Low + + + Medium + + + High + - )} - - Custom - + )} + + Custom + {recommendedFeeDisplay && ( <> - - - New fee:{" "} - {calculateFeeFromRate(recommendedFeeDisplay, 300)} sats - + + + {" "} + New fee: + {" "} + {calculateFeeFromRate( + recommendedFeeDisplay, + 300 + )}{" "} + sats + { > This recommended fee is derived from{" "} {recommendedFeeDisplay} per kb, for a transaction that - is approximately 300 kB in size. + is approximately 300 bytes in size. - - + )} @@ -408,7 +406,7 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { { Update fee {!hideRecommendations && feeTimestamp && ( - *Recommended fees last updated: {formatTimestampForum(feeTimestamp)} - )} + + *Recommended fees last updated:{" "} + {formatTimestampForum(feeTimestamp)} + + )} )} diff --git a/src/components/sell/Settings.tsx b/src/components/sell/Settings.tsx index c68a55b..8cf6be7 100644 --- a/src/components/sell/Settings.tsx +++ b/src/components/sell/Settings.tsx @@ -39,20 +39,34 @@ import { SetLeftFeature } from "ag-grid-community"; import { formatTimestampForum } from "../../utils/formatTime"; import { SelectRow } from "../header/Header"; import { useAtom } from "jotai/react"; -import { isEnabledCustomLockingFeeAtom, selectedFeePublisherAtom } from "../../global/state"; +import { + isEnabledCustomLockingFeeAtom, + selectedFeePublisherAtom, +} from "../../global/state"; +import { useRecommendedFees } from "../../hooks/useRecommendedFees"; export const Settings = () => { - const saveDataLocal = useGlobal().persistentOperations.saveData - const getDataLocal = useGlobal().persistentOperations.getData + const saveDataLocal = useGlobal().persistentOperations.saveData; + const getDataLocal = useGlobal().persistentOperations.getData; const [openModal, setOpenModal] = useState(false); const [lockingFee, setLockingFee] = useState(""); + const [recommendedFee, setRecommendedFee] = useState("medium_fee_per_kb"); + const [selectedCoin, setSelectedCoin] = useState("LITECOIN"); + + const { hideRecommendations, recommendedFeeDisplay, coin } = useRecommendedFees({ + selectedCoin, + recommendedFee + }); const [editLockingFee, setEditLockingFee] = useState(""); const [openAlert, setOpenAlert] = useState(false); const [info, setInfo] = useState(null); - const [selectedCoin, setSelectedCoin] = useState("LTC"); - const [selectedFeePublisher, setSelectedFeePublisher] = useAtom(selectedFeePublisherAtom) - const [isEnabledCustomLockingFee, setIsEnabledCustomLockingFee] = useAtom(isEnabledCustomLockingFeeAtom) + const [selectedFeePublisher, setSelectedFeePublisher] = useAtom( + selectedFeePublisherAtom + ); + const [isEnabledCustomLockingFee, setIsEnabledCustomLockingFee] = useAtom( + isEnabledCustomLockingFeeAtom + ); const handleCloseAlert = ( event?: React.SyntheticEvent | Event, reason?: SnackbarCloseReason @@ -69,12 +83,14 @@ export const Settings = () => { const typeRequest = "feekb"; try { - const feeToSave = editLockingFee; - + let feeToSave = editLockingFee; + if (recommendedFee !== "custom") { + feeToSave = recommendedFeeDisplay + } const response = await qortalRequestWithTimeout( { action: "UPDATE_FOREIGN_FEE", - coin: selectedCoin, + coin: coin, type: typeRequest, value: feeToSave, }, @@ -102,7 +118,16 @@ export const Settings = () => { const handleChange = (event: React.ChangeEvent) => { setIsEnabledCustomLockingFee(event.target.checked); - saveDataLocal('isEnabledCustomLockingFee', event.target.checked) + saveDataLocal("isEnabledCustomLockingFee", event.target.checked); + }; + + const handleChangeRecommended = ( + event: React.MouseEvent, + newAlignment: string + ) => { + if (newAlignment) { + setRecommendedFee(newAlignment); + } }; const establishUpdateFeeForm = useCallback(async (coin) => { @@ -136,27 +161,33 @@ export const Settings = () => { }, []); useEffect(() => { - if(!openModal) return - establishUpdateFeeForm(selectedCoin); - }, [selectedCoin, establishUpdateFeeForm, openModal]); + if (!openModal) return; + establishUpdateFeeForm(coin); + }, [coin, establishUpdateFeeForm, openModal]); - useEffect(()=> { - const getSavedSelectedPublisher = async ()=> { + useEffect(() => { + const getSavedSelectedPublisher = async () => { try { - const res = await getDataLocal('selectedFeePublisher') - if(res){ - setSelectedFeePublisher(res) + const res = await getDataLocal("selectedFeePublisher"); + if (res) { + setSelectedFeePublisher(res); } - const res2 = await getDataLocal('isEnabledCustomLockingFee') - if(res2){ - setIsEnabledCustomLockingFee(res) + const res2 = await getDataLocal("isEnabledCustomLockingFee"); + if (res2) { + setIsEnabledCustomLockingFee(res); } } catch (error) { - console.error(error) + console.error(error); } - } - getSavedSelectedPublisher() - }, []) + }; + getSavedSelectedPublisher(); + }, []); + + useEffect(() => { + if (hideRecommendations) { + setRecommendedFee("custom"); + } + }, [hideRecommendations]); return ( <> @@ -187,18 +218,33 @@ export const Settings = () => { }} open={openModal} > - + Locking fees - } label="Enable custom locking fee" /> + + } + label="Enable custom locking fee" + /> {isEnabledCustomLockingFee && ( - + <> + + + + + + + + + + Recommended fee selection (in sats per kb) + + + + + {!hideRecommendations && ( + <> + + Low + + + Medium + + + High + + + )} + + Custom + + + {recommendedFeeDisplay && ( + <> + + + + + {" "} + New fee: + {" "} + {recommendedFeeDisplay}{" "} + sats per kb + + + + + + )} + + + + {recommendedFee === "custom" && ( + + + - Locking fee for {selectedCoin} (sats per kb) + Custom fee { autoComplete="off" /> - + + + )} + )} - + { Update locking fee - - + + Fee publisher - + size="small" + value={selectedFeePublisher} + onChange={(e) => { + if (e.target.value) { + setSelectedFeePublisher(e.target.value); + saveDataLocal("selectedFeePublisher", e.target.value); + } + }} + > + + + + + + + + )} { +const { getCoinLabel } = useContext(gameContext); + const [selectedFeePublisher, setSelectedFeePublisher] = useAtom( + selectedFeePublisherAtom + ); + +const [feeLocation, setFeeLocation] = useState({ + name: "", + identifier: "", + service: "JSON", + }); + const { resource } = usePublish(3, "JSON", feeLocation); + + const coin = useMemo(() => { + const coinLabel = getCoinLabel(selectedCoin); + if (typeof coinLabel !== "string") return null; + return coinLabel?.toLowerCase(); + }, [selectedCoin, getCoinLabel]); + + const getLatestFees = useCallback(async () => { + try { + const coinLabel = getCoinLabel(selectedCoin); + if (typeof coinLabel !== "string") return; + const coin = coinLabel?.toUpperCase(); + const identifier = `coinInfo-${coin}`; + const res = await fetch( + `/arbitrary/resources/searchsimple?service=JSON&identifier=${identifier}&name=${selectedFeePublisher}&prefix=true&limit=1&reverse=true` + ); + const data = await res.json(); + if (data && data?.length > 0) { + setFeeLocation(data[0]); + } + } catch (error) { + console.error(error); + } + }, [selectedFeePublisher, selectedCoin]); + + useEffect(() => { + getLatestFees(); + }, [getLatestFees]); + + const recommendedFeeData = useMemo(() => { + if ( + !resource?.qortalMetadata?.identifier?.includes(`${coin.toUpperCase()}`) + ) + return; + if (!resource?.data) return null; + const isValid = isValidFeeEstimate(resource.data); + if (!isValid) return null; + return resource.data; + }, [resource, coin]); + + const recommendedFeeDisplay = useMemo(() => { + if (!recommendedFeeData) return null; + + if (!recommendedFeeData) return null; + return recommendedFeeData[recommendedFee] || null; + }, [recommendedFeeData, recommendedFee]); + + const hideRecommendations = useMemo(() => { + if (recommendedFeeData) return false; + return true; + }, [recommendedFeeData]); + return { + hideRecommendations, + recommendedFeeDisplay, + coin + } +} diff --git a/src/hooks/useUpdateFee.tsx b/src/hooks/useUpdateFee.tsx new file mode 100644 index 0000000..6167be1 --- /dev/null +++ b/src/hooks/useUpdateFee.tsx @@ -0,0 +1,61 @@ +import { useAtom } from 'jotai/react'; +import React, { useCallback, useContext, useMemo } from 'react' +import { calculateRateFromFee } from '../components/sell/FeeManager'; +import { isEnabledCustomLockingFeeAtom } from '../global/state'; +import gameContext from '../contexts/gameContext'; + +export const useUpdateFee = ({setFee, selectedCoin}) => { + const [isEnabledCustomLockingFee, setIsEnabledCustomLockingFee] = useAtom( + isEnabledCustomLockingFeeAtom + ); + + const { + + getCoinLabel, + + } = useContext(gameContext); + + + const coin = useMemo(() => { + const coinLabel = getCoinLabel(selectedCoin); + if (typeof coinLabel !== "string") return null; + return coinLabel?.toLowerCase(); + }, [selectedCoin, getCoinLabel]); + const updateFee = useCallback(async (suggestedFee ) => { + const typeRequest = "feerequired"; + const typeRequestLocking = "feekb"; + + + + const feeToSave = +suggestedFee + + const response = await qortalRequestWithTimeout( + { + action: "UPDATE_FOREIGN_FEE", + coin: coin, + type: typeRequest, + value: feeToSave, + }, + 1800000 + ); + + if (!isEnabledCustomLockingFee) { + await qortalRequestWithTimeout( + { + action: "UPDATE_FOREIGN_FEE", + coin: coin, + type: typeRequestLocking, + value: calculateRateFromFee(feeToSave, 300), + }, + 1800000 + ); + } + + if (response && !isNaN(+response)) { + setFee(response); + return response + } else throw new Error("Unable to update fee"); + + }, [coin, isEnabledCustomLockingFee, setFee]); + return updateFee +} diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 828536f..2c6e42f 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -113,8 +113,8 @@ export const HomePage = () => { - +