Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pairs toggle #540

Merged
merged 12 commits into from
Aug 4, 2024
62 changes: 59 additions & 3 deletions src/app/components/AccountHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> {
Expand Down Expand Up @@ -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<HTMLInputElement>) => {
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: <OpenOrdersRows data={openOrders} />,
rows: <OpenOrdersRows data={filteredRowsForOpenOrders} />,
};

case Tables.ORDER_HISTORY:
const filteredRowsForOrderHistory = hideOtherPairs
? orderHistory
: combinedOrderHistory;
return {
headers: headers[Tables.ORDER_HISTORY],
rows: <OrderHistoryRows data={orderHistory} />,
rows: <OrderHistoryRows data={filteredRowsForOrderHistory} />,
};

default:
Expand All @@ -222,10 +244,44 @@ function DisplayTable() {
rows: <></>,
};
}
}, [openOrders, orderHistory, selectedTable]);
}, [
openOrders,
orderHistory,
selectedTable,
hideOtherPairs,
combinedOrderHistory,
combinedOpenOrders,
]);

return (
<div className="overflow-x-auto scrollbar-none">
<div className="flex flex-col md:items-end xs:items-start">
<label className="label cursor-pointer">
<input
type="checkbox"
className="peer hidden"
checked={hideOtherPairs}
role="switch"
onChange={handleToggleChange}
/>
<span
className={`relative inline-flex items-center w-8 h-4 rounded-lg transition-colors duration-300 ease-in-out xs:ml-4 ${
hideOtherPairs ? "bg-content-dark" : "border border-white"
}`}
>
<span
className={`absolute left-0.5 top-0.3 w-3 h-3 rounded-lg shadow-md transform transition-transform duration-300 ease-in-out ${
hideOtherPairs
? "translate-x-4 bg-dexter-green"
: "translate-x-0 bg-gray-200"
}`}
/>
</span>
<span className="label-text text-xs md:pl-0 mr-16 xs:ml-2">
Hide other pairs
</span>
</label>
</div>
<table className="table table-zebra table-xs !mt-0 mb-16 w-full max-w-[100%]">
<thead>
<tr className="h-12">
Expand Down
119 changes: 111 additions & 8 deletions src/app/state/accountHistorySlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export interface AccountHistoryState {
selectedTable: Tables;
tables: Tables[];
selectedOrdersToCancel: Record<string, Order>; // the key is `${orderRecieptAddress}_${nftRecieptId}`
hideOtherPairs: boolean;
orderHistoryAllPairs: adex.OrderReceipt[];
}

// INITIAL STATE
Expand All @@ -40,6 +42,8 @@ const initialState: AccountHistoryState = {
selectedTable: Tables.OPEN_ORDERS,
tables: Object.values(Tables),
selectedOrdersToCancel: {},
hideOtherPairs: true,
orderHistoryAllPairs: [],
};

// ASYNC THUNKS
Expand All @@ -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
Expand Down Expand Up @@ -170,21 +208,35 @@ export const accountHistorySlice = createSlice({
resetSelectedOrdersToCancel: (state) => {
state.selectedOrdersToCancel = {};
},
setHideOtherPairs: (state, action: PayloadAction<boolean>) => {
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(
Expand Down Expand Up @@ -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<string, adex.OrderReceipt>();

// 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;
26 changes: 25 additions & 1 deletion src/app/trade/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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";
Expand All @@ -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());
Expand All @@ -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 (
<div className="grow">
<PromoBannerCarousel
Expand Down
Loading