import { ColDef } from "ag-grid-community"; import { AgGridReact } from "ag-grid-react"; import React, { useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react"; import { autoSizeStrategy, baseLocalHost } from "../Grids/TradeOffers"; import { Alert, Box, Snackbar, SnackbarCloseReason, Typography, } from "@mui/material"; import gameContext from "../../contexts/gameContext"; import { BuyContainerDivider } from "../Grids/Table-styles"; const defaultColDef = { resizable: true, // Make columns resizable by default sortable: true, // Make columns sortable by default suppressMovable: true, // Prevent columns from being movable }; export default function TradeBotList({ qortAddress, failedTradeBots }) { const [tradeBotList, setTradeBotList] = useState([]); const [selectedTrade, setSelectedTrade] = useState(null); const tradeBotListRef = useRef([]); const offeringTrades = useRef([]); const qortAddressRef = useRef(null); const gridRef = useRef(null); const { updateTemporaryFailedTradeBots, fetchTemporarySellOrders, deleteTemporarySellOrder, getCoinLabel, selectedCoin, } = useContext(gameContext); const [open, setOpen] = useState(false); const [info, setInfo] = useState(null); const filteredOutTradeBotListWithoutFailed = useMemo(() => { const list = tradeBotList.filter( (item) => !failedTradeBots.some( (failedItem) => failedItem.atAddress === item.atAddress ) ); return list; }, [failedTradeBots, tradeBotList]); const onGridReady = useCallback((params: any) => { params.api.sizeColumnsToFit(); // Adjust columns to fit the grid width const allColumnIds = params.columnApi .getAllColumns() .map((col: any) => col.getColId()); params.columnApi.autoSizeColumns(allColumnIds); // Automatically adjust the width to fit content }, []); const columnDefs: ColDef[] = useMemo(() => { return [ { headerCheckboxSelection: false, // Adds a checkbox in the header for selecting all rows checkboxSelection: true, // Adds checkboxes in each row for selection headerName: "Select", // You can customize the header name width: 50, // Adjust the width as needed pinned: "left", // Optional, to pin this column on the left resizable: false, }, { headerName: "QORT AMOUNT", field: "qortAmount", flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, }, { headerName: `${getCoinLabel()}/QORT`, valueGetter: (params) => +params.data.foreignAmount / +params.data.qortAmount, sortable: true, sort: "asc", flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, }, { headerName: `Total ${getCoinLabel()} Value`, field: "foreignAmount", flex: 1, // Flex makes this column responsive minWidth: 150, // Ensure it doesn't shrink too much resizable: true, }, { headerName: "Status", field: "status", flex: 1, // Flex makes this column responsive minWidth: 300, // Ensure it doesn't shrink too much resizable: true, }, ]; }, [selectedCoin, getCoinLabel]); useEffect(() => { if (qortAddress) { qortAddressRef.current = qortAddress; } }, [qortAddress]); const restartTradeOffersWebSocket = () => { setTimeout(() => initTradeOffersWebSocket(true), 50); }; const processTradeBotState = (state) => { if (state.creatorAddress === qortAddressRef.current) { switch (state.tradeState) { case "BOB_WAITING_FOR_AT_CONFIRM": return "PENDING"; case "BOB_WAITING_FOR_MESSAGE": return "LISTED"; case "BOB_WAITING_FOR_AT_REDEEM": return "TRADING"; case "BOB_DONE": case "BOB_REFUNDED": case "ALICE_DONE": case "ALICE_REFUNDED": return null; case "ALICE_WAITING_FOR_AT_LOCK": return "BUYING"; case "ALICE_REFUNDING_A": return "REFUNDING"; default: return null; // Return null or a default value if no tradeState matches } } return null; // Return null if creatorAddress doesn't match qortAddressRef.current }; const processTradeBots = (tradeBots) => { let sellTrades = [...tradeBotListRef.current]; // Start with the existing trades tradeBots.forEach((trade) => { const status = processTradeBotState(trade); if (status) { // Check if the trade is already in the list const existingIndex = sellTrades.findIndex( (existingTrade) => existingTrade.atAddress === trade.atAddress ); if (existingIndex > -1) { // Replace the existing trade if it exists sellTrades[existingIndex] = { ...trade, status }; } else { // Add new trade if it doesn't exist sellTrades.push({ ...trade, status }); } } }); setTradeBotList(sellTrades); tradeBotListRef.current = sellTrades; }; const restartTradeOffers = () => { if (socketRef.current) { socketRef.current.close(1000, "forced"); // Close with a custom reason socketRef.current = null; } offeringTrades.current = []; setTradeBotList([]); tradeBotListRef.current = []; }; const socketRef = useRef(null); const initTradeOffersWebSocket = (restarted = false) => { let tradeOffersSocketCounter = 0; let socketTimeout: any; // let socketLink = `ws://127.0.0.1:12391/websockets/crosschain/tradebot?foreignBlockchain=LITECOIN`; let socketLink = `${ window.location.protocol === "https:" ? "wss:" : "ws:" }//${baseLocalHost}/websockets/crosschain/tradebot?foreignBlockchain=${selectedCoin}`; socketRef.current = new WebSocket(socketLink); socketRef.current.onopen = () => { setTimeout(pingSocket, 50); tradeOffersSocketCounter += 1; }; socketRef.current.onmessage = (e) => { tradeOffersSocketCounter += 1; restarted = false; processTradeBots(JSON.parse(e.data)); }; socketRef.current.onclose = (event) => { clearTimeout(socketTimeout); if (event.reason === "forced") { return; } restartTradeOffersWebSocket(); }; socketRef.current.onerror = (e) => { clearTimeout(socketTimeout); }; const pingSocket = () => { socketRef.current.send("ping"); socketTimeout = setTimeout(pingSocket, 295000); }; }; useEffect(() => { if (!qortAddress) return; if (selectedCoin === null) return; restartTradeOffers(); setTimeout(() => { initTradeOffersWebSocket(); }, 500); return () => { if (socketRef.current) { socketRef.current.close(1000, "forced"); } }; }, [qortAddress, selectedCoin]); const onSelectionChanged = (event: any) => { const selectedRows = event.api.getSelectedRows(); if (selectedRows[0]) { setSelectedTrade(selectedRows[0]); } else { setSelectedTrade(null); } }; const handleClose = ( event?: React.SyntheticEvent | Event, reason?: SnackbarCloseReason ) => { if (reason === "clickaway") { return; } setOpen(false); setInfo(null); }; const cancelSell = async () => { try { if (!selectedTrade) return; setOpen(true); setInfo({ type: "info", message: "Attempting to cancel sell order", }); const res = await qortalRequestWithTimeout( { action: "CANCEL_TRADE_SELL_ORDER", qortAmount: selectedTrade.qortAmount, foreignBlockchain: selectedTrade.foreignBlockchain, foreignAmount: selectedTrade.foreignAmount, atAddress: selectedTrade.atAddress, }, 900000 ); if (res?.signature) { await deleteTemporarySellOrder(selectedTrade.atAddress); setSelectedTrade(null); setOpen(true); setInfo({ type: "success", message: "Sell order canceled. Please wait a couple of minutes for the network to propogate the changes", }); } if (res?.error && res?.failedTradeBot) { setOpen(true); setInfo({ type: "error", message: "Unable to cancel sell order. Please try again.", }); } } catch (error) { if (error?.error && error?.failedTradeBot) { setOpen(true); setInfo({ type: "error", message: "Unable to cancel sell order. Please try again.", }); } } }; const CancelButton = () => { return ( ); }; return (
params.data.qortalAtAddress} // Ensure rows have unique IDs /> {/* {selectedOffer && ( )} */}
{/* {selectedTotalQORT?.toFixed(3)} QORT */} {/* foreignCoinBalance ? 'red' : 'white', }}>{selectedTotalLTC?.toFixed(4)} LTC */} {/* {foreignCoinBalance?.toFixed(4)} LTC balance */} {CancelButton()} {info?.message}
); }