Merge pull request #57 from hyle-team/dev

Dev
This commit is contained in:
Dmitrii Kolpakov 2026-02-25 11:52:19 +01:00 committed by GitHub
commit 3cafe3c892
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 564 additions and 225 deletions

View file

@ -6,8 +6,6 @@ import useUpdateUser from '@/hook/useUpdateUser';
import AlertType from '@/interfaces/common/AlertType'; import AlertType from '@/interfaces/common/AlertType';
import ConnectButtonProps from '@/interfaces/props/components/UI/ConnectButton/ConnectButtonProps'; import ConnectButtonProps from '@/interfaces/props/components/UI/ConnectButton/ConnectButtonProps';
import ZanoWindow from '@/interfaces/common/ZanoWindow'; import ZanoWindow from '@/interfaces/common/ZanoWindow';
import { getSavedWalletCredentials, setWalletCredentials } from '@/utils/utils';
import { uuid } from 'uuidv4';
import Button from '../Button/Button'; import Button from '../Button/Button';
function ConnectButton(props: ConnectButtonProps) { function ConnectButton(props: ConnectButtonProps) {
@ -28,41 +26,51 @@ function ConnectButton(props: ConnectButtonProps) {
await (window as unknown as ZanoWindow).zano.request('GET_WALLET_DATA') await (window as unknown as ZanoWindow).zano.request('GET_WALLET_DATA')
).data; ).data;
if (!walletData?.address) { const walletAddress = walletData?.address;
const walletAlias = walletData?.alias;
if (!walletAddress) {
throw new Error('Companion is offline'); throw new Error('Companion is offline');
} }
if (!walletData?.alias) { if (!walletAlias) {
throw new Error('Alias not found'); throw new Error('Alias not found');
} }
let nonce = ''; if (typeof walletAddress !== 'string' || typeof walletAlias !== 'string') {
let signature = ''; throw new Error('Invalid wallet data');
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;
} }
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', { const result = await fetch('/api/auth', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -70,11 +78,11 @@ function ConnectButton(props: ConnectButtonProps) {
}, },
body: JSON.stringify({ body: JSON.stringify({
data: { data: {
alias: walletData.alias, alias: walletAlias,
address: walletData.address, address: walletAddress,
signature, signature,
publicKey, publicKey,
message: nonce, message: authMessage,
}, },
}), }),
}).then((res) => res.json()); }).then((res) => res.json());
@ -83,14 +91,6 @@ function ConnectButton(props: ConnectButtonProps) {
throw new Error('Server auth error'); throw new Error('Server auth error');
} }
if (!existingWallet) {
setWalletCredentials({
publicKey,
signature,
nonce,
});
}
sessionStorage.setItem('token', result?.data); sessionStorage.setItem('token', result?.data);
updateWalletState(dispatch, { ...walletData, connected: true }); updateWalletState(dispatch, { ...walletData, connected: true });
@ -105,7 +105,6 @@ function ConnectButton(props: ConnectButtonProps) {
setAlertState('error'); setAlertState('error');
setAlertErrMessage((error as { message: string }).message); setAlertErrMessage((error as { message: string }).message);
setTimeout(() => setAlertState(null), 3000); setTimeout(() => setAlertState(null), 3000);
setWalletCredentials(undefined);
} }
} }

View file

@ -18,7 +18,7 @@ import Button from '@/components/UI/Button/Button';
import { useWindowWidth } from '@react-hook/window-size'; import { useWindowWidth } from '@react-hook/window-size';
import ConnectButton from '@/components/UI/ConnectButton/ConnectButton'; 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 useAdvancedTheme from '@/hook/useTheme';
import { Store } from '@/store/store-reducer'; import { Store } from '@/store/store-reducer';
@ -54,7 +54,6 @@ function Header({ isLg }: { isLg?: boolean }) {
function logout() { function logout() {
sessionStorage.removeItem('token'); sessionStorage.removeItem('token');
setWalletCredentials(undefined);
updateWalletState(dispatch, null); updateWalletState(dispatch, null);
} }

View file

@ -16,6 +16,7 @@ import { countByKeyRecord, createOrderSorter } from '@/utils/utils';
import ApplyTip from '@/interfaces/common/ApplyTip'; import ApplyTip from '@/interfaces/common/ApplyTip';
import { useQuerySyncedTab } from '@/hook/useQuerySyncedTab'; import { useQuerySyncedTab } from '@/hook/useQuerySyncedTab';
import { useMediaQuery } from '@/hook/useMediaQuery'; import { useMediaQuery } from '@/hook/useMediaQuery';
import { GetUserOrdersBodyStatus } from '@/interfaces/fetch-data/get-user-orders/GetUserOrdersData';
import { UserOrdersProps } from './types'; import { UserOrdersProps } from './types';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { import {
@ -73,7 +74,7 @@ const UserOrders = ({
length: offers.length, length: offers.length,
}, },
{ {
title: 'History', title: 'History - Last 100 Items',
type: 'history', type: 'history',
length: ordersHistory.length, length: ordersHistory.length,
}, },
@ -105,7 +106,13 @@ const UserOrders = ({
})(); })();
(async () => { (async () => {
const result = await getUserOrders(); const result = await getUserOrders({
limit: 100,
offset: 0,
filterInfo: {
status: GetUserOrdersBodyStatus.FINISHED,
},
});
if (!result.success) { if (!result.success) {
setAlertState('error'); setAlertState('error');
@ -116,9 +123,7 @@ const UserOrders = ({
return; return;
} }
const filteredOrdersHistory = result.data const filteredOrdersHistory = result.data.filter((s) => s.status === 'finished');
.filter((s) => s.pair_id === pairData?.id)
.filter((s) => s.status === 'finished');
fetchUser(); fetchUser();

View file

@ -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;
};
};
};

View file

@ -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;
};
};
};

View file

@ -1,13 +1,9 @@
import AlertType from '@/interfaces/common/AlertType';
import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes';
import { Dispatch, SetStateAction } from 'react';
interface OrdersTableProps { interface OrdersTableProps {
value: UserOrderData[]; value: UserOrderData[];
category: string; category: string;
setAlertState: Dispatch<SetStateAction<AlertType>>; deleteOrder: (orderId: string) => Promise<void>;
setAlertSubtitle: Dispatch<SetStateAction<string>>;
setOrders: Dispatch<SetStateAction<UserOrderData[]>>;
} }
export default OrdersTableProps; export default OrdersTableProps;

View file

@ -0,0 +1,5 @@
interface CancelAllRes {
success: true;
}
export default CancelAllRes;

View file

@ -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;

View file

@ -1,7 +1,28 @@
import CurrencyRow from '@/interfaces/common/CurrencyRow'; import CurrencyRow from '@/interfaces/common/CurrencyRow';
import OfferType from '@/interfaces/common/OfferType'; 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; id: string;
type: OfferType; type: OfferType;
timestamp: string; timestamp: string;
@ -15,13 +36,27 @@ interface UserOrderData {
left: number; left: number;
first_currency: CurrencyRow; first_currency: CurrencyRow;
second_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 { interface GetUserOrdersRes {
success: true; success: true;
totalItemsCount: number;
data: UserOrderData[]; data: UserOrderData[];
} }
export default GetUserOrdersRes; export default GetUserOrdersRes;
export type { UserOrderData };

View file

@ -4,64 +4,66 @@ import DeleteIcon from '@/assets/images/UI/delete.svg';
import NoOffersIcon from '@/assets/images/UI/no_offers.svg'; import NoOffersIcon from '@/assets/images/UI/no_offers.svg';
import EmptyLink from '@/components/UI/EmptyLink/EmptyLink'; import EmptyLink from '@/components/UI/EmptyLink/EmptyLink';
import { notationToString, toStandardDateString } from '@/utils/utils'; import { notationToString, toStandardDateString } from '@/utils/utils';
import { cancelOrder, getUserOrders } from '@/utils/methods';
import OrdersTableProps from '@/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps'; import OrdersTableProps from '@/interfaces/props/pages/dex/orders/OrdersTable/OrdersTableProps';
import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
import Tooltip from '@/components/UI/Tooltip/Tooltip'; 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'; import styles from './OrdersTable.module.scss';
function OrdersTable(props: OrdersTableProps) { function OrdersTable(props: OrdersTableProps) {
const orders = props.value || []; const orders = props.value || [];
const { setAlertState, setAlertSubtitle, setOrders, category } = props; const { deleteOrder, category } = props;
const isActive = category === 'active-orders'; const isActive = category === 'active-orders';
function Row(props: { orderData: UserOrderData }) { function Row(props: { orderData: UserOrderData }) {
const { state } = useContext(Store);
const { orderData } = props; const { orderData } = props;
const firstCurrencyName = orderData?.first_currency?.name || ''; const firstCurrencyName = orderData?.first_currency?.name || '';
const secondCurrencyName = orderData?.second_currency?.name || ''; const secondCurrencyName = orderData?.second_currency?.name || '';
const secondCurrencyId = orderData.second_currency.asset_id ?? undefined;
const timestampDate = new Date(parseInt(orderData.timestamp, 10)); const timestampDate = new Date(parseInt(orderData.timestamp, 10));
async function deleteOrder() { const secondAssetUsdPriceNumber = secondCurrencyId
setAlertState('loading'); ? state.assetsRates.get(secondCurrencyId)
setAlertSubtitle('Canceling order...'); : undefined;
const result = await cancelOrder(orderData.id); const secondAssetUsdPrice = secondAssetUsdPriceNumber
? new Decimal(secondAssetUsdPriceNumber)
: undefined;
if (result.success) { const pairRateNumber = orderData.pair.rate;
setAlertState('success'); const pairRate = pairRateNumber !== undefined ? new Decimal(pairRateNumber) : undefined;
setAlertSubtitle('Order canceled');
} else {
setAlertState('error');
setAlertSubtitle('Error canceling order');
}
setTimeout(() => { const firstCurrencyUsdPrice =
setAlertState(null); pairRate && secondAssetUsdPrice ? pairRate.mul(secondAssetUsdPrice) : undefined;
setAlertSubtitle('');
}, 2000);
const { success, data } = await getUserOrders(); const actualAmount = isActive
? new Decimal(orderData.amount)
: new Decimal(orderData.amount).minus(orderData.left);
if (success) { const actualTotal = isActive
setOrders(data); ? new Decimal(orderData.total)
} : new Decimal(orderData.amount).minus(orderData.left).mul(orderData.price);
}
const amount = ( const amountUSD = firstCurrencyUsdPrice
isActive ? firstCurrencyUsdPrice.mul(actualAmount)
? new Decimal(orderData.amount) : undefined;
: new Decimal(orderData.amount).minus(orderData.left) const priceUSD = secondAssetUsdPrice ? secondAssetUsdPrice.mul(orderData.price) : undefined;
).toString(); const totalUSD = secondAssetUsdPrice ? secondAssetUsdPrice.mul(actualTotal) : undefined;
const total = (
isActive const amountPresentation: string = notationToString(actualAmount.toFixed());
? new Decimal(orderData.total) const pricePresentation: string = notationToString(orderData.price);
: new Decimal(orderData.amount).minus(orderData.left).mul(orderData.price) const totalPresentation: string = notationToString(actualTotal.toFixed());
).toString();
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({ function CurrencyTableData({
header, header,
@ -128,21 +130,21 @@ function OrdersTable(props: OrdersTableProps) {
</td> </td>
<CurrencyTableData <CurrencyTableData
price={notationToString(5.23)} price={priceUSDPresentation}
header="Price" header="Price"
value={notationToString(orderData.price)} value={pricePresentation}
currency={secondCurrencyName} currency={secondCurrencyName}
/> />
<CurrencyTableData <CurrencyTableData
price={notationToString(5.23)} price={amountUSDPresentation}
header="Amount" header="Amount"
value={notationToString(amount)} value={amountPresentation}
currency={firstCurrencyName} currency={firstCurrencyName}
/> />
<CurrencyTableData <CurrencyTableData
price={notationToString(5.23)} price={totalUSDPresentation}
header="Total" header="Total"
value={notationToString(total)} value={totalPresentation}
currency={secondCurrencyName} currency={secondCurrencyName}
/> />
{isActive && ( {isActive && (
@ -150,7 +152,7 @@ function OrdersTable(props: OrdersTableProps) {
<Button <Button
key={nanoid(16)} key={nanoid(16)}
className={styles.delete__button} className={styles.delete__button}
onClick={deleteOrder} onClick={() => deleteOrder(orderData.id)}
> >
<DeleteIcon /> <DeleteIcon />
</Button> </Button>

View file

@ -6,7 +6,7 @@ import { useEffect, useState } from 'react';
import Dropdown from '@/components/UI/Dropdown/Dropdown'; import Dropdown from '@/components/UI/Dropdown/Dropdown';
import DateRangeSelector from '@/components/UI/DateRangeSelector/DateRangeSelector'; import DateRangeSelector from '@/components/UI/DateRangeSelector/DateRangeSelector';
import Button from '@/components/UI/Button/Button'; 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 Alert from '@/components/UI/Alert/Alert';
import AlertType from '@/interfaces/common/AlertType'; import AlertType from '@/interfaces/common/AlertType';
import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; 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 DateState from '@/interfaces/common/DateState';
import useUpdateUser from '@/hook/useUpdateUser'; import useUpdateUser from '@/hook/useUpdateUser';
import { Footer } from '@/zano_ui/src'; 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'; import OrdersTable from './OrdersTable/OrdersTable';
const ORDERS_PER_PAGE = 10;
function Orders() { function Orders() {
const fetchUser = useUpdateUser(); const fetchUser = useUpdateUser();
@ -42,6 +52,8 @@ function Orders() {
}, },
]; ];
const [initialized, setInitialized] = useState(false);
const [pairsValues, setPairsValues] = useState<PairValue[]>([{ name: 'All pairs', code: '' }]); const [pairsValues, setPairsValues] = useState<PairValue[]>([{ name: 'All pairs', code: '' }]);
const [pairDropdownValue, setPairDropdownState] = useState(pairsValues[0]); const [pairDropdownValue, setPairDropdownState] = useState(pairsValues[0]);
@ -60,142 +72,325 @@ function Orders() {
}); });
const [orders, setOrders] = useState<UserOrderData[]>([]); const [orders, setOrders] = useState<UserOrderData[]>([]);
const [lastOrderOffset, setLastOrderOffset] = useState(0);
const [totalOrdersCount, setTotalOrdersCount] = useState<number | undefined>(undefined);
const [orderPageLoading, setOrderPageLoading] = useState(false);
useEffect(() => { const isFinishedCategory = categoryState.code === 'history';
async function getOrders() {
setAlertState('loading');
setAlertSubtitle('Loading orders data...');
const result = await getUserOrders(); function deriveGetUserOrdersFiltersFromState() {
const status =
categoryState.code === 'active-orders'
? GetUserOrdersBodyStatus.ACTIVE
: GetUserOrdersBodyStatus.FINISHED;
if (!result.success) { const type = (() => {
setAlertState('error'); if (buyDropdownValue.name === 'Buy & Sell') {
setAlertSubtitle('Error loading orders data'); return undefined;
await new Promise((resolve) => setTimeout(resolve, 2000)); }
setAlertState(null);
setAlertSubtitle(''); 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; return;
} }
fetchUser(); if (totalOrdersCount !== undefined && lastOrderOffset >= totalOrdersCount) {
return;
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;
} }
const pairs = getPairsFromOrders(result.data); if (orderPageLoading) {
return;
setPairsValues(pairs);
setPairDropdownState(pairs[0]);
setAlertState(null);
setAlertSubtitle('');
const { success, data } = await getUserOrders();
if (success) {
setOrders(data);
} }
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) { async function deleteOrder(orderId: string) {
if (buyDropdownValue.name === 'Buy & Sell') return true; 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 (!result.success) {
if (!pairDropdownValue) return true; throw new Error('ERROR_CANCELING_ORDER');
}
return !pairDropdownValue.code || e.pair_id === pairDropdownValue.code; setAlertState('success');
} setAlertSubtitle('Order canceled');
function dateFilter(e: UserOrderData) { setTimeout(() => {
if (!dateRange.first || !dateRange.last) return true; setAlertState(null);
const firstDate = new Date(dateRange.first); setAlertSubtitle('');
const lastDate = new Date(dateRange.last); }, 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); setAlertState('error');
lastDate.setHours(24, 0, 0, 0); setAlertSubtitle('Error canceling order');
if (!dateRange.first && !dateRange.last) return true; setTimeout(() => {
setAlertState(null);
if (dateRange.first && !dateRange.last) return timestamp >= firstDate.getTime(); setAlertSubtitle('');
}, 2000);
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';
} }
return e.status === 'finished';
} }
const activeOrders = orders.filter((e) => e.status === 'active');
async function cancelAllOrders() { async function cancelAllOrders() {
setAlertState('loading'); try {
setAlertSubtitle('Canceling all orders...'); setAlertState('loading');
setAlertSubtitle('Canceling all orders...');
// const results = await Promise.allSettled( // Simulate loading time
// activeOrders.map(async (e) => { await new Promise((resolve) => setTimeout(resolve, 500));
// await cancelOrder(e.id);
// }),
// );
const results = await (async () => { const { type, pairId, date } = deriveCancelAllOrdersFiltersFromState();
const res = [];
for (const order of activeOrders) { const cancelAllRes = await fetchMethods.cancelAllOrders({
res.push(await cancelOrder(order.id).catch(() => null)); 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'); setAlertState('error');
setAlertSubtitle('Some of the orders were not canceled'); setAlertSubtitle('Error canceling all orders');
} else {
setAlertState('success');
setAlertSubtitle('All orders canceled');
}
setTimeout(() => { setTimeout(() => {
setAlertState(null); setAlertState(null);
setAlertSubtitle(''); setAlertSubtitle('');
}, 2000); }, 2000);
const { success, data } = await getUserOrders();
if (success) {
setOrders(data);
} }
} }
@ -243,23 +438,23 @@ function Orders() {
<DateRangeSelector value={dateRange} setValue={setDateRange} /> <DateRangeSelector value={dateRange} setValue={setDateRange} />
</div> </div>
<Button transparent onClick={cancelAllOrders}> {!isFinishedCategory && (
Cancel all orders <Button transparent onClick={cancelAllOrders}>
</Button> Cancel all orders
</Button>
)}
</div> </div>
</div> </div>
<OrdersTable <OrdersTable
value={orders value={orders}
.filter(buySellFilter)
.filter(pairFilter)
.filter(dateFilter)
.filter(categoryFilter)}
setAlertState={setAlertState}
setAlertSubtitle={setAlertSubtitle}
setOrders={setOrders}
category={categoryState.code} category={categoryState.code}
deleteOrder={deleteOrder}
/> />
<div className={styles['orders__preloader-wrapper']} ref={inViewRef}>
{orderPageLoading && <Preloader />}
</div>
</div> </div>
{alertState && ( {alertState && (
<Alert <Alert

View file

@ -92,5 +92,10 @@
} }
} }
} }
.orders__preloader-wrapper {
display: flex;
justify-content: center;
}
} }
} }

View file

@ -25,6 +25,10 @@ import axios from 'axios';
import GetPairsPagesAmountRes from '@/interfaces/responses/dex/GetPairsPagesAmountRes'; import GetPairsPagesAmountRes from '@/interfaces/responses/dex/GetPairsPagesAmountRes';
import { PairSortOption } from '@/interfaces/enum/pair'; import { PairSortOption } from '@/interfaces/enum/pair';
import { API_URL } from '@/constants'; import { API_URL } from '@/constants';
import { GetUserOrdersData } from '@/interfaces/fetch-data/get-user-orders/GetUserOrdersData';
import GetUserOrdersAllPairsRes from '@/interfaces/responses/orders/GetUserOrdersAllPairsRes';
import { CancelAllData } from '@/interfaces/fetch-data/cancel-all-orders/CancelAllData';
import CancelAllRes from '@/interfaces/responses/orders/CancelAllRes';
const isServer = typeof window === 'undefined'; const isServer = typeof window === 'undefined';
const baseUrl = isServer ? API_URL : ''; const baseUrl = isServer ? API_URL : '';
@ -246,9 +250,35 @@ export async function getUserOrdersPage(pairId: string): Promise<ErrorRes | GetU
.then((res) => res.data); .then((res) => res.data);
} }
export async function getUserOrders(): Promise<ErrorRes | GetUserOrdersRes> { export async function getUserOrders({
limit,
offset,
filterInfo: { pairId, status, type, date },
}: GetUserOrdersData): Promise<ErrorRes | GetUserOrdersRes> {
return axios 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<ErrorRes | GetUserOrdersAllPairsRes> {
return axios
.patch('/api/orders/get-user-orders-pairs', {
token: sessionStorage.getItem('token'), token: sessionStorage.getItem('token'),
}) })
.then((res) => res.data); .then((res) => res.data);
@ -309,6 +339,26 @@ export async function applyOrder(orderData: ApplyOrderData): Promise<ErrorRes |
.then((res) => res.data); .then((res) => res.data);
} }
export async function cancelAllOrders({
filterInfo: { pairId, type, date },
}: CancelAllData): Promise<ErrorRes | CancelAllRes> {
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( export async function confirmTransaction(
transactionId: string, transactionId: string,
): Promise<ErrorRes | { success: true }> { ): Promise<ErrorRes | { success: true }> {

View file

@ -111,16 +111,6 @@ export const localeTimeLeft = (now: number | null, timestamp: number) => {
return `${intToStrFixedLen(hours)}:${intToStrFixedLen(minutes)}:${intToStrFixedLen(seconds)}`; 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) { export function setWalletCredentials(credentials: WalletCredentials | undefined) {
if (credentials) { if (credentials) {
localStorage.setItem('wallet', JSON.stringify(credentials)); localStorage.setItem('wallet', JSON.stringify(credentials));