added stuck sell order table

This commit is contained in:
PhilReact 2025-05-09 13:50:31 +03:00
parent e0ab3846d7
commit 733c49e33c
9 changed files with 454 additions and 216 deletions

10
package-lock.json generated
View File

@ -28,6 +28,7 @@
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
"react-router-dom": "^6.23.0", "react-router-dom": "^6.23.0",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
"react-virtuoso": "^4.12.7",
"sass": "^1.76.0", "sass": "^1.76.0",
"short-unique-id": "^5.2.0", "short-unique-id": "^5.2.0",
"socket.io-client": "^4.7.5" "socket.io-client": "^4.7.5"
@ -6846,6 +6847,15 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/react-virtuoso": {
"version": "4.12.7",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.12.7.tgz",
"integrity": "sha512-njJp764he6Fi1p89PUW0k2kbyWu9w/y+MwdxmwK2kvdwwzVDbz2c2wMj5xdSruBFVgFTsI7Z85hxZR7aSHBrbQ==",
"peerDependencies": {
"react": ">=16 || >=17 || >= 18 || >= 19",
"react-dom": ">=16 || >=17 || >= 18 || >=19"
}
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",

View File

@ -30,6 +30,7 @@
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
"react-router-dom": "^6.23.0", "react-router-dom": "^6.23.0",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
"react-virtuoso": "^4.12.7",
"sass": "^1.76.0", "sass": "^1.76.0",
"short-unique-id": "^5.2.0", "short-unique-id": "^5.2.0",
"socket.io-client": "^4.7.5" "socket.io-client": "^4.7.5"

View File

@ -56,12 +56,15 @@ import {
export const baseLocalHost = window.location.host; export const baseLocalHost = window.location.host;
// export const baseLocalHost = "devnet-nodes.qortal.link:11111"; // export const baseLocalHost = "devnet-nodes.qortal.link:11111";
// export const baseLocalHost = "127.0.0.1:12391";
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";
import moment from "moment"; import moment from "moment";
import { RequestQueueWithPromise } from "qapp-core"; import { RequestQueueWithPromise } from "qapp-core";
import { useUpdateFee } from "../../hooks/useUpdateFee"; import { useUpdateFee } from "../../hooks/useUpdateFee";
import { useSetAtom } from "jotai/react";
import { stuckTradesAtom } from "../../global/state";
const copyToClipboard = (text: string) => { const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
@ -97,6 +100,7 @@ export const TradeOffers: React.FC<any> = ({
const [offers, setOffers] = useState<any[]>([]); const [offers, setOffers] = useState<any[]>([]);
const [signedUnlockingFees, setSignedUnlockingFees] = useState(null); const [signedUnlockingFees, setSignedUnlockingFees] = useState(null);
const [qortalNames, setQortalNames] = useState({}); const [qortalNames, setQortalNames] = useState({});
const setStuckTrades = useSetAtom(stuckTradesAtom)
const { const {
fetchOngoingTransactions, fetchOngoingTransactions,
onGoingTrades, onGoingTrades,
@ -448,6 +452,10 @@ export const TradeOffers: React.FC<any> = ({
return offeringTrade.tradePresenceExpiry > Date.now(); return offeringTrade.tradePresenceExpiry > Date.now();
}; };
const filterForStuckDrades = (offeringTrade: any) => {
return (!offeringTrade?.tradePresenceExpiry || offeringTrade.tradePresenceExpiry < Date.now());
};
const startOfferPresenceMapping = async () => { const startOfferPresenceMapping = async () => {
if (tradePresenceTxns.current) { if (tradePresenceTxns.current) {
for (const tradePresence of tradePresenceTxns.current) { for (const tradePresence of tradePresenceTxns.current) {
@ -467,6 +475,11 @@ export const TradeOffers: React.FC<any> = ({
offeringTrades.current?.filter((offeringTrade) => offeringTrades.current?.filter((offeringTrade) =>
filterOffersUsingTradePresence(offeringTrade) filterOffersUsingTradePresence(offeringTrade)
) || []; ) || [];
const stuckTrades = offeringTrades.current?.filter((offeringTrade) =>
filterForStuckDrades(offeringTrade)
) || [];
setStuckTrades(stuckTrades?.sort((a, b) => b.timestamp - a.timestamp))
let tradesPresenceCleaned: any[] = filteredOffers; let tradesPresenceCleaned: any[] = filteredOffers;
blockedTradesList.current.forEach((item: any) => { blockedTradesList.current.forEach((item: any) => {
@ -549,10 +562,10 @@ export const TradeOffers: React.FC<any> = ({
socketRef.current.onmessage = (e) => { socketRef.current.onmessage = (e) => {
offeringTrades.current = [ offeringTrades.current = [
...offeringTrades.current?.filter( ...offeringTrades.current?.filter(
(coin) => coin?.foreignBlockchain === selectedCoin (coin) => coin?.foreignBlockchain === selectedCoin && coin?.mode === 'OFFERING'
), ),
...JSON.parse(e.data)?.filter( ...JSON.parse(e.data)?.filter(
(coin) => coin?.foreignBlockchain === selectedCoin (coin) => coin?.foreignBlockchain === selectedCoin && coin?.mode === 'OFFERING'
), ),
]; ];
restarted = false; restarted = false;

View File

@ -69,6 +69,8 @@ import UnsignedFees from "../sell/UnsignedFees";
import { FeeManager } from "../sell/FeeManager"; import { FeeManager } from "../sell/FeeManager";
import { Info } from "../sell/Info"; import { Info } from "../sell/Info";
import { Settings } from "../sell/Settings"; import { Settings } from "../sell/Settings";
import { useSetAtom } from "jotai/react";
import { stuckTradesAtom } from "../../global/state";
const checkIfLocal = async () => { const checkIfLocal = async () => {
try { try {
@ -171,6 +173,8 @@ export const Header = ({
const [amount, setAmount] = useState<string>(""); const [amount, setAmount] = useState<string>("");
const [coinAddresses, setCoinAddresses] = useState({}); const [coinAddresses, setCoinAddresses] = useState({});
const { isUsingGateway } = useContext(gameContext); const { isUsingGateway } = useContext(gameContext);
const setStuckTrades = useSetAtom(stuckTradesAtom)
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setChecked(false); setChecked(false);
@ -492,6 +496,7 @@ export const Header = ({
onChange={(e) => { onChange={(e) => {
setFee(null) setFee(null)
setSelectedCoin(e.target.value) setSelectedCoin(e.target.value)
setStuckTrades([])
}} }}
> >
<MenuItem value={"LITECOIN"}> <MenuItem value={"LITECOIN"}>

View File

@ -4,6 +4,7 @@ import {
Button, Button,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText,
DialogTitle, DialogTitle,
IconButton, IconButton,
InputLabel, InputLabel,
@ -13,12 +14,16 @@ import {
Typography, Typography,
styled, styled,
} from "@mui/material"; } from "@mui/material";
import React, { useContext } from "react"; import React, { useContext, useEffect, useState } from "react";
import { BootstrapDialog } from "../Terms"; import { BootstrapDialog } from "../Terms";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { Spacer } from "../common/Spacer"; import { Spacer } from "../common/Spacer";
import gameContext from "../../contexts/gameContext"; import gameContext from "../../contexts/gameContext";
import TradeBotList from "./TradeBotList"; import TradeBotList from "./TradeBotList";
import { stuckTradesAtom } from "../../global/state";
import { useAtom } from "jotai/react";
import { StuckOrdersTable } from "./StuckOrdersTable";
import { useGlobal } from "qapp-core";
export const CustomLabel = styled(InputLabel)` export const CustomLabel = styled(InputLabel)`
font-weight: 400; font-weight: 400;
@ -97,6 +102,7 @@ export const CustomInput = styled(TextField)({
export const CreateSell = ({ qortAddress, show }) => { export const CreateSell = ({ qortAddress, show }) => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [openStuckOrders, setOpenStuckOrders] = React.useState(false);
const [qortAmount, setQortAmount] = React.useState(0); const [qortAmount, setQortAmount] = React.useState(0);
const [foreignAmount, setForeignAmount] = React.useState(0); const [foreignAmount, setForeignAmount] = React.useState(0);
const { const {
@ -220,7 +226,21 @@ export const CreateSell = ({ qortAddress, show }) => {
display: show ? "block" : "none", display: show ? "block" : "none",
}} }}
> >
<Button onClick={handleClickOpen}>New Sell Order</Button> <Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<Button sx={{
margin: '10px 0px'
}} variant="outlined" onClick={handleClickOpen}>New Sell Order</Button>
{!isUsingGateway && (
<Button sx={{
margin: '10px 0px'
}} variant="outlined" onClick={()=> setOpenStuckOrders(true)}>Stuck orders</Button>
)}
</Box>
<TradeBotList <TradeBotList
qortAddress={qortAddress} qortAddress={qortAddress}
failedTradeBots={sellOrders.filter((item) => item.status === "FAILED")} failedTradeBots={sellOrders.filter((item) => item.status === "FAILED")}
@ -325,6 +345,55 @@ export const CreateSell = ({ qortAddress, show }) => {
{info?.message} {info?.message}
</Alert> </Alert>
</Snackbar> </Snackbar>
{openStuckOrders && (
<StuckOrders setOpenStuckOrders={setOpenStuckOrders} />
)}
</div> </div>
); );
}; };
const StuckOrders = ({setOpenStuckOrders})=> {
const [stuckTrades] = useAtom(stuckTradesAtom)
const address = useGlobal().auth.address
const filteredByAddress = stuckTrades?.filter((item)=> item?.qortalCreator === address)
return (
<BootstrapDialog
aria-labelledby="customized-dialog-title"
open={true}
maxWidth="lg"
fullWidth={true}
>
<DialogTitle sx={{ m: 0, p: 2 }} id="customized-dialog-title">
Stuck sell orders
</DialogTitle>
<IconButton
aria-label="close"
onClick={()=> setOpenStuckOrders(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
color: theme.palette.grey[500],
})}
>
<CloseIcon />
</IconButton>
<DialogContent dividers>
<DialogContentText></DialogContentText>
<Spacer height="20px" />
{filteredByAddress?.length === 0 && (
<DialogContentText>No stuck trades</DialogContentText>
)}
<Spacer height="20px" />
<StuckOrdersTable data={filteredByAddress} />
</DialogContent>
<DialogActions>
<Button autoFocus onClick={()=> setOpenStuckOrders(false)}>
Close
</Button>
</DialogActions>
</BootstrapDialog>
)
}

View File

@ -83,7 +83,10 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => {
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const [recommendedFee, setRecommendedFee] = useState("medium_fee_per_kb"); const [recommendedFee, setRecommendedFee] = useState("medium_fee_per_kb");
const {hideRecommendations, recommendedFeeDisplay} = useRecommendedFees({selectedCoin, recommendedFee}) const { hideRecommendations, recommendedFeeDisplay } = useRecommendedFees({
selectedCoin,
recommendedFee,
});
const [openAlert, setOpenAlert] = useState(false); const [openAlert, setOpenAlert] = useState(false);
const [info, setInfo] = useState<any>(null); const [info, setInfo] = useState<any>(null);
@ -143,10 +146,6 @@ const {hideRecommendations, recommendedFeeDisplay} = useRecommendedFees({selecte
establishUpdateFeeForm(coin); establishUpdateFeeForm(coin);
}, [coin, establishUpdateFeeForm]); }, [coin, establishUpdateFeeForm]);
useEffect(() => { useEffect(() => {
if (hideRecommendations) { if (hideRecommendations) {
setRecommendedFee("custom"); setRecommendedFee("custom");
@ -158,13 +157,12 @@ const {hideRecommendations, recommendedFeeDisplay} = useRecommendedFees({selecte
const typeRequestLocking = "feekb"; const typeRequestLocking = "feekb";
try { try {
let feeToSave = editFee; let feeToSave = editFee;
if (recommendedFee !== "custom") { if (recommendedFee !== "custom") {
feeToSave = calculateFeeFromRate(recommendedFeeDisplay, 300); feeToSave = calculateFeeFromRate(recommendedFeeDisplay, 300);
} }
if (+fee === +feeToSave) { if (+fee === +feeToSave) {
return return;
} }
const response = await qortalRequestWithTimeout( const response = await qortalRequestWithTimeout(
{ {
@ -215,8 +213,6 @@ const {hideRecommendations, recommendedFeeDisplay} = useRecommendedFees({selecte
} }
}; };
if (fee === null || fee === undefined) return; if (fee === null || fee === undefined) return;
return ( return (
<> <>
@ -274,6 +270,18 @@ const {hideRecommendations, recommendedFeeDisplay} = useRecommendedFees({selecte
</Typography> </Typography>
</HeaderRow> </HeaderRow>
</CoinActionRow> </CoinActionRow>
<CoinActionRow>
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography>current fee: {fee}</Typography>
</Box>
</CoinActionRow>
<CoinActionRow> <CoinActionRow>
<HeaderRow> <HeaderRow>
<Box <Box

View File

@ -52,10 +52,13 @@ export const Settings = () => {
const [lockingFee, setLockingFee] = useState(""); const [lockingFee, setLockingFee] = useState("");
const [recommendedFee, setRecommendedFee] = useState("medium_fee_per_kb"); const [recommendedFee, setRecommendedFee] = useState("medium_fee_per_kb");
const [selectedCoin, setSelectedCoin] = useState("LITECOIN"); const [selectedCoin, setSelectedCoin] = useState("LITECOIN");
const {
const { hideRecommendations, recommendedFeeDisplay, coin } = useRecommendedFees({ isUsingGateway
} = useContext(gameContext);
const { hideRecommendations, recommendedFeeDisplay, coin } =
useRecommendedFees({
selectedCoin, selectedCoin,
recommendedFee recommendedFee,
}); });
const [editLockingFee, setEditLockingFee] = useState(""); const [editLockingFee, setEditLockingFee] = useState("");
@ -85,7 +88,7 @@ export const Settings = () => {
try { try {
let feeToSave = editLockingFee; let feeToSave = editLockingFee;
if (recommendedFee !== "custom") { if (recommendedFee !== "custom") {
feeToSave = recommendedFeeDisplay feeToSave = recommendedFeeDisplay;
} }
const response = await qortalRequestWithTimeout( const response = await qortalRequestWithTimeout(
{ {
@ -218,6 +221,7 @@ export const Settings = () => {
}} }}
open={openModal} open={openModal}
> >
{!isUsingGateway && (
<CoinActionContainer <CoinActionContainer
sx={{ sx={{
border: "1px solid #3F3F3F", border: "1px solid #3F3F3F",
@ -241,8 +245,8 @@ export const Settings = () => {
<CoinSelectRow <CoinSelectRow
sx={{ sx={{
gap: "20px", gap: "20px",
width: '100%', width: "100%",
justifyContent: 'center' justifyContent: "center",
}} }}
> >
<Select <Select
@ -273,8 +277,17 @@ export const Settings = () => {
<SelectRow coin="ARRR" /> <SelectRow coin="ARRR" />
</MenuItem> </MenuItem>
</Select> </Select>
<Box> </CoinSelectRow>
<CoinSelectRow>
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography>current fee: {lockingFee}</Typography>
</Box> </Box>
</CoinSelectRow> </CoinSelectRow>
<CoinActionRow> <CoinActionRow>
@ -350,8 +363,7 @@ export const Settings = () => {
{" "} {" "}
New fee: New fee:
</span>{" "} </span>{" "}
{recommendedFeeDisplay}{" "} {recommendedFeeDisplay} sats per kb
sats per kb
</Typography> </Typography>
</Box> </Box>
@ -383,7 +395,6 @@ export const Settings = () => {
</> </>
)} )}
<ButtonBase <ButtonBase
onClick={updateLockingFee} onClick={updateLockingFee}
disabled={recommendedFee === "custom" && !editLockingFee} disabled={recommendedFee === "custom" && !editLockingFee}
@ -409,6 +420,7 @@ export const Settings = () => {
<Typography>Update locking fee</Typography> <Typography>Update locking fee</Typography>
</ButtonBase> </ButtonBase>
</CoinActionContainer> </CoinActionContainer>
)}
<Spacer height="20px" /> <Spacer height="20px" />
<CoinActionContainer <CoinActionContainer
sx={{ sx={{

View File

@ -0,0 +1,118 @@
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Button,
} from "@mui/material";
import { forwardRef } from "react";
import { TableVirtuoso, TableComponents } from "react-virtuoso";
import {
dismissToast,
showError,
showLoading,
showSuccess,
useGlobal,
} from "qapp-core";
import { formatTimestampForum } from "../../utils/formatTime";
interface NameData {
name: string;
isSelling?: boolean;
}
const VirtuosoTableComponents: TableComponents<NameData> = {
Scroller: forwardRef<HTMLDivElement>((props, ref) => (
<TableContainer component={Paper} {...props} ref={ref} />
)),
Table: (props) => (
<Table
{...props}
sx={{ borderCollapse: "separate", tableLayout: "fixed" }}
/>
),
TableHead: forwardRef<HTMLTableSectionElement>((props, ref) => (
<TableHead {...props} ref={ref} />
)),
TableRow,
TableBody: forwardRef<HTMLTableSectionElement>((props, ref) => (
<TableBody {...props} ref={ref} />
)),
};
function fixedHeaderContent() {
return (
<TableRow sx={{ backgroundColor: "background.paper" }}>
<TableCell>Date</TableCell>
<TableCell>Amount (QORT)</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
);
}
function rowContent(_index: number, row: NameData) {
const cancelSell = async (name: string) => {
const loadId = showLoading("Attempting to cancel sell...please wait");
try {
const res = await qortalRequestWithTimeout(
{
action: "CANCEL_TRADE_SELL_ORDER",
qortAmount: row.qortAmount,
foreignBlockchain: row.foreignBlockchain,
foreignAmount: row.foreignAmount,
atAddress: row.qortalAtAddress,
},
900000
);
if (res?.signature) {
showSuccess(
"Canceled sell order. It might take awhile for the cancel sell order changes to show up."
);
}
} catch (error) {
showError(error?.message || "Unable to cancel sell order");
console.log("error", error);
} finally {
dismissToast(loadId);
}
};
return (
<>
<TableCell>{formatTimestampForum(row?.timestamp)}</TableCell>
<TableCell>{row?.qortAmount}</TableCell>
<TableCell>
<Button
variant="contained"
size="small"
onClick={() => cancelSell(row.name)}
>
Cancel sell
</Button>
</TableCell>
</>
);
}
export const StuckOrdersTable = ({ data }) => {
return (
<Paper
sx={{
height: "80vh", // Header + footer height
width: "100%",
}}
>
<TableVirtuoso
data={data}
components={VirtuosoTableComponents}
fixedHeaderContent={fixedHeaderContent}
itemContent={(index, row) => rowContent(index, row)}
/>
</Paper>
);
};

View File

@ -5,3 +5,5 @@ import { atomWithReset } from 'jotai/utils';
export const selectedFeePublisherAtom = atomWithReset('Ice.JSON'); export const selectedFeePublisherAtom = atomWithReset('Ice.JSON');
export const isEnabledCustomLockingFeeAtom = atomWithReset(false); export const isEnabledCustomLockingFeeAtom = atomWithReset(false);
export const stuckTradesAtom = atomWithReset([]);