diff --git a/src/app/components/AccountHistory.tsx b/src/app/components/AccountHistory.tsx index 60188655..f26f6ae9 100644 --- a/src/app/components/AccountHistory.tsx +++ b/src/app/components/AccountHistory.tsx @@ -31,6 +31,12 @@ import Papa from "papaparse"; import HoverGradientButton from "./HoverGradientButton"; import { twMerge } from "tailwind-merge"; +import { + setHideOtherPairs, + selectCombinedOrderHistory, + selectCombinedOpenOrders, +} from "../state/accountHistorySlice"; + function createOrderReceiptAddressLookup( pairsList: PairInfo[] ): Record { @@ -201,19 +207,35 @@ function DisplayTable() { ); const openOrders = useAppSelector(selectOpenOrders); const orderHistory = useAppSelector(selectOrderHistory); + const dispatch = useAppDispatch(); + const hideOtherPairs = useAppSelector( + (state) => state.accountHistory.hideOtherPairs + ); + const combinedOrderHistory = useAppSelector(selectCombinedOrderHistory); + const combinedOpenOrders = useAppSelector(selectCombinedOpenOrders); + + const handleToggleChange = (e: React.ChangeEvent) => { + dispatch(setHideOtherPairs(e.target.checked)); + }; const tableToShow = useMemo(() => { switch (selectedTable) { case Tables.OPEN_ORDERS: + const filteredRowsForOpenOrders = hideOtherPairs + ? openOrders + : combinedOpenOrders; return { headers: headers[Tables.OPEN_ORDERS], - rows: , + rows: , }; case Tables.ORDER_HISTORY: + const filteredRowsForOrderHistory = hideOtherPairs + ? orderHistory + : combinedOrderHistory; return { headers: headers[Tables.ORDER_HISTORY], - rows: , + rows: , }; default: @@ -222,10 +244,44 @@ function DisplayTable() { rows: <>, }; } - }, [openOrders, orderHistory, selectedTable]); + }, [ + openOrders, + orderHistory, + selectedTable, + hideOtherPairs, + combinedOrderHistory, + combinedOpenOrders, + ]); return (
+
+ +
diff --git a/src/app/state/accountHistorySlice.ts b/src/app/state/accountHistorySlice.ts index 8a417f0c..9167fc97 100644 --- a/src/app/state/accountHistorySlice.ts +++ b/src/app/state/accountHistorySlice.ts @@ -31,6 +31,8 @@ export interface AccountHistoryState { selectedTable: Tables; tables: Tables[]; selectedOrdersToCancel: Record; // the key is `${orderRecieptAddress}_${nftRecieptId}` + hideOtherPairs: boolean; + orderHistoryAllPairs: adex.OrderReceipt[]; } // INITIAL STATE @@ -40,6 +42,8 @@ const initialState: AccountHistoryState = { selectedTable: Tables.OPEN_ORDERS, tables: Object.values(Tables), selectedOrdersToCancel: {}, + hideOtherPairs: true, + orderHistoryAllPairs: [], }; // ASYNC THUNKS @@ -65,6 +69,40 @@ export const fetchAccountHistory = createAsyncThunk< } }); +export const fetchAccountHistoryAllPairs = createAsyncThunk< + adex.OrderReceipt[], + undefined, + { state: RootState } +>("accountHistory/fetchAccountHistoryAllPairs", async (_, thunkAPI) => { + const state = thunkAPI.getState(); + const pairAddresses = state.pairSelector.pairsList.map( + (pairInfo) => pairInfo.address + ); + + const account = state.radix?.walletData.accounts[0]?.address || ""; + + const orderHistoryPromises = pairAddresses.map((pairAddress) => + adex.getAccountOrders(account, pairAddress, 0) + ); + + const apiResponses = await Promise.all(orderHistoryPromises); + const allOrders: adex.OrderReceipt[] = []; + + apiResponses.forEach((apiResponse) => { + const plainApiResponse: SdkResult = JSON.parse(JSON.stringify(apiResponse)); + + if (plainApiResponse.status === "SUCCESS") { + allOrders.push(...plainApiResponse.data.orders); + } else { + console.error( + `Error fetching orders for pair: ${plainApiResponse.message}` + ); + } + }); + + return allOrders; +}); + export const batchCancel = createAsyncThunk< Order[], // return value Order[], // input @@ -170,21 +208,35 @@ export const accountHistorySlice = createSlice({ resetSelectedOrdersToCancel: (state) => { state.selectedOrdersToCancel = {}; }, + setHideOtherPairs: (state, action: PayloadAction) => { + state.hideOtherPairs = action.payload; + }, }, extraReducers: (builder) => { - builder.addCase(fetchAccountHistory.fulfilled, (state, action) => { - state.orderHistory = action.payload.data.orders; - }); - builder.addCase(batchCancel.fulfilled, (state) => { - state.selectedOrdersToCancel = {}; - }); + builder + .addCase(fetchAccountHistory.fulfilled, (state, action) => { + state.orderHistory = action.payload.data.orders; + }) + .addCase(batchCancel.fulfilled, (state) => { + state.selectedOrdersToCancel = {}; + }) + .addCase(fetchAccountHistoryAllPairs.fulfilled, (state, action) => { + state.orderHistoryAllPairs = action.payload; + }) + .addCase(fetchAccountHistoryAllPairs.rejected, (state, action) => { + console.error("Failed to fetch account history:", action.error.message); + }); }, }); // SELECTORS -export const { setSelectedTable, selectOrderToCancel, deselectOrderToCancel } = - accountHistorySlice.actions; +export const { + setSelectedTable, + selectOrderToCancel, + deselectOrderToCancel, + resetAccountHistory, +} = accountHistorySlice.actions; // TODO: possibly remove, as this selector seems to not be used anywhere in the code export const selectFilteredData = createSelector( @@ -212,4 +264,55 @@ export const selectOrderHistory = createSelector( (orderHistory) => orderHistory.filter((order) => order.status !== "PENDING") ); +// A function that checks an order against a filter condition and returns TRUE if it matches, FALSE otherwise +type FilterFunction = (order: adex.OrderReceipt) => boolean; + +let selectCombinedOrders = (filterFunction: FilterFunction) => + createSelector( + (state: RootState) => state.accountHistory.orderHistory, + (state: RootState) => state.accountHistory.orderHistoryAllPairs, + (orderHistory, orderHistoryAllPairs) => { + // Create a Map to handle duplicates + const orderMap = new Map(); + + // Generate unique orderId helper + const getUniqueOrderId = (order: adex.OrderReceipt) => + `${order.pairName}_${order.id}`; + + // Add orders from orderHistory + orderHistory.forEach((order) => { + orderMap.set(getUniqueOrderId(order), order); + }); + + // Add orders from orderHistoryAllPairs, will overwrite any duplicates from orderHistory + orderHistoryAllPairs.forEach((order) => { + orderMap.set(getUniqueOrderId(order), order); + }); + + return Array.from(orderMap.values()) + .filter(filterFunction) + .sort((a, b) => { + const timeDifference = + new Date(b.timeSubmitted).getTime() - + new Date(a.timeSubmitted).getTime(); + if (timeDifference !== 0) { + return timeDifference; + } else { + return b.id - a.id; + } + }); + } + ); + +// create aliases for calling the selector with different filtering functions +export const selectCombinedOrderHistory = selectCombinedOrders( + (order) => order.status !== "PENDING" +); +export const selectCombinedOpenOrders = selectCombinedOrders( + (order) => order.status === "PENDING" +); + export const selectTables = (state: RootState) => state.accountHistory.tables; + +export const { setHideOtherPairs } = accountHistorySlice.actions; +export const { actions, reducer } = accountHistorySlice; diff --git a/src/app/trade/page.tsx b/src/app/trade/page.tsx index 5c81d23b..63ccd1d5 100644 --- a/src/app/trade/page.tsx +++ b/src/app/trade/page.tsx @@ -11,7 +11,10 @@ import { AccountHistory } from "../components/AccountHistory"; import { PriceInfo } from "../components/PriceInfo"; import { fetchBalances, selectPair } from "state/pairSelectorSlice"; import { useAppDispatch, useAppSelector } from "../hooks"; -import { fetchAccountHistory } from "../state/accountHistorySlice"; +import { + fetchAccountHistory, + fetchAccountHistoryAllPairs, +} from "../state/accountHistorySlice"; import { PromoBannerCarousel } from "../components/PromoBannerCarousel"; @@ -22,6 +25,10 @@ export default function Trade() { const pairName = pairSelector.name; const pairsList = pairSelector.pairsList; + const hideOtherPairs = useAppSelector( + (state) => state.accountHistory.hideOtherPairs + ); + // Detect changes in selected pair and adjust pagetitle useEffect(() => { document.title = pairName ? `DeXter • ${pairName.toUpperCase()}` : "DeXter"; @@ -42,6 +49,7 @@ export default function Trade() { } }, [pairsList, dispatch, searchParams]); + // Update orders of selected pair every 5 seconds useEffect(() => { const intervalId = setInterval(() => { dispatch(fetchBalances()); @@ -51,6 +59,22 @@ export default function Trade() { return () => clearInterval(intervalId); // Cleanup interval on component unmount }, [dispatch]); + // Update orders of all pairs every 2 mins (if selected) + useEffect(() => { + const showAllPairs = !hideOtherPairs; + if (showAllPairs) { + dispatch(fetchAccountHistoryAllPairs()); + } + + const intervalId = setInterval(() => { + if (showAllPairs) { + dispatch(fetchAccountHistoryAllPairs()); + } + }, 120000); // Dispatch every 2 mins (120 seconds) + + return () => clearInterval(intervalId); // Cleanup interval on component unmount + }, [dispatch, hideOtherPairs]); + return (