diff --git a/package-lock.json b/package-lock.json index 9a44d49..d6a4b93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ "lodash": "^4.17.21", "moment": "^2.30.1", "react": "^18.2.0", + "react-countdown-circle-timer": "^3.2.1", "react-dom": "^18.2.0", "react-ga4": "^2.1.0", + "react-loader-spinner": "^6.1.6", "react-router-dom": "^6.23.0", "react-toastify": "^10.0.5", "sass": "^1.76.0", @@ -3236,6 +3238,11 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3826,6 +3833,14 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001660", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", @@ -4003,6 +4018,24 @@ "node": ">=8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5984,7 +6017,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -6218,7 +6250,6 @@ "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6242,6 +6273,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6332,6 +6368,14 @@ "node": ">=0.10.0" } }, + "node_modules/react-countdown-circle-timer": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/react-countdown-circle-timer/-/react-countdown-circle-timer-3.2.1.tgz", + "integrity": "sha512-yBAy/9ILXOiFbLBM+3jS72TW5LeRcH8wkRC9NNqMpUkCXkGjSnaeRbJMsR9lsYF0oVXjSDbJaRbCuVMT+9HnKA==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -6354,6 +6398,22 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-loader-spinner": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz", + "integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==", + "dependencies": { + "react-is": "^18.2.0", + "styled-components": "^6.1.2" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -6788,6 +6848,11 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7041,6 +7106,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -7210,6 +7307,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 25ab599..f23f0d3 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ "lodash": "^4.17.21", "moment": "^2.30.1", "react": "^18.2.0", + "react-countdown-circle-timer": "^3.2.1", "react-dom": "^18.2.0", "react-ga4": "^2.1.0", + "react-loader-spinner": "^6.1.6", "react-router-dom": "^6.23.0", "react-toastify": "^10.0.5", "sass": "^1.76.0", diff --git a/src/components/Grids/TradeOffers.tsx b/src/components/Grids/TradeOffers.tsx index 9b9f599..7a73c04 100644 --- a/src/components/Grids/TradeOffers.tsx +++ b/src/components/Grids/TradeOffers.tsx @@ -1,19 +1,48 @@ -import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { AgGridReact } from 'ag-grid-react'; -import { ColDef, RowClassParams, RowStyle, SizeColumnsToContentStrategy } from 'ag-grid-community'; -import 'ag-grid-community/styles/ag-grid.css'; -import 'ag-grid-community/styles/ag-theme-alpine.css'; -import axios from 'axios'; -import { sendRequestToExtension } from '../../App'; -import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Snackbar, SnackbarCloseReason, Typography } from '@mui/material'; -import gameContext from '../../contexts/gameContext'; -import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; -import { useModal } from '../common/useModal'; -import FileSaver from 'file-saver'; -import { BuyContainer } from './Table-styles'; +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { AgGridReact } from "ag-grid-react"; +import { + ColDef, + RowClassParams, + RowStyle, + SizeColumnsToContentStrategy, +} from "ag-grid-community"; +import "ag-grid-community/styles/ag-grid.css"; +import "ag-grid-community/styles/ag-theme-alpine.css"; +import axios from "axios"; +import { sendRequestToExtension } from "../../App"; +import { + Alert, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Snackbar, + SnackbarCloseReason, + Typography, +} from "@mui/material"; +import gameContext from "../../contexts/gameContext"; +import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; +import { useModal } from "../common/useModal"; +import FileSaver from "file-saver"; +import { Spacer } from "../common/Spacer"; +import { Hourglass } from "react-loader-spinner"; +import ErrorIcon from "@mui/icons-material/Error"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import { CountdownCircleTimer } from "react-countdown-circle-timer"; +import { BuyContainer } from "./Table-styles"; -export const baseLocalHost = window.location.host -// export const baseLocalHost = '127.0.0.1:12391' +// export const baseLocalHost = window.location.host +export const baseLocalHost = "127.0.0.1:12391"; interface RowData { amountQORT: number; @@ -23,26 +52,35 @@ interface RowData { } export const saveFileToDisk = async (data) => { - const dataString = JSON.stringify(data); - const blob = new Blob([dataString], { type: 'application/json' }); -const fileName = "traderecord_" + Date.now() + '_' + ".json"; + const blob = new Blob([dataString], { type: "application/json" }); + const fileName = "traderecord_" + Date.now() + "_" + ".json"; -await FileSaver.saveAs(blob, fileName); - -} - -export const autoSizeStrategy: SizeColumnsToContentStrategy = { - type: 'fitCellContents' + await FileSaver.saveAs(blob, fileName); }; -export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { - const [offers, setOffers] = useState([]) - const [qortalNames, setQortalNames] = useState({}) - const { fetchOngoingTransactions, onGoingTrades, updateTransactionInDB, isUsingGateway, getCoinLabel, selectedCoin } = useContext(gameContext); - const listOfOngoingTradesAts = useMemo(()=> { - return onGoingTrades?.filter((item)=> item?.status !== 'trade-failed')?.map((trade)=> trade?.qortalAtAddress) || [] - }, [onGoingTrades]) +export const autoSizeStrategy: SizeColumnsToContentStrategy = { + type: "fitCellContents", +}; + +export const TradeOffers: React.FC = ({ foreignCoinBalance }: any) => { + const [offers, setOffers] = useState([]); + const [qortalNames, setQortalNames] = useState({}); + const { + fetchOngoingTransactions, + onGoingTrades, + updateTransactionInDB, + isUsingGateway, + getCoinLabel, + selectedCoin, + } = useContext(gameContext); + const listOfOngoingTradesAts = useMemo(() => { + return ( + onGoingTrades + ?.filter((item) => item?.status !== "trade-failed") + ?.map((trade) => trade?.qortalAtAddress) || [] + ); + }, [onGoingTrades]); const { isShow: isShowInfo, onCancel: onCancelInfo, @@ -51,121 +89,149 @@ export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { message: messageInfo, } = useModal(); - const offersWithoutOngoing = useMemo(()=> { - return offers.filter((item)=> !listOfOngoingTradesAts.includes(item.qortalAtAddress)) - }, [listOfOngoingTradesAts, offers]) - const initiatedFetchPresence = useRef(false) - const initiatedFetchPresenceSocket = useRef(false) + const offersWithoutOngoing = useMemo(() => { + return offers.filter( + (item) => !listOfOngoingTradesAts.includes(item.qortalAtAddress) + ); + }, [listOfOngoingTradesAts, offers]); + const initiatedFetchPresence = useRef(false); + const initiatedFetchPresenceSocket = useRef(false); + const [isShowBuyInProgress, setIsShowBuyInProgress] = useState(null); + const socketRef = useRef(null); + const socketPresenceRef = useRef(null); + const [selectedOffer, setSelectedOffer] = useState(null); + const [selectedOffers, setSelectedOffers] = useState([]); + const [record, setRecord] = useState(null); + const tradePresenceTxns = useRef([]); + const offeringTrades = useRef([]); + const blockedTradesList = useRef([]); + const gridRef = useRef(null); - const socketRef = useRef(null) - const socketPresenceRef = useRef(null) - const [selectedOffer, setSelectedOffer] = useState(null) - const [selectedOffers, setSelectedOffers] = useState([]) - const [record, setRecord] = useState(null) - const tradePresenceTxns = useRef([]) - const offeringTrades = useRef([]) - const blockedTradesList = useRef([]) - const gridRef = useRef(null) - - - const [open, setOpen] = useState(false) - const [info, setInfo] = useState(null) + const [open, setOpen] = useState(false); + const [info, setInfo] = useState(null); const BuyButton = () => { return ( - ); }; - + const defaultColDef = { resizable: true, // Make columns resizable by default sortable: true, // Make columns sortable by default suppressMovable: true, // Prevent columns from being movable }; - const getName = async (address)=> { + const getName = async (address) => { try { const response = await fetch("/names/address/" + address); - const nameData = await response.json(); - if (nameData?.length > 0) { - setQortalNames((prev)=> { - return { - ...prev, - [address]: nameData[0].name - } - }) - } else { - setQortalNames((prev)=> { - return { - ...prev, - [address]: null - } - }) - } + const nameData = await response.json(); + if (nameData?.length > 0) { + setQortalNames((prev) => { + return { + ...prev, + [address]: nameData[0].name, + }; + }); + } else { + setQortalNames((prev) => { + return { + ...prev, + [address]: null, + }; + }); + } } catch (error) { // error } - } + }; - const restartTradeOffers = ()=> { + const restartTradeOffers = () => { if (socketRef.current) { - socketRef.current.close(1000, 'forced'); // Close with a custom reason - socketRef.current = null + socketRef.current.close(1000, "forced"); // Close with a custom reason + socketRef.current = null; } - offeringTrades.current = [] - setOffers([]) - setSelectedOffer(null) - } + offeringTrades.current = []; + setOffers([]); + setSelectedOffer(null); + }; - const restartPresence = ()=> { + const restartPresence = () => { if (socketPresenceRef.current) { - socketPresenceRef.current.close(1000, 'forced'); // Close with a custom reason - socketPresenceRef.current = null + socketPresenceRef.current.close(1000, "forced"); // Close with a custom reason + socketPresenceRef.current = null; } - } + }; - const columnDefs: ColDef[] = useMemo(()=> { + const columnDefs: ColDef[] = useMemo(() => { return [ - { + { headerCheckboxSelection: true, // Adds a checkbox in the header for selecting all rows checkboxSelection: true, // Adds checkboxes in each row for selection headerName: "", // You can customize the header name width: 50, // Adjust the width as needed - pinned: 'left', // Optional, to pin this column on the left + 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: "Seller", field: "qortalCreator", flex: 1, // Flex makes this column responsive - minWidth: 300, // Ensure it doesn't shrink too much - resizable: true, valueGetter: (params)=> { - if(params?.data?.qortalCreator){ - if(qortalNames[params?.data?.qortalCreator]){ - return qortalNames[params?.data?.qortalCreator] - } else if(qortalNames[params?.data?.qortalCreator] === undefined){ - getName(params?.data?.qortalCreator) - - return params?.data?.qortalCreator - } else { - return params?.data?.qortalCreator - - } - } - } }, - ]; - },[qortalNames]) + { + 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: "Seller", + field: "qortalCreator", + flex: 1, // Flex makes this column responsive + minWidth: 300, // Ensure it doesn't shrink too much + resizable: true, + valueGetter: (params) => { + if (params?.data?.qortalCreator) { + if (qortalNames[params?.data?.qortalCreator]) { + return qortalNames[params?.data?.qortalCreator]; + } else if (qortalNames[params?.data?.qortalCreator] === undefined) { + getName(params?.data?.qortalCreator); - + return params?.data?.qortalCreator; + } else { + return params?.data?.qortalCreator; + } + } + }, + }, + ]; + }, [qortalNames]); // const onRowClicked = (event: any) => { // if(listOfOngoingTradesAts.includes(event.data.qortalAtAddress)) return @@ -174,235 +240,256 @@ export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { // }; const restartTradePresenceWebSocket = () => { - restartPresence() - setTimeout(() => initTradePresenceWebSocket(true), 50) - } - - + restartPresence(); + setTimeout(() => initTradePresenceWebSocket(true), 50); + }; const getNewBlockedTrades = async () => { const unconfirmedTransactionsList = async () => { + const unconfirmedTransactionslUrl = `/transactions/unconfirmed?txType=MESSAGE&limit=0&reverse=true`; - const unconfirmedTransactionslUrl = `/transactions/unconfirmed?txType=MESSAGE&limit=0&reverse=true` + var addBlockedTrades = JSON.parse( + localStorage.getItem("failedTrades") || "[]" + ); - var addBlockedTrades = JSON.parse(localStorage.getItem('failedTrades') || '[]') - - await fetch(unconfirmedTransactionslUrl).then(response => { - return response.json() - }).then(data => { - data.map((item: any) => { - const unconfirmedNessageTimeDiff = Date.now() - item.timestamp - const timeOneHour = 60 * 60 * 1000 - if (Number(unconfirmedNessageTimeDiff) > Number(timeOneHour)) { - const addBlocked = { - timestamp: item.timestamp, - recipient: item.recipient - } - addBlockedTrades.push(addBlocked) - } + await fetch(unconfirmedTransactionslUrl) + .then((response) => { + return response.json(); }) - localStorage.setItem("failedTrades", JSON.stringify(addBlockedTrades)) - blockedTradesList.current = JSON.parse(localStorage.getItem('failedTrades') || '[]') - }) - } + .then((data) => { + data.map((item: any) => { + const unconfirmedNessageTimeDiff = Date.now() - item.timestamp; + const timeOneHour = 60 * 60 * 1000; + if (Number(unconfirmedNessageTimeDiff) > Number(timeOneHour)) { + const addBlocked = { + timestamp: item.timestamp, + recipient: item.recipient, + }; + addBlockedTrades.push(addBlocked); + } + }); + localStorage.setItem( + "failedTrades", + JSON.stringify(addBlockedTrades) + ); + blockedTradesList.current = JSON.parse( + localStorage.getItem("failedTrades") || "[]" + ); + }); + }; - await unconfirmedTransactionsList() + await unconfirmedTransactionsList(); const filterUnconfirmedTransactionsList = async () => { - let cleanBlockedTrades = blockedTradesList.current.reduce((newArray, cut: any) => { - if (cut && !newArray.some((obj: any) => obj.recipient === cut.recipient)) { - newArray.push(cut) - } - return newArray - }, [] as any[]) - localStorage.setItem("failedTrades", JSON.stringify(cleanBlockedTrades)) - blockedTradesList.current = JSON.parse(localStorage.getItem("failedTrades") || "[]") - } + let cleanBlockedTrades = blockedTradesList.current.reduce( + (newArray, cut: any) => { + if ( + cut && + !newArray.some((obj: any) => obj.recipient === cut.recipient) + ) { + newArray.push(cut); + } + return newArray; + }, + [] as any[] + ); + localStorage.setItem("failedTrades", JSON.stringify(cleanBlockedTrades)); + blockedTradesList.current = JSON.parse( + localStorage.getItem("failedTrades") || "[]" + ); + }; - await filterUnconfirmedTransactionsList() - processOffersWithPresence() - } + await filterUnconfirmedTransactionsList(); + processOffersWithPresence(); + }; - const executeGetNewBlockTrades = useCallback(()=> { - getNewBlockedTrades() - - }, []) + const executeGetNewBlockTrades = useCallback(() => { + getNewBlockedTrades(); + }, []); useEffect(() => { subscribeToEvent("execute-get-new-block-trades", executeGetNewBlockTrades); return () => { - unsubscribeFromEvent("execute-get-new-block-trades", executeGetNewBlockTrades); + unsubscribeFromEvent( + "execute-get-new-block-trades", + executeGetNewBlockTrades + ); }; }, []); const processOffersWithPresence = () => { - if (offeringTrades.current === null) return + if (offeringTrades.current === null) return; async function asyncForEach(array: any, callback: any) { for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array) + await callback(array[index], index, array); } } - const filterOffersUsingTradePresence = (offeringTrade: any) => { return offeringTrade.tradePresenceExpiry > Date.now(); - } + }; const startOfferPresenceMapping = async () => { if (tradePresenceTxns.current) { for (const tradePresence of tradePresenceTxns.current) { - const offerIndex = offeringTrades.current.findIndex(offeringTrade => offeringTrade.qortalCreatorTradeAddress === tradePresence.tradeAddress); + const offerIndex = offeringTrades.current.findIndex( + (offeringTrade) => + offeringTrade.qortalCreatorTradeAddress === + tradePresence.tradeAddress + ); if (offerIndex !== -1) { - offeringTrades.current[offerIndex].tradePresenceExpiry = tradePresence.timestamp; + offeringTrades.current[offerIndex].tradePresenceExpiry = + tradePresence.timestamp; } } } - let filteredOffers = offeringTrades.current?.filter((offeringTrade) => filterOffersUsingTradePresence(offeringTrade)) || [] - let tradesPresenceCleaned: any[] = filteredOffers - + let filteredOffers = + offeringTrades.current?.filter((offeringTrade) => + filterOffersUsingTradePresence(offeringTrade) + ) || []; + let tradesPresenceCleaned: any[] = filteredOffers; blockedTradesList.current.forEach((item: any) => { - const toDelete = item.recipient - tradesPresenceCleaned = tradesPresenceCleaned?.filter(el => { - return el.qortalCreatorTradeAddress !== toDelete - }) || [] - }) + const toDelete = item.recipient; + tradesPresenceCleaned = + tradesPresenceCleaned?.filter((el) => { + return el.qortalCreatorTradeAddress !== toDelete; + }) || []; + }); if (tradesPresenceCleaned) { - updateGridData(tradesPresenceCleaned) + updateGridData(tradesPresenceCleaned); } - } + }; - startOfferPresenceMapping() - } + startOfferPresenceMapping(); + }; const restartTradeOffersWebSocket = () => { - setTimeout(() => initTradeOffersWebSocket(true), 50) - } + setTimeout(() => initTradeOffersWebSocket(true), 50); + }; const initTradePresenceWebSocket = (restarted = false) => { - if(socketPresenceRef.current) return - let socketTimeout: any - let socketLink - if(isUsingGateway){ - socketLink = `wss://appnode.qortal.org/websockets/crosschain/tradepresence` + if (socketPresenceRef.current) return; + let socketTimeout: any; + let socketLink; + if (isUsingGateway) { + socketLink = `wss://appnode.qortal.org/websockets/crosschain/tradepresence`; } else { - socketLink = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${baseLocalHost}/websockets/crosschain/tradepresence`; - + socketLink = `${ + window.location.protocol === "https:" ? "wss:" : "ws:" + }//${baseLocalHost}/websockets/crosschain/tradepresence`; } - - - socketPresenceRef.current = new WebSocket(socketLink) + + socketPresenceRef.current = new WebSocket(socketLink); socketPresenceRef.current.onopen = () => { - setTimeout(pingSocket, 50) - } + setTimeout(pingSocket, 50); + }; socketPresenceRef.current.onmessage = (e) => { - tradePresenceTxns.current = !initiatedFetchPresenceSocket.current ? JSON.parse(e.data) : [...tradePresenceTxns.current, ...JSON.parse(e.data)] - initiatedFetchPresenceSocket.current = true - processOffersWithPresence() - restarted = false - } + tradePresenceTxns.current = !initiatedFetchPresenceSocket.current + ? JSON.parse(e.data) + : [...tradePresenceTxns.current, ...JSON.parse(e.data)]; + initiatedFetchPresenceSocket.current = true; + processOffersWithPresence(); + restarted = false; + }; socketPresenceRef.current.onclose = (event) => { - clearTimeout(socketTimeout) - if (event.reason === 'forced') { - return + clearTimeout(socketTimeout); + if (event.reason === "forced") { + return; } - restartTradePresenceWebSocket() - } + restartTradePresenceWebSocket(); + }; socketPresenceRef.current.onerror = (e) => { - clearTimeout(socketTimeout) - restartTradePresenceWebSocket() - } + clearTimeout(socketTimeout); + restartTradePresenceWebSocket(); + }; const pingSocket = () => { - socketPresenceRef.current.send('ping') - socketTimeout = setTimeout(pingSocket, 295000) - } - } - - - + socketPresenceRef.current.send("ping"); + socketTimeout = setTimeout(pingSocket, 295000); + }; + }; const initTradeOffersWebSocket = (restarted = false) => { + if (socketRef.current) return; + let socketTimeout: any; - if(socketRef.current) return - let socketTimeout: any - - let socketLink - if(isUsingGateway){ - socketLink = `wss://appnode.qortal.org/websockets/crosschain/tradeoffers?foreignBlockchain=${selectedCoin}&includeHistoric=true` + let socketLink; + if (isUsingGateway) { + socketLink = `wss://appnode.qortal.org/websockets/crosschain/tradeoffers?foreignBlockchain=${selectedCoin}&includeHistoric=true`; } else { - socketLink = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${baseLocalHost}/websockets/crosschain/tradeoffers?foreignBlockchain=${selectedCoin}&includeHistoric=true` - + socketLink = `${ + window.location.protocol === "https:" ? "wss:" : "ws:" + }//${baseLocalHost}/websockets/crosschain/tradeoffers?foreignBlockchain=${selectedCoin}&includeHistoric=true`; } - socketRef.current = new WebSocket(socketLink) + socketRef.current = new WebSocket(socketLink); socketRef.current.onopen = () => { - setTimeout(pingSocket, 50) - } + setTimeout(pingSocket, 50); + }; socketRef.current.onmessage = (e) => { - offeringTrades.current = [...offeringTrades.current?.filter((coin)=> coin?.foreignBlockchain === selectedCoin), ...JSON.parse(e.data)?.filter((coin)=> coin?.foreignBlockchain === selectedCoin)] - restarted = false - processOffersWithPresence() - } + offeringTrades.current = [ + ...offeringTrades.current?.filter( + (coin) => coin?.foreignBlockchain === selectedCoin + ), + ...JSON.parse(e.data)?.filter( + (coin) => coin?.foreignBlockchain === selectedCoin + ), + ]; + restarted = false; + processOffersWithPresence(); + }; socketRef.current.onclose = (event) => { - clearTimeout(socketTimeout) - if (event.reason === 'forced') { - return + clearTimeout(socketTimeout); + if (event.reason === "forced") { + return; } - restartTradeOffersWebSocket() - socketRef.current = null - } + restartTradeOffersWebSocket(); + socketRef.current = null; + }; socketRef.current.onerror = (e) => { - clearTimeout(socketTimeout) - } + clearTimeout(socketTimeout); + }; const pingSocket = () => { - socketRef.current.send('ping') - socketTimeout = setTimeout(pingSocket, 295000) - } - } - + socketRef.current.send("ping"); + socketTimeout = setTimeout(pingSocket, 295000); + }; + }; useEffect(() => { - blockedTradesList.current = JSON.parse(localStorage.getItem('failedTrades') || '[]') - if(!initiatedFetchPresence.current){ - initiatedFetchPresence.current = true - initTradePresenceWebSocket() - + blockedTradesList.current = JSON.parse( + localStorage.getItem("failedTrades") || "[]" + ); + if (!initiatedFetchPresence.current) { + initiatedFetchPresence.current = true; + initTradePresenceWebSocket(); } - getNewBlockedTrades() + getNewBlockedTrades(); const intervalBlockTrades = setInterval(() => { - - initiatedFetchPresenceSocket.current = false - restartPresence() - initTradePresenceWebSocket() - getNewBlockedTrades() - }, 150000) + initiatedFetchPresenceSocket.current = false; + restartPresence(); + initTradePresenceWebSocket(); + getNewBlockedTrades(); + }, 150000); return () => { - clearInterval(intervalBlockTrades) - } - }, [isUsingGateway]) - - + clearInterval(intervalBlockTrades); + }; + }, [isUsingGateway]); useEffect(() => { - if(selectedCoin === null) return - restartTradeOffers() + if (selectedCoin === null) return; + restartTradeOffers(); setTimeout(() => { - initTradeOffersWebSocket() - + initTradeOffersWebSocket(); }, 500); return () => { - if(socketRef.current){ - socketRef.current.close(1000, 'forced'); + if (socketRef.current) { + socketRef.current.close(1000, "forced"); } - } - }, [isUsingGateway, selectedCoin]) - - - - + }; + }, [isUsingGateway, selectedCoin]); const selectedTotalLTC = useMemo(() => { return selectedOffers.reduce((acc: number, curr: any) => { @@ -410,7 +497,6 @@ export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { }, 0); }, [selectedOffers]); - const buyOrder = async () => { try { if(+foreignCoinBalance < +selectedTotalLTC.toFixed(4)){ @@ -421,35 +507,50 @@ export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { }) return } - - if (selectedOffers?.length < 1) return - setOpen(true) - setInfo({ - type: 'info', - message: "Attempting to submit buy order. Please wait..." - }) - const listOfATs = selectedOffers - const response = await qortalRequestWithTimeout({ - action: "CREATE_TRADE_BUY_ORDER", - crosschainAtInfo: listOfATs, - foreignBlockchain: selectedCoin - }, 900000); - - if(response?.error){ - setOpen(true) - setInfo({ - type: 'error', - message: response?.error || "Failed to submit trade order." - }) - return + + if (selectedOffers?.length < 1) return; + + setIsShowBuyInProgress({ status: "buying" }); + + // setOpen(true) + // setInfo({ + // type: 'info', + // message: "Attempting to submit buy order. Please wait..." + // }) + const listOfATs = selectedOffers; + const response = await qortalRequestWithTimeout( + { + action: "CREATE_TRADE_BUY_ORDER", + crosschainAtInfo: listOfATs, + foreignBlockchain: selectedCoin, + }, + 900000 + ); + + if (response?.error) { + setIsShowBuyInProgress({ + status: "error", + message: response?.error || "Failed to submit trade order.", + }); + // setOpen(true) + // setInfo({ + // type: 'error', + // message: response?.error || "Failed to submit trade order." + // }) + return; } if (response?.extra?.atAddresses) { - setSelectedOffers([]) + setIsShowBuyInProgress({ status: "success" }); + setSelectedOffers([]); const transactionData = { qortalAtAddresses: response?.extra?.atAddresses, qortAddress: response?.extra?.senderAddress, node: response?.extra?.node, - status:response?.extra?.status ? response?.extra?.status : response.callResponse === true ? 'trade-ongoing' : 'trade-failed', + status: response?.extra?.status + ? response?.extra?.status + : response.callResponse === true + ? "trade-ongoing" + : "trade-failed", encryptedMessageToBase58: response?.encryptedMessageToBase58, chatSignature: response?.chatSignature, sender: response?.extra?.senderAddress, @@ -457,43 +558,44 @@ export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { reference: response?.callResponse?.reference, }; - - // Update transactions in IndexedDB const result = await updateTransactionInDB(transactionData); - setOpen(true) + setOpen(true); setInfo({ - type: 'success', - message: "Submitted Order" - }) - fetchOngoingTransactions() - if(isUsingGateway){ - setRecord(transactionData) + type: "success", + message: "Submitted Order", + }); + fetchOngoingTransactions(); + if (isUsingGateway) { + setRecord(transactionData); await showInfo({ message: `Keep a record of your order in case your trade gets stuck`, - }) + }); } - } - } catch (error) { - setOpen(true) - setInfo({ - type: 'error', - message: error?.error || error?.message || "Failed to submit trade order." - }) - console.error(error) + setIsShowBuyInProgress({ + status: "error", + message: + error?.error || error?.message || "Failed to submit trade order.", + }); + // setOpen(true) + // setInfo({ + // type: 'error', + // message: error?.error || error?.message || "Failed to submit trade order." + // }) + // console.error(error) } - } + }; - - const getRowStyle = (params: RowClassParams): RowStyle | undefined => { - + const getRowStyle = ( + params: RowClassParams + ): RowStyle | undefined => { if (listOfOngoingTradesAts.includes(params.data.qortalAtAddress)) { - return { background: '#D9D9D91A'}; + return { background: "#D9D9D91A" }; } if (params.data.qortalAtAddress === selectedOffer?.qortalAtAddress) { - return { background: '#6D94F533'}; + return { background: "#6D94F533" }; } return undefined; }; @@ -504,83 +606,82 @@ export const TradeOffers: React.FC = ({foreignCoinBalance}:any) => { const onSelectionChanged = (event: any) => { const selectedRows = event.api.getSelectedRows(); - + setSelectedOffers([...selectedRows]); // Set all selected rows }; - + const onRowClicked = (event: any) => { if (listOfOngoingTradesAts.includes(event.data.qortalAtAddress)) return; const selectedRows = gridRef.current?.api.getSelectedRows(); setSelectedOffers([...selectedRows]); // Always spread the array to ensure state updates correctly }; - const updateGridData = (newData: any) => { if (gridRef.current) { - setOffers(newData); - } }; const getRowId = useCallback(function (params: any) { return String(params.data.qortalAtAddress); -}, []); + }, []); -const selectedTotalQORT = useMemo(() => { - return selectedOffers.reduce((acc: number, curr: any) => { - return acc + (+curr.qortAmount || 0); // Ensure qortAmount is defined - }, 0); -}, [selectedOffers]); + const selectedTotalQORT = useMemo(() => { + return selectedOffers.reduce((acc: number, curr: any) => { + return acc + (+curr.qortAmount || 0); // Ensure qortAmount is defined + }, 0); + }, [selectedOffers]); + 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 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 handleClose = ( + event?: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason + ) => { + if (reason === "clickaway") { + return; + } - -const handleClose = ( - event?: React.SyntheticEvent | Event, - reason?: SnackbarCloseReason, -) => { - if (reason === 'clickaway') { - return; - } - - setOpen(false); - setInfo(null) -}; - - - + setOpen(false); + setInfo(null); + }; return ( - -
- params.data.qortalAtAddress} // Ensure rows have unique IDs - /> - {/* {selectedOffer && ( + +
+ params.data.qortalAtAddress} // Ensure rows have unique IDs + /> + {/* {selectedOffer && ( )} */} @@ -627,12 +728,10 @@ const handleClose = ( {info?.message} @@ -645,35 +744,167 @@ const handleClose = ( > {"Download record"} - + {messageInfo.message} - + - )} + {isShowBuyInProgress && ( + + + {isShowBuyInProgress?.status === "error" && ( + + + + {` Failed to submit buy order.`} + + + {isShowBuyInProgress?.message} + + )} + {isShowBuyInProgress?.status === "success" && ( + + + + {` Successfully submitted order.`} + + + + You can see the progress of your + order in the "Pending" table. + + + + Note: Submission of an order does not necessarily mean that + your submission will be the one completing the order. Another + account may have submitted it before you. + + + )} + {isShowBuyInProgress?.status === "buying" && ( + + + + {` Attempting to submit buy order`} + + + + Please do not leave this application until there is a + response. Please wait! + + + {isUsingGateway && ( + <> + + Using gateway: might take up to 3 minutes to submit the buy order. + + + + { + //nothing + }} + size={60} + strokeWidth={4} + > + {({ remainingTime }) => {remainingTime}} + + + + )} + + )} + + + + + + )} ); }; - diff --git a/src/components/history/History.tsx b/src/components/history/History.tsx new file mode 100644 index 0000000..6cb056d --- /dev/null +++ b/src/components/history/History.tsx @@ -0,0 +1,110 @@ +import { Alert, Box, Button, ButtonBase, DialogActions, DialogContent, DialogTitle, IconButton, InputLabel, Snackbar, SnackbarCloseReason, TextField, Typography, styled } from '@mui/material' +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react' +import { BootstrapDialog } from '../Terms' +import CloseIcon from '@mui/icons-material/Close'; +import { Spacer } from '../common/Spacer'; +import gameContext from '../../contexts/gameContext'; +import HistoryList from './HistoryList'; +import RefreshIcon from "@mui/icons-material/Refresh"; + + + + +export const History = ({qortAddress, show}) => { + const [buyHistory, setBuyHistory] = useState({}) + const [sellHistory, setSellHistory] = useState({}) + + const { getCoinLabel, selectedCoin} = useContext(gameContext) + const [mode, setMode] = useState('buyHistory') + const [open, setOpen] = useState(false) + + const selectedHistory = useMemo(()=> { + if(mode === 'buyHistory') return buyHistory[selectedCoin] || [] + if(mode === 'sellHistory') return sellHistory[selectedCoin] || [] + }, [selectedCoin, buyHistory, sellHistory, mode]) + const getBuyHistory = useCallback((address, foreignBlockchain, mode, limit = 20)=> { + setOpen(true) + let historyUrl + if(mode === 'buyHistory'){ + historyUrl = `/crosschain/trades?foreignBlockchain=${foreignBlockchain}&buyerAddress=${address}&limit=${limit}&reverse=true`; + + } + if(mode === 'sellHistory'){ + historyUrl = `/crosschain/trades?foreignBlockchain=${foreignBlockchain}&sellerAddress=${address}&limit=${limit}&reverse=true`; + + } + + + + fetch(historyUrl) + .then((response) => { + return response.json(); + }) + .then((data) => { + if(mode === 'buyHistory'){ + setBuyHistory((prev)=> { + return { + ...prev, + [foreignBlockchain]: data + } + }) + } + if(mode === 'sellHistory'){ + setSellHistory((prev)=> { + return { + ...prev, + [foreignBlockchain]: data + } + }) + } + + }).catch(()=> {}).finally(()=> { + setOpen(false) + }) + }, []) + + useEffect(()=> { + if(!qortAddress || !selectedCoin) return + if(mode === 'buyHistory' && buyHistory[selectedCoin])return + if(mode === 'sellHistory' && sellHistory[selectedCoin])return + + getBuyHistory(qortAddress, selectedCoin, mode) + }, [qortAddress, selectedCoin, buyHistory, mode]) + + return ( +
+ + + { + getBuyHistory(qortAddress, selectedCoin, mode) + }}> + + + Showing most recent 20 results + + { + setOpen(false) + }} + > + setOpen(false)} + severity="info" + variant="filled" + sx={{ width: "100%" }} + > + {'Fetching History'} + + +
+ ) +} diff --git a/src/components/history/HistoryList.tsx b/src/components/history/HistoryList.tsx new file mode 100644 index 0000000..dd680a3 --- /dev/null +++ b/src/components/history/HistoryList.tsx @@ -0,0 +1,190 @@ +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 { formatTimestampForum } from "../../utils/formatTime"; + +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 HistoryList({ qortAddress, historyList }) { + const gridRef = useRef(null); + console.log('historyList', historyList) + const { getCoinLabel, selectedCoin} = useContext(gameContext) + const [qortalNames, setQortalNames] = useState({}); + + + 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 getName = async (address) => { + try { + const response = await fetch("/names/address/" + address); + const nameData = await response.json(); + if (nameData?.length > 0) { + setQortalNames((prev) => { + return { + ...prev, + [address]: nameData[0].name, + }; + }); + } else { + setQortalNames((prev) => { + return { + ...prev, + [address]: null, + }; + }); + } + } catch (error) { + // error + } + }; + + + const columnDefs: ColDef[] = useMemo(()=> { + return [ + { + 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, + 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: "Time", + field: "tradeTimestamp", + valueGetter: (params) => + formatTimestampForum(params.data.tradeTimestamp), + flex: 1, // Flex makes this column responsive + minWidth: 200, // Ensure it doesn't shrink too much + resizable: true, + }, + { + headerName: "Buyer", + field: "buyerReceivingAddress", + flex: 1, // Flex makes this column responsive + minWidth: 200, // Ensure it doesn't shrink too much + resizable: true, + valueGetter: (params) => { + if (params?.data?.buyerReceivingAddress) { + if (qortalNames[params?.data?.buyerReceivingAddress]) { + return qortalNames[params?.data?.buyerReceivingAddress]; + } else if (qortalNames[params?.data?.buyerReceivingAddress] === undefined) { + getName(params?.data?.buyerReceivingAddress); + + return params?.data?.buyerReceivingAddress; + } else { + return params?.data?.buyerReceivingAddress; + } + } + }, + }, + { + headerName: "Seller", + field: "sellerAddress", + flex: 1, // Flex makes this column responsive + minWidth: 200, // Ensure it doesn't shrink too much + resizable: true, + valueGetter: (params) => { + if (params?.data?.sellerAddress) { + if (qortalNames[params?.data?.sellerAddress]) { + return qortalNames[params?.data?.sellerAddress]; + } else if (qortalNames[params?.data?.sellerAddress] === undefined) { + getName(params?.data?.sellerAddress); + + return params?.data?.sellerAddress; + } else { + return params?.data?.sellerAddress; + } + } + }, + }, + ]; + + }, [selectedCoin, qortalNames]) + + + + // const onSelectionChanged = (event: any) => { + // const selectedRows = event.api.getSelectedRows(); + // if(selectedRows[0]){ + // setSelectedTrade(selectedRows[0]) + // } else { + // setSelectedTrade(null) + // } + // }; + + + + + + + + return ( +
+
+ params.data.qortalAtAddress} // Ensure rows have unique IDs + /> +
+
+ ); +} diff --git a/src/components/sell/CreateSell.tsx b/src/components/sell/CreateSell.tsx index 66f3f14..d4e6c7a 100644 --- a/src/components/sell/CreateSell.tsx +++ b/src/components/sell/CreateSell.tsx @@ -232,7 +232,7 @@ export const CreateSell = ({qortAddress, show}) => { /> - {`Price Each (${getCoinLabel()})`} + {`Price of Each QORT (in ${getCoinLabel()})`} { {`${qortAmount * foreignAmount} ${getCoinLabel()}`} for {qortAmount} QORT Total sell amount needs to be greater than: {minimumAmountSellTrades[selectedCoin]?.value} {' '} {minimumAmountSellTrades[selectedCoin]?.ticker} diff --git a/src/components/sell/TradeBotList.tsx b/src/components/sell/TradeBotList.tsx index c317868..c619787 100644 --- a/src/components/sell/TradeBotList.tsx +++ b/src/components/sell/TradeBotList.tsx @@ -18,47 +18,7 @@ const defaultColDef = { suppressMovable: true, // Prevent columns from being movable }; -// const columnDefs: ColDef[] = [ -// { -// 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: "LTC/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 LTC 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, -// }, -// ]; + export default function TradeBotList({ qortAddress, failedTradeBots }) { const [tradeBotList, setTradeBotList] = useState([]); diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index f732c34..ccb1cda 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -11,6 +11,7 @@ import { Spacer } from "../../components/common/Spacer"; import { ReusableModal } from "../../components/common/reusable-modal/ReusableModal"; import { Tab, TabDivider, TabsContainer, TabsRow } from "./Home-Styles"; import { CreateSell } from "../../components/sell/CreateSell"; +import { History } from "../../components/history/History"; export const HomePage = () => { const { @@ -22,7 +23,8 @@ export const HomePage = () => { onGoingTrades, selectedCoin, } = useContext(gameContext); - const [mode, setMode] = useState("buy"); + const { setNotification } = useContext(NotificationContext); + const [mode, setMode] = useState("history"); const filteredOngoingTrades = useMemo(() => { return onGoingTrades?.filter( (item) => item?.tradeInfo?.foreignBlockchain === selectedCoin @@ -111,6 +113,7 @@ export const HomePage = () => {
+ ); diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index bc26d72..6bc2c01 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -1,6 +1,25 @@ +import moment from "moment"; + export function formatTime(seconds: number): string { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; // Pad the seconds with a leading zero if less than 10 return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } + + +export function formatTimestampForum(timestamp: number): string { + const now = moment(); + const timestampMoment = moment(timestamp); + const elapsedTime = now.diff(timestampMoment, 'minutes'); + + if (elapsedTime < 1) { + return `Just now - ${timestampMoment.format('h:mm A')}`; + } else if (elapsedTime < 60) { + return `${elapsedTime}m ago - ${timestampMoment.format('h:mm A')}`; + } else if (elapsedTime < 1440) { + return `${Math.floor(elapsedTime / 60)}h ago - ${timestampMoment.format('h:mm A')}`; + } else { + return timestampMoment.format('MMM D, YYYY - h:mm A'); + } +} \ No newline at end of file