commit
3cafe3c892
14 changed files with 564 additions and 225 deletions
|
|
@ -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,29 +26,41 @@ 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 = '';
|
||||
if (typeof walletAddress !== 'string' || typeof walletAlias !== 'string') {
|
||||
throw new Error('Invalid wallet data');
|
||||
}
|
||||
|
||||
const existingWallet = getSavedWalletCredentials();
|
||||
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');
|
||||
}
|
||||
|
||||
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 },
|
||||
{ message: authMessage },
|
||||
null,
|
||||
);
|
||||
|
||||
|
|
@ -58,10 +68,8 @@ function ConnectButton(props: ConnectButtonProps) {
|
|||
throw new Error('Sign denied');
|
||||
}
|
||||
|
||||
nonce = generatedNonce;
|
||||
signature = signResult.data.result.sig;
|
||||
publicKey = signResult.data.result.pkey;
|
||||
}
|
||||
const signature = signResult.data.result.sig;
|
||||
const publicKey = signResult.data.result.pkey;
|
||||
|
||||
const result = await fetch('/api/auth', {
|
||||
method: 'POST',
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
16
src/interfaces/fetch-data/cancel-all-orders/CancelAllData.ts
Normal file
16
src/interfaces/fetch-data/cancel-all-orders/CancelAllData.ts
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -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<SetStateAction<AlertType>>;
|
||||
setAlertSubtitle: Dispatch<SetStateAction<string>>;
|
||||
setOrders: Dispatch<SetStateAction<UserOrderData[]>>;
|
||||
deleteOrder: (orderId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export default OrdersTableProps;
|
||||
|
|
|
|||
5
src/interfaces/responses/orders/CancelAllRes.ts
Normal file
5
src/interfaces/responses/orders/CancelAllRes.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
interface CancelAllRes {
|
||||
success: true;
|
||||
}
|
||||
|
||||
export default CancelAllRes;
|
||||
18
src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts
Normal file
18
src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts
Normal 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;
|
||||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
if (success) {
|
||||
setOrders(data);
|
||||
}
|
||||
}
|
||||
|
||||
const amount = (
|
||||
isActive
|
||||
const actualAmount = isActive
|
||||
? new Decimal(orderData.amount)
|
||||
: new Decimal(orderData.amount).minus(orderData.left)
|
||||
).toString();
|
||||
const total = (
|
||||
isActive
|
||||
: new Decimal(orderData.amount).minus(orderData.left);
|
||||
|
||||
const actualTotal = isActive
|
||||
? new Decimal(orderData.total)
|
||||
: new Decimal(orderData.amount).minus(orderData.left).mul(orderData.price)
|
||||
).toString();
|
||||
: new Decimal(orderData.amount).minus(orderData.left).mul(orderData.price);
|
||||
|
||||
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) {
|
|||
</td>
|
||||
|
||||
<CurrencyTableData
|
||||
price={notationToString(5.23)}
|
||||
price={priceUSDPresentation}
|
||||
header="Price"
|
||||
value={notationToString(orderData.price)}
|
||||
value={pricePresentation}
|
||||
currency={secondCurrencyName}
|
||||
/>
|
||||
<CurrencyTableData
|
||||
price={notationToString(5.23)}
|
||||
price={amountUSDPresentation}
|
||||
header="Amount"
|
||||
value={notationToString(amount)}
|
||||
value={amountPresentation}
|
||||
currency={firstCurrencyName}
|
||||
/>
|
||||
<CurrencyTableData
|
||||
price={notationToString(5.23)}
|
||||
price={totalUSDPresentation}
|
||||
header="Total"
|
||||
value={notationToString(total)}
|
||||
value={totalPresentation}
|
||||
currency={secondCurrencyName}
|
||||
/>
|
||||
{isActive && (
|
||||
|
|
@ -150,7 +152,7 @@ function OrdersTable(props: OrdersTableProps) {
|
|||
<Button
|
||||
key={nanoid(16)}
|
||||
className={styles.delete__button}
|
||||
onClick={deleteOrder}
|
||||
onClick={() => deleteOrder(orderData.id)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<PairValue[]>([{ name: 'All pairs', code: '' }]);
|
||||
|
||||
const [pairDropdownValue, setPairDropdownState] = useState(pairsValues[0]);
|
||||
|
|
@ -60,142 +72,325 @@ function Orders() {
|
|||
});
|
||||
|
||||
const [orders, setOrders] = useState<UserOrderData[]>([]);
|
||||
const [lastOrderOffset, setLastOrderOffset] = useState(0);
|
||||
const [totalOrdersCount, setTotalOrdersCount] = useState<number | undefined>(undefined);
|
||||
const [orderPageLoading, setOrderPageLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function getOrders() {
|
||||
const isFinishedCategory = categoryState.code === 'history';
|
||||
|
||||
function deriveGetUserOrdersFiltersFromState() {
|
||||
const status =
|
||||
categoryState.code === 'active-orders'
|
||||
? GetUserOrdersBodyStatus.ACTIVE
|
||||
: GetUserOrdersBodyStatus.FINISHED;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (totalOrdersCount !== undefined && lastOrderOffset >= totalOrdersCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
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...');
|
||||
|
||||
const result = await getUserOrders();
|
||||
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);
|
||||
|
||||
if (!result.success) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error loading orders data');
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function onFilterChange() {
|
||||
if (!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);
|
||||
await initialize();
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
onFilterChange();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [buyDropdownValue, pairDropdownValue, dateRange, categoryState]);
|
||||
|
||||
const pairs = getPairsFromOrders(result.data);
|
||||
|
||||
setPairsValues(pairs);
|
||||
setPairDropdownState(pairs[0]);
|
||||
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
|
||||
const { success, data } = await getUserOrders();
|
||||
|
||||
if (success) {
|
||||
setOrders(data);
|
||||
}
|
||||
}
|
||||
|
||||
getOrders();
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
function buySellFilter(e: UserOrderData) {
|
||||
if (buyDropdownValue.name === 'Buy & Sell') return true;
|
||||
|
||||
if (buyDropdownValue.name === 'Buy') return e.type === 'buy';
|
||||
|
||||
if (buyDropdownValue.name === 'Sell') return e.type === 'sell';
|
||||
}
|
||||
|
||||
function pairFilter(e: UserOrderData) {
|
||||
if (!pairDropdownValue) return true;
|
||||
|
||||
return !pairDropdownValue.code || e.pair_id === pairDropdownValue.code;
|
||||
}
|
||||
|
||||
function dateFilter(e: UserOrderData) {
|
||||
if (!dateRange.first || !dateRange.last) return true;
|
||||
const firstDate = new Date(dateRange.first);
|
||||
const lastDate = new Date(dateRange.last);
|
||||
|
||||
const timestamp = parseInt(e.timestamp, 10);
|
||||
|
||||
firstDate.setHours(0, 0, 0, 0);
|
||||
lastDate.setHours(24, 0, 0, 0);
|
||||
|
||||
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';
|
||||
}
|
||||
return e.status === 'finished';
|
||||
}
|
||||
|
||||
const activeOrders = orders.filter((e) => e.status === 'active');
|
||||
|
||||
async function cancelAllOrders() {
|
||||
async function deleteOrder(orderId: string) {
|
||||
try {
|
||||
setAlertState('loading');
|
||||
setAlertSubtitle('Canceling all orders...');
|
||||
setAlertSubtitle('Canceling order...');
|
||||
|
||||
// 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 result = await fetchMethods.cancelOrder(orderId);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error('ERROR_CANCELING_ORDER');
|
||||
}
|
||||
return res;
|
||||
})();
|
||||
|
||||
if (results.some((e) => e === null)) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Some of the orders were not canceled');
|
||||
} else {
|
||||
setAlertState('success');
|
||||
setAlertSubtitle('All orders canceled');
|
||||
}
|
||||
setAlertSubtitle('Order canceled');
|
||||
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 2000);
|
||||
|
||||
const { success, data } = await getUserOrders();
|
||||
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);
|
||||
|
||||
if (success) {
|
||||
setOrders(data);
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error canceling order');
|
||||
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelAllOrders() {
|
||||
try {
|
||||
setAlertState('loading');
|
||||
setAlertSubtitle('Canceling all orders...');
|
||||
|
||||
// Simulate loading time
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
const { type, pairId, date } = deriveCancelAllOrdersFiltersFromState();
|
||||
|
||||
const cancelAllRes = await fetchMethods.cancelAllOrders({
|
||||
filterInfo: {
|
||||
type,
|
||||
pairId,
|
||||
date,
|
||||
},
|
||||
});
|
||||
|
||||
if (!cancelAllRes.success) {
|
||||
throw new Error('Error canceling all orders');
|
||||
}
|
||||
|
||||
await initialize();
|
||||
} catch (error) {
|
||||
console.error('Error canceling all orders:', error);
|
||||
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error canceling all orders');
|
||||
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,23 +438,23 @@ function Orders() {
|
|||
<DateRangeSelector value={dateRange} setValue={setDateRange} />
|
||||
</div>
|
||||
|
||||
{!isFinishedCategory && (
|
||||
<Button transparent onClick={cancelAllOrders}>
|
||||
Cancel all orders
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OrdersTable
|
||||
value={orders
|
||||
.filter(buySellFilter)
|
||||
.filter(pairFilter)
|
||||
.filter(dateFilter)
|
||||
.filter(categoryFilter)}
|
||||
setAlertState={setAlertState}
|
||||
setAlertSubtitle={setAlertSubtitle}
|
||||
setOrders={setOrders}
|
||||
value={orders}
|
||||
category={categoryState.code}
|
||||
deleteOrder={deleteOrder}
|
||||
/>
|
||||
|
||||
<div className={styles['orders__preloader-wrapper']} ref={inViewRef}>
|
||||
{orderPageLoading && <Preloader />}
|
||||
</div>
|
||||
</div>
|
||||
{alertState && (
|
||||
<Alert
|
||||
|
|
|
|||
|
|
@ -92,5 +92,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.orders__preloader-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ import axios from 'axios';
|
|||
import GetPairsPagesAmountRes from '@/interfaces/responses/dex/GetPairsPagesAmountRes';
|
||||
import { PairSortOption } from '@/interfaces/enum/pair';
|
||||
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 baseUrl = isServer ? API_URL : '';
|
||||
|
|
@ -246,9 +250,35 @@ export async function getUserOrdersPage(pairId: string): Promise<ErrorRes | GetU
|
|||
.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
|
||||
.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'),
|
||||
})
|
||||
.then((res) => res.data);
|
||||
|
|
@ -309,6 +339,26 @@ export async function applyOrder(orderData: ApplyOrderData): Promise<ErrorRes |
|
|||
.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(
|
||||
transactionId: string,
|
||||
): Promise<ErrorRes | { success: true }> {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue