diff --git a/src/components/UI/ConnectButton/ConnectButton.tsx b/src/components/UI/ConnectButton/ConnectButton.tsx index 706bcc7..96a2618 100644 --- a/src/components/UI/ConnectButton/ConnectButton.tsx +++ b/src/components/UI/ConnectButton/ConnectButton.tsx @@ -6,8 +6,6 @@ import useUpdateUser from '@/hook/useUpdateUser'; import AlertType from '@/interfaces/common/AlertType'; import ConnectButtonProps from '@/interfaces/props/components/UI/ConnectButton/ConnectButtonProps'; import ZanoWindow from '@/interfaces/common/ZanoWindow'; -import { getSavedWalletCredentials, setWalletCredentials } from '@/utils/utils'; -import { uuid } from 'uuidv4'; import Button from '../Button/Button'; function ConnectButton(props: ConnectButtonProps) { @@ -28,41 +26,51 @@ function ConnectButton(props: ConnectButtonProps) { await (window as unknown as ZanoWindow).zano.request('GET_WALLET_DATA') ).data; - if (!walletData?.address) { + const walletAddress = walletData?.address; + const walletAlias = walletData?.alias; + + if (!walletAddress) { throw new Error('Companion is offline'); } - if (!walletData?.alias) { + if (!walletAlias) { throw new Error('Alias not found'); } - let nonce = ''; - let signature = ''; - let publicKey = ''; - - const existingWallet = getSavedWalletCredentials(); - - if (existingWallet) { - nonce = existingWallet.nonce; - signature = existingWallet.signature; - publicKey = existingWallet.publicKey; - } else { - const generatedNonce = uuid(); - const signResult = await (window as unknown as ZanoWindow).zano.request( - 'REQUEST_MESSAGE_SIGN', - { message: generatedNonce }, - null, - ); - - if (!signResult?.data?.result) { - throw new Error('Sign denied'); - } - - nonce = generatedNonce; - signature = signResult.data.result.sig; - publicKey = signResult.data.result.pkey; + if (typeof walletAddress !== 'string' || typeof walletAlias !== 'string') { + throw new Error('Invalid wallet data'); } + const authRequestRes = await fetch('/api/auth/request-auth', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + address: walletAddress, + alias: walletAlias, + }), + }).then((res) => res.json()); + + const authMessage = authRequestRes?.data; + + if (!authRequestRes.success || typeof authMessage !== 'string') { + throw new Error('Unknown error during auth request'); + } + + const signResult = await (window as unknown as ZanoWindow).zano.request( + 'REQUEST_MESSAGE_SIGN', + { message: authMessage }, + null, + ); + + if (!signResult?.data?.result) { + throw new Error('Sign denied'); + } + + const signature = signResult.data.result.sig; + const publicKey = signResult.data.result.pkey; + const result = await fetch('/api/auth', { method: 'POST', headers: { @@ -70,11 +78,11 @@ function ConnectButton(props: ConnectButtonProps) { }, body: JSON.stringify({ data: { - alias: walletData.alias, - address: walletData.address, + alias: walletAlias, + address: walletAddress, signature, publicKey, - message: nonce, + message: authMessage, }, }), }).then((res) => res.json()); @@ -83,14 +91,6 @@ function ConnectButton(props: ConnectButtonProps) { throw new Error('Server auth error'); } - if (!existingWallet) { - setWalletCredentials({ - publicKey, - signature, - nonce, - }); - } - sessionStorage.setItem('token', result?.data); updateWalletState(dispatch, { ...walletData, connected: true }); @@ -105,7 +105,6 @@ function ConnectButton(props: ConnectButtonProps) { setAlertState('error'); setAlertErrMessage((error as { message: string }).message); setTimeout(() => setAlertState(null), 3000); - setWalletCredentials(undefined); } } diff --git a/src/components/default/Header/Header.tsx b/src/components/default/Header/Header.tsx index 4006c26..6c59e82 100644 --- a/src/components/default/Header/Header.tsx +++ b/src/components/default/Header/Header.tsx @@ -18,7 +18,7 @@ import Button from '@/components/UI/Button/Button'; import { useWindowWidth } from '@react-hook/window-size'; import ConnectButton from '@/components/UI/ConnectButton/ConnectButton'; -import { classes, notationToString, setWalletCredentials, shortenAddress } from '@/utils/utils'; +import { classes, notationToString, shortenAddress } from '@/utils/utils'; import useAdvancedTheme from '@/hook/useTheme'; import { Store } from '@/store/store-reducer'; @@ -54,7 +54,6 @@ function Header({ isLg }: { isLg?: boolean }) { function logout() { sessionStorage.removeItem('token'); - setWalletCredentials(undefined); updateWalletState(dispatch, null); } diff --git a/src/components/dex/UserOrders/index.tsx b/src/components/dex/UserOrders/index.tsx index 89e1a9b..b454f4e 100644 --- a/src/components/dex/UserOrders/index.tsx +++ b/src/components/dex/UserOrders/index.tsx @@ -16,6 +16,7 @@ import { countByKeyRecord, createOrderSorter } from '@/utils/utils'; import ApplyTip from '@/interfaces/common/ApplyTip'; import { useQuerySyncedTab } from '@/hook/useQuerySyncedTab'; import { useMediaQuery } from '@/hook/useMediaQuery'; +import { GetUserOrdersBodyStatus } from '@/interfaces/fetch-data/get-user-orders/GetUserOrdersData'; import { UserOrdersProps } from './types'; import styles from './styles.module.scss'; import { @@ -73,7 +74,7 @@ const UserOrders = ({ length: offers.length, }, { - title: 'History', + title: 'History - Last 100 Items', type: 'history', length: ordersHistory.length, }, @@ -105,7 +106,13 @@ const UserOrders = ({ })(); (async () => { - const result = await getUserOrders(); + const result = await getUserOrders({ + limit: 100, + offset: 0, + filterInfo: { + status: GetUserOrdersBodyStatus.FINISHED, + }, + }); if (!result.success) { setAlertState('error'); @@ -116,9 +123,7 @@ const UserOrders = ({ return; } - const filteredOrdersHistory = result.data - .filter((s) => s.pair_id === pairData?.id) - .filter((s) => s.status === 'finished'); + const filteredOrdersHistory = result.data.filter((s) => s.status === 'finished'); fetchUser(); diff --git a/src/interfaces/fetch-data/cancel-all-orders/CancelAllData.ts b/src/interfaces/fetch-data/cancel-all-orders/CancelAllData.ts new file mode 100644 index 0000000..dfacd38 --- /dev/null +++ b/src/interfaces/fetch-data/cancel-all-orders/CancelAllData.ts @@ -0,0 +1,16 @@ +export enum CancelAllBodyOrderType { + BUY = 'buy', + SELL = 'sell', +} + +export type CancelAllData = { + filterInfo: { + pairId?: number; + type?: CancelAllBodyOrderType; + date?: { + // UNIX timestamps in milliseconds + from: number; + to: number; + }; + }; +}; diff --git a/src/interfaces/fetch-data/get-user-orders/GetUserOrdersData.ts b/src/interfaces/fetch-data/get-user-orders/GetUserOrdersData.ts new file mode 100644 index 0000000..ad917db --- /dev/null +++ b/src/interfaces/fetch-data/get-user-orders/GetUserOrdersData.ts @@ -0,0 +1,24 @@ +export enum GetUserOrdersBodyStatus { + ACTIVE = 'active', + FINISHED = 'finished', +} + +export enum GetUserOrdersBodyType { + BUY = 'buy', + SELL = 'sell', +} + +export type GetUserOrdersData = { + limit: number; + offset: number; + filterInfo: { + pairId?: number; + status?: GetUserOrdersBodyStatus; + type?: GetUserOrdersBodyType; + date?: { + // UNIX timestamps in milliseconds + from: number; + to: number; + }; + }; +}; diff --git a/src/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps.ts b/src/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps.ts index 203e7d7..d48f40a 100644 --- a/src/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps.ts +++ b/src/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps.ts @@ -1,13 +1,9 @@ -import AlertType from '@/interfaces/common/AlertType'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; -import { Dispatch, SetStateAction } from 'react'; interface OrdersTableProps { value: UserOrderData[]; category: string; - setAlertState: Dispatch>; - setAlertSubtitle: Dispatch>; - setOrders: Dispatch>; + deleteOrder: (orderId: string) => Promise; } export default OrdersTableProps; diff --git a/src/interfaces/responses/orders/CancelAllRes.ts b/src/interfaces/responses/orders/CancelAllRes.ts new file mode 100644 index 0000000..073718d --- /dev/null +++ b/src/interfaces/responses/orders/CancelAllRes.ts @@ -0,0 +1,5 @@ +interface CancelAllRes { + success: true; +} + +export default CancelAllRes; diff --git a/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts b/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts new file mode 100644 index 0000000..65c8b29 --- /dev/null +++ b/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts @@ -0,0 +1,18 @@ +export type GetUserOrdersAllPairsResPair = { + id: number; + firstCurrency: { + id: number; + ticker: string | null; + }; + secondCurrency: { + id: number; + ticker: string | null; + }; +}; + +type GetUserOrdersAllPairsRes = { + success: true; + data: GetUserOrdersAllPairsResPair[]; +}; + +export default GetUserOrdersAllPairsRes; diff --git a/src/interfaces/responses/orders/GetUserOrdersRes.ts b/src/interfaces/responses/orders/GetUserOrdersRes.ts index 4ac61ac..4e023e0 100644 --- a/src/interfaces/responses/orders/GetUserOrdersRes.ts +++ b/src/interfaces/responses/orders/GetUserOrdersRes.ts @@ -1,7 +1,28 @@ import CurrencyRow from '@/interfaces/common/CurrencyRow'; import OfferType from '@/interfaces/common/OfferType'; -interface UserOrderData { +export type GetUserOrdersResCurrency = { + id: number; + name: string; + code: string; + type: string; + asset_id: string; + auto_parsed: boolean; + asset_info?: { + asset_id: string; + logo: string; + price_url: string; + ticker: string; + full_name: string; + total_max_supply: string; + current_supply: string; + decimal_point: number; + meta_info: string; + }; + whitelisted: boolean; +}; + +export interface UserOrderData { id: string; type: OfferType; timestamp: string; @@ -15,13 +36,27 @@ interface UserOrderData { left: number; first_currency: CurrencyRow; second_currency: CurrencyRow; + + pair: { + id: number; + first_currency_id: number; + second_currency_id: number; + rate?: number; + coefficient?: number; + high?: number; + low?: number; + volume: number; + featured: boolean; + + first_currency: GetUserOrdersResCurrency; + second_currency: GetUserOrdersResCurrency; + }; } interface GetUserOrdersRes { success: true; + totalItemsCount: number; data: UserOrderData[]; } export default GetUserOrdersRes; - -export type { UserOrderData }; diff --git a/src/pages/dex/orders/OrdersTable/OrdersTable.tsx b/src/pages/dex/orders/OrdersTable/OrdersTable.tsx index dda4b21..929a97e 100644 --- a/src/pages/dex/orders/OrdersTable/OrdersTable.tsx +++ b/src/pages/dex/orders/OrdersTable/OrdersTable.tsx @@ -4,64 +4,66 @@ import DeleteIcon from '@/assets/images/UI/delete.svg'; import NoOffersIcon from '@/assets/images/UI/no_offers.svg'; import EmptyLink from '@/components/UI/EmptyLink/EmptyLink'; import { notationToString, toStandardDateString } from '@/utils/utils'; -import { cancelOrder, getUserOrders } from '@/utils/methods'; import OrdersTableProps from '@/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; import Decimal from 'decimal.js'; import Tooltip from '@/components/UI/Tooltip/Tooltip'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; +import { Store } from '@/store/store-reducer'; import styles from './OrdersTable.module.scss'; function OrdersTable(props: OrdersTableProps) { const orders = props.value || []; - const { setAlertState, setAlertSubtitle, setOrders, category } = props; + const { deleteOrder, category } = props; const isActive = category === 'active-orders'; function Row(props: { orderData: UserOrderData }) { + const { state } = useContext(Store); const { orderData } = props; const firstCurrencyName = orderData?.first_currency?.name || ''; const secondCurrencyName = orderData?.second_currency?.name || ''; + const secondCurrencyId = orderData.second_currency.asset_id ?? undefined; + const timestampDate = new Date(parseInt(orderData.timestamp, 10)); - async function deleteOrder() { - setAlertState('loading'); - setAlertSubtitle('Canceling order...'); - const result = await cancelOrder(orderData.id); + const secondAssetUsdPriceNumber = secondCurrencyId + ? state.assetsRates.get(secondCurrencyId) + : undefined; + const secondAssetUsdPrice = secondAssetUsdPriceNumber + ? new Decimal(secondAssetUsdPriceNumber) + : undefined; - if (result.success) { - setAlertState('success'); - setAlertSubtitle('Order canceled'); - } else { - setAlertState('error'); - setAlertSubtitle('Error canceling order'); - } + const pairRateNumber = orderData.pair.rate; + const pairRate = pairRateNumber !== undefined ? new Decimal(pairRateNumber) : undefined; - setTimeout(() => { - setAlertState(null); - setAlertSubtitle(''); - }, 2000); + const firstCurrencyUsdPrice = + pairRate && secondAssetUsdPrice ? pairRate.mul(secondAssetUsdPrice) : undefined; - const { success, data } = await getUserOrders(); + const actualAmount = isActive + ? new Decimal(orderData.amount) + : new Decimal(orderData.amount).minus(orderData.left); - if (success) { - setOrders(data); - } - } + const actualTotal = isActive + ? new Decimal(orderData.total) + : new Decimal(orderData.amount).minus(orderData.left).mul(orderData.price); - const amount = ( - isActive - ? new Decimal(orderData.amount) - : new Decimal(orderData.amount).minus(orderData.left) - ).toString(); - const total = ( - isActive - ? new Decimal(orderData.total) - : new Decimal(orderData.amount).minus(orderData.left).mul(orderData.price) - ).toString(); + const amountUSD = firstCurrencyUsdPrice + ? firstCurrencyUsdPrice.mul(actualAmount) + : undefined; + const priceUSD = secondAssetUsdPrice ? secondAssetUsdPrice.mul(orderData.price) : undefined; + const totalUSD = secondAssetUsdPrice ? secondAssetUsdPrice.mul(actualTotal) : undefined; + + const amountPresentation: string = notationToString(actualAmount.toFixed()); + const pricePresentation: string = notationToString(orderData.price); + const totalPresentation: string = notationToString(actualTotal.toFixed()); + + const amountUSDPresentation: string = amountUSD ? amountUSD.toFixed(2) : 'N/A'; + const priceUSDPresentation: string = priceUSD ? priceUSD.toFixed(2) : 'N/A'; + const totalUSDPresentation: string = totalUSD ? totalUSD.toFixed(2) : 'N/A'; function CurrencyTableData({ header, @@ -128,21 +130,21 @@ function OrdersTable(props: OrdersTableProps) { {isActive && ( @@ -150,7 +152,7 @@ function OrdersTable(props: OrdersTableProps) { diff --git a/src/pages/dex/orders/index.tsx b/src/pages/dex/orders/index.tsx index 66b44bd..ec3e688 100644 --- a/src/pages/dex/orders/index.tsx +++ b/src/pages/dex/orders/index.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'; import Dropdown from '@/components/UI/Dropdown/Dropdown'; import DateRangeSelector from '@/components/UI/DateRangeSelector/DateRangeSelector'; import Button from '@/components/UI/Button/Button'; -import { cancelOrder, getUserOrders } from '@/utils/methods'; +import * as fetchMethods from '@/utils/methods'; import Alert from '@/components/UI/Alert/Alert'; import AlertType from '@/interfaces/common/AlertType'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; @@ -14,8 +14,18 @@ import PairValue from '@/interfaces/props/pages/dex/orders/PairValue'; import DateState from '@/interfaces/common/DateState'; import useUpdateUser from '@/hook/useUpdateUser'; import { Footer } from '@/zano_ui/src'; +import { + GetUserOrdersBodyStatus, + GetUserOrdersBodyType, +} from '@/interfaces/fetch-data/get-user-orders/GetUserOrdersData'; +import Decimal from 'decimal.js'; +import { useInView } from 'react-intersection-observer'; +import Preloader from '@/components/UI/Preloader/Preloader'; +import { CancelAllBodyOrderType } from '@/interfaces/fetch-data/cancel-all-orders/CancelAllData'; import OrdersTable from './OrdersTable/OrdersTable'; +const ORDERS_PER_PAGE = 10; + function Orders() { const fetchUser = useUpdateUser(); @@ -42,6 +52,8 @@ function Orders() { }, ]; + const [initialized, setInitialized] = useState(false); + const [pairsValues, setPairsValues] = useState([{ name: 'All pairs', code: '' }]); const [pairDropdownValue, setPairDropdownState] = useState(pairsValues[0]); @@ -60,142 +72,325 @@ function Orders() { }); const [orders, setOrders] = useState([]); + const [lastOrderOffset, setLastOrderOffset] = useState(0); + const [totalOrdersCount, setTotalOrdersCount] = useState(undefined); + const [orderPageLoading, setOrderPageLoading] = useState(false); - useEffect(() => { - async function getOrders() { - setAlertState('loading'); - setAlertSubtitle('Loading orders data...'); + const isFinishedCategory = categoryState.code === 'history'; - const result = await getUserOrders(); + function deriveGetUserOrdersFiltersFromState() { + const status = + categoryState.code === 'active-orders' + ? GetUserOrdersBodyStatus.ACTIVE + : GetUserOrdersBodyStatus.FINISHED; - if (!result.success) { - setAlertState('error'); - setAlertSubtitle('Error loading orders data'); - await new Promise((resolve) => setTimeout(resolve, 2000)); - setAlertState(null); - setAlertSubtitle(''); + const type = (() => { + if (buyDropdownValue.name === 'Buy & Sell') { + return undefined; + } + + return buyDropdownValue.name === 'Buy' + ? GetUserOrdersBodyType.BUY + : GetUserOrdersBodyType.SELL; + })(); + + const pairId = + pairDropdownValue.code === '' + ? undefined + : new Decimal(pairDropdownValue.code).toNumber(); + + const date = (() => { + if (!dateRange.first || !dateRange.last) return undefined; + + const firstDate = new Date(dateRange.first); + const lastDate = new Date(dateRange.last); + + firstDate.setHours(0, 0, 0, 0); + lastDate.setHours(23, 59, 59, 999); + + return { + from: firstDate.getTime(), + to: lastDate.getTime(), + }; + })(); + + return { + status, + type, + pairId, + date, + }; + } + + function deriveCancelAllOrdersFiltersFromState() { + const type = (() => { + if (buyDropdownValue.name === 'Buy & Sell') { + return undefined; + } + + return buyDropdownValue.name === 'Buy' + ? CancelAllBodyOrderType.BUY + : CancelAllBodyOrderType.SELL; + })(); + + const pairId = + pairDropdownValue.code === '' + ? undefined + : new Decimal(pairDropdownValue.code).toNumber(); + + const date = (() => { + if (!dateRange.first || !dateRange.last) return undefined; + + const firstDate = new Date(dateRange.first); + const lastDate = new Date(dateRange.last); + + firstDate.setHours(0, 0, 0, 0); + lastDate.setHours(23, 59, 59, 999); + + return { + from: firstDate.getTime(), + to: lastDate.getTime(), + }; + })(); + + return { + type, + pairId, + date, + }; + } + + async function addNewOrdersPage() { + const { status, type, pairId, date } = deriveGetUserOrdersFiltersFromState(); + + const getUserOrdersRes = await fetchMethods.getUserOrders({ + limit: ORDERS_PER_PAGE, + offset: lastOrderOffset, + filterInfo: { + status, + type, + pairId, + date, + }, + }); + + if (!getUserOrdersRes.success) { + throw new Error('Error fetching user orders'); + } + + const newOrders = getUserOrdersRes.data; + const newOrdersAmount = newOrders.length; + + setOrders((prev) => [...prev, ...newOrders]); + setLastOrderOffset((prev) => prev + newOrdersAmount); + setTotalOrdersCount(getUserOrdersRes.totalItemsCount); + } + + const { ref: inViewRef } = useInView({ + threshold: 0, + onChange: async (inView) => { + if (!inView || !initialized) { return; } - fetchUser(); - - setOrders(result.data); - - function getPairsFromOrders(orders: UserOrderData[]) { - const pairs = [ - { - code: '', - name: 'All pairs', - }, - ]; - - for (let i = 0; i < orders.length; i++) { - const pair = { - name: `${orders[i].first_currency.name}/${orders[i].second_currency.name}`, - code: orders[i].pair_id, - }; - - if (!pairs.find((e) => e.code === pair.code)) pairs.push(pair); - } - - return pairs; + if (totalOrdersCount !== undefined && lastOrderOffset >= totalOrdersCount) { + return; } - const pairs = getPairsFromOrders(result.data); - - setPairsValues(pairs); - setPairDropdownState(pairs[0]); - - setAlertState(null); - setAlertSubtitle(''); - - const { success, data } = await getUserOrders(); - - if (success) { - setOrders(data); + if (orderPageLoading) { + return; } + + setOrderPageLoading(true); + + try { + await addNewOrdersPage(); + } catch (error) { + console.error('Error fetching new orders page:', error); + + setAlertState('error'); + setAlertSubtitle('Error loading more orders'); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + setAlertState(null); + setAlertSubtitle(''); + } finally { + setOrderPageLoading(false); + } + }, + }); + + async function initPairsDropdown() { + try { + const getUserOrdersAllPairsRes = await fetchMethods.getUserOrdersAllPairs(); + + if (!getUserOrdersAllPairsRes.success) { + throw new Error('Error fetching pairs for orders'); + } + + const ordersPairs = getUserOrdersAllPairsRes.data; + + const statePairs = ordersPairs.map((e) => ({ + name: `${e.firstCurrency.ticker}/${e.secondCurrency.ticker}`, + code: new Decimal(e.id).toFixed(), + })); + + setPairsValues([{ name: 'All pairs', code: '' }, ...statePairs]); + } catch (error) { + console.error('Error while initPairsDropdown:', error); + } + } + + async function initOrders() { + const { status, type, pairId, date } = deriveGetUserOrdersFiltersFromState(); + + const getUserOrdersRes = await fetchMethods.getUserOrders({ + limit: ORDERS_PER_PAGE, + offset: 0, + filterInfo: { + status, + type, + pairId, + date, + }, + }); + + if (!getUserOrdersRes.success) { + throw new Error('Error fetching user orders'); } - getOrders(); + const newOrders = getUserOrdersRes.data; + const newOrdersAmount = newOrders.length; + + setOrders(newOrders); + setLastOrderOffset(newOrdersAmount); + setTotalOrdersCount(getUserOrdersRes.totalItemsCount); + + return newOrders; + } + + async function initialize() { + try { + setAlertState('loading'); + setAlertSubtitle('Loading orders data...'); + + setOrders([]); + setLastOrderOffset(0); + setTotalOrdersCount(undefined); + + // Simulate loading time + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await fetchUser(); + + await initPairsDropdown(); + + await initOrders(); + + setInitialized(true); + setAlertState(null); + setAlertSubtitle(''); + } catch (error) { + console.error('Error during initialization:', error); + + setAlertState('error'); + setAlertSubtitle('Error loading orders data'); + await new Promise((resolve) => setTimeout(resolve, 2000)); + setAlertState(null); + setAlertSubtitle(''); + } + } + + useEffect(() => { + async function onFilterChange() { + if (!initialized) { + return; + } + + await initialize(); + } + + onFilterChange(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [buyDropdownValue, pairDropdownValue, dateRange, categoryState]); + + useEffect(() => { + initialize(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - function buySellFilter(e: UserOrderData) { - if (buyDropdownValue.name === 'Buy & Sell') return true; + async function deleteOrder(orderId: string) { + try { + setAlertState('loading'); + setAlertSubtitle('Canceling order...'); - if (buyDropdownValue.name === 'Buy') return e.type === 'buy'; + // Simulate loading time + await new Promise((resolve) => setTimeout(resolve, 500)); - if (buyDropdownValue.name === 'Sell') return e.type === 'sell'; - } + const result = await fetchMethods.cancelOrder(orderId); - function pairFilter(e: UserOrderData) { - if (!pairDropdownValue) return true; + if (!result.success) { + throw new Error('ERROR_CANCELING_ORDER'); + } - return !pairDropdownValue.code || e.pair_id === pairDropdownValue.code; - } + setAlertState('success'); + setAlertSubtitle('Order canceled'); - function dateFilter(e: UserOrderData) { - if (!dateRange.first || !dateRange.last) return true; - const firstDate = new Date(dateRange.first); - const lastDate = new Date(dateRange.last); + setTimeout(() => { + setAlertState(null); + setAlertSubtitle(''); + }, 2000); - const timestamp = parseInt(e.timestamp, 10); + setOrders((prev) => prev.filter((e) => e.id !== orderId)); + setLastOrderOffset((prev) => Math.max(prev - 1, 0)); + setTotalOrdersCount((prev) => (prev !== undefined ? prev - 1 : prev)); + } catch (error) { + console.error('Error canceling order:', error); - firstDate.setHours(0, 0, 0, 0); - lastDate.setHours(24, 0, 0, 0); + setAlertState('error'); + setAlertSubtitle('Error canceling order'); - if (!dateRange.first && !dateRange.last) return true; - - if (dateRange.first && !dateRange.last) return timestamp >= firstDate.getTime(); - - if (!dateRange.first && dateRange.last) return timestamp <= lastDate.getTime(); - - return timestamp >= firstDate.getTime() && timestamp <= lastDate.getTime(); - } - - function categoryFilter(e: UserOrderData) { - if (categoryState.code === 'active-orders') { - return e.status === 'active'; + setTimeout(() => { + setAlertState(null); + setAlertSubtitle(''); + }, 2000); } - return e.status === 'finished'; } - const activeOrders = orders.filter((e) => e.status === 'active'); - async function cancelAllOrders() { - setAlertState('loading'); - setAlertSubtitle('Canceling all orders...'); + try { + setAlertState('loading'); + setAlertSubtitle('Canceling all orders...'); - // const results = await Promise.allSettled( - // activeOrders.map(async (e) => { - // await cancelOrder(e.id); - // }), - // ); + // Simulate loading time + await new Promise((resolve) => setTimeout(resolve, 500)); - const results = await (async () => { - const res = []; - for (const order of activeOrders) { - res.push(await cancelOrder(order.id).catch(() => null)); + const { type, pairId, date } = deriveCancelAllOrdersFiltersFromState(); + + const cancelAllRes = await fetchMethods.cancelAllOrders({ + filterInfo: { + type, + pairId, + date, + }, + }); + + if (!cancelAllRes.success) { + throw new Error('Error canceling all orders'); } - return res; - })(); - if (results.some((e) => e === null)) { + await initialize(); + } catch (error) { + console.error('Error canceling all orders:', error); + setAlertState('error'); - setAlertSubtitle('Some of the orders were not canceled'); - } else { - setAlertState('success'); - setAlertSubtitle('All orders canceled'); - } + setAlertSubtitle('Error canceling all orders'); - setTimeout(() => { - setAlertState(null); - setAlertSubtitle(''); - }, 2000); - - const { success, data } = await getUserOrders(); - - if (success) { - setOrders(data); + setTimeout(() => { + setAlertState(null); + setAlertSubtitle(''); + }, 2000); } } @@ -243,23 +438,23 @@ function Orders() { - + {!isFinishedCategory && ( + + )} + +
+ {orderPageLoading && } +
{alertState && ( res.data); } -export async function getUserOrders(): Promise { +export async function getUserOrders({ + limit, + offset, + filterInfo: { pairId, status, type, date }, +}: GetUserOrdersData): Promise { return axios - .post('/api/orders/get', { + .patch('/api/orders/get', { + token: sessionStorage.getItem('token'), + + limit, + offset, + filterInfo: { + pairId, + status, + type, + date: date + ? { + from: date.from, + to: date.to, + } + : undefined, + }, + }) + .then((res) => res.data); +} + +export async function getUserOrdersAllPairs(): Promise { + return axios + .patch('/api/orders/get-user-orders-pairs', { token: sessionStorage.getItem('token'), }) .then((res) => res.data); @@ -309,6 +339,26 @@ export async function applyOrder(orderData: ApplyOrderData): Promise res.data); } +export async function cancelAllOrders({ + filterInfo: { pairId, type, date }, +}: CancelAllData): Promise { + return axios + .patch('/api/orders/cancel-all', { + token: sessionStorage.getItem('token'), + filterInfo: { + pairId, + type, + date: date + ? { + from: date.from, + to: date.to, + } + : undefined, + }, + }) + .then((res) => res.data); +} + export async function confirmTransaction( transactionId: string, ): Promise { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index a41d021..6dffb62 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -111,16 +111,6 @@ export const localeTimeLeft = (now: number | null, timestamp: number) => { return `${intToStrFixedLen(hours)}:${intToStrFixedLen(minutes)}:${intToStrFixedLen(seconds)}`; }; -export function getSavedWalletCredentials() { - const savedWallet = localStorage.getItem('wallet'); - if (!savedWallet) return undefined; - try { - return JSON.parse(savedWallet) as WalletCredentials; - } catch { - return undefined; - } -} - export function setWalletCredentials(credentials: WalletCredentials | undefined) { if (credentials) { localStorage.setItem('wallet', JSON.stringify(credentials));