Merge branch 'staging' into dev
This commit is contained in:
commit
02f3c95f0c
24 changed files with 292 additions and 166 deletions
|
|
@ -19,8 +19,8 @@ export default function GenericTable<T>(props: GenericTableProps<T>) {
|
|||
renderGroupHeader,
|
||||
sortGroups,
|
||||
responsive,
|
||||
scrollRef,
|
||||
} = props;
|
||||
|
||||
const isMatch = useMediaQuery(responsive?.query ?? '');
|
||||
const mediaActive = !!responsive?.query && isMatch;
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ export default function GenericTable<T>(props: GenericTableProps<T>) {
|
|||
|
||||
if (mediaActive && responsive?.alignOverride) {
|
||||
cols = cols.map((c) => {
|
||||
const ov = responsive.alignOverride![c.key];
|
||||
const ov = responsive.alignOverride?.[c.key];
|
||||
return ov ? { ...c, align: ov } : c;
|
||||
});
|
||||
}
|
||||
|
|
@ -60,7 +60,11 @@ export default function GenericTable<T>(props: GenericTableProps<T>) {
|
|||
return (
|
||||
<div className={className}>
|
||||
{data.length > 0 ? (
|
||||
<div className="orders-scroll" style={{ maxHeight: '100%', overflowY: 'auto' }}>
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="orders-scroll"
|
||||
style={{ maxHeight: '100%', overflowY: 'auto' }}
|
||||
>
|
||||
<table
|
||||
className={tableClassName}
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -38,4 +38,5 @@ export type GenericTableProps<T> = {
|
|||
alignOverride?: Record<string, 'left' | 'center' | 'right'>;
|
||||
tableLayout?: 'auto' | 'fixed';
|
||||
};
|
||||
scrollRef?: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
setRangeInputValue,
|
||||
rangeInputValue = '50',
|
||||
balance = 0,
|
||||
zanoBalance = 0,
|
||||
amountValid,
|
||||
priceValid,
|
||||
totalValid,
|
||||
|
|
@ -73,15 +74,35 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
async function postOrder() {
|
||||
const price = new Decimal(priceState);
|
||||
const amount = new Decimal(amountState);
|
||||
const total = new Decimal(totalState);
|
||||
|
||||
const isFull =
|
||||
price.greaterThan(0) &&
|
||||
price.lessThan(1000000000) &&
|
||||
amount.greaterThan(0) &&
|
||||
amount.lessThan(1000000000);
|
||||
amount.lessThan(1000000000) &&
|
||||
total.greaterThan(0);
|
||||
|
||||
if (!isFull) return;
|
||||
|
||||
if (isBuy) {
|
||||
const zanoAmount = new Decimal(zanoBalance);
|
||||
if (zanoAmount.lessThan(total)) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Insufficient ZANO balance');
|
||||
setTimeout(() => setAlertState(null), 3000);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const assetAmount = new Decimal(balance);
|
||||
if (assetAmount.lessThan(amount)) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle(`Insufficient ${firstCurrencyName} balance`);
|
||||
setTimeout(() => setAlertState(null), 3000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const orderData: CreateOrderData = {
|
||||
type: isBuy ? 'buy' : 'sell',
|
||||
side: 'limit',
|
||||
|
|
@ -129,6 +150,7 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
|
||||
const buttonText = creatingState ? 'Creating...' : 'Create Order';
|
||||
const isButtonDisabled = !priceValid || !amountValid || !totalValid || creatingState;
|
||||
const showTotalError = priceState !== '' && amountState !== '' && !totalValid;
|
||||
|
||||
return (
|
||||
<div data-tour="input-panel" className={styles.inputPanel}>
|
||||
|
|
@ -200,8 +222,8 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
invalid={!!amountState && !amountValid}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<RangeInput value={rangeInputValue} onInput={onRangeInput} />
|
||||
<div className={classes(isBuy && styles.disabled)}>
|
||||
<RangeInput value={!isBuy ? rangeInputValue : '50'} onInput={onRangeInput} />
|
||||
<div className={styles.inputPanel__body_labels}>
|
||||
<p className={styles.inputPanel__body_labels__item}>0%</p>
|
||||
<p className={styles.inputPanel__body_labels__item}>100%</p>
|
||||
|
|
@ -219,12 +241,12 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
|
||||
<div className={styles.inputPanel__body_total}>
|
||||
<LabeledInput
|
||||
value={notationToString(totalState)}
|
||||
value={amountState && priceState && notationToString(totalState)}
|
||||
setValue={() => undefined}
|
||||
currency={secondCurrencyName}
|
||||
label="Total"
|
||||
readonly={true}
|
||||
invalid={!!totalState && !totalValid}
|
||||
invalid={showTotalError}
|
||||
/>
|
||||
|
||||
<div className={classes(styles.inputPanel__body_labels, styles.mobileWrap)}>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.applyAlert {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export function buildOrderPoolColumns({
|
|||
key: 'quantity',
|
||||
header: <>Qty ({firstCurrencyName})</>,
|
||||
width: '80px',
|
||||
cell: (row) => <p>{notationToString(row.amount, 8)}</p>,
|
||||
cell: (row) => <p>{notationToString(row.left, 8)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
classes,
|
||||
createOrderSorter,
|
||||
|
|
@ -35,32 +35,92 @@ const tabsData: tabsType[] = [
|
|||
const OrdersPool = (props: OrdersPoolProps) => {
|
||||
const {
|
||||
ordersBuySell,
|
||||
OrdersHistory,
|
||||
setOrdersBuySell,
|
||||
currencyNames,
|
||||
ordersLoading,
|
||||
ordersHistory,
|
||||
filteredOrdersHistory,
|
||||
secondAssetUsdPrice,
|
||||
takeOrderClick,
|
||||
trades,
|
||||
tradesLoading,
|
||||
} = props;
|
||||
const ordersInfoRef = useRef<HTMLTableSectionElement | null>(null);
|
||||
const ordersInfoRef = useRef<HTMLTableSectionElement>(null);
|
||||
const scrollRef = useRef<HTMLTableSectionElement>(null);
|
||||
const ordersMiddleRef = useRef<HTMLDivElement>(null);
|
||||
const { firstCurrencyName, secondCurrencyName } = currencyNames;
|
||||
const [infoTooltipPos, setInfoTooltipPos] = useState({ x: 0, y: 0 });
|
||||
const [ordersInfoTooltip, setOrdersInfoTooltip] = useState<PageOrderData | null>(null);
|
||||
const [currentOrder, setCurrentOrder] = useState<tabsType>(tabsData[0]);
|
||||
const { maxBuyLeftValue, maxSellLeftValue } = OrdersHistory.reduce(
|
||||
(acc, order) => {
|
||||
const left = parseFloat(String(order.left)) || 0;
|
||||
if (order.type === 'buy') acc.maxBuyLeftValue = Math.max(acc.maxBuyLeftValue, left);
|
||||
if (order.type === 'sell') acc.maxSellLeftValue = Math.max(acc.maxSellLeftValue, left);
|
||||
return acc;
|
||||
},
|
||||
{ maxBuyLeftValue: 0, maxSellLeftValue: 0 },
|
||||
);
|
||||
|
||||
const totalLeft = maxBuyLeftValue + maxSellLeftValue;
|
||||
const totals = useMemo(() => {
|
||||
let buyTotal = new Decimal(0);
|
||||
let sellTotal = new Decimal(0);
|
||||
let maxBuyRow = new Decimal(0);
|
||||
let maxSellRow = new Decimal(0);
|
||||
|
||||
for (const o of ordersHistory) {
|
||||
const qty = new Decimal(o.amount || 0);
|
||||
const price = new Decimal(o.price || 0);
|
||||
const rowTotal = qty.mul(price);
|
||||
|
||||
if (o.type === 'buy') {
|
||||
buyTotal = buyTotal.plus(rowTotal);
|
||||
if (rowTotal.gt(maxBuyRow)) maxBuyRow = rowTotal;
|
||||
} else if (o.type === 'sell') {
|
||||
sellTotal = sellTotal.plus(rowTotal);
|
||||
if (rowTotal.gt(maxSellRow)) maxSellRow = rowTotal;
|
||||
}
|
||||
}
|
||||
|
||||
const totalZano = buyTotal.plus(sellTotal);
|
||||
const pct = (part: Decimal, whole: Decimal) =>
|
||||
whole.gt(0) ? part.mul(100).div(whole) : new Decimal(0);
|
||||
|
||||
const buyPct = pct(buyTotal, totalZano);
|
||||
const sellPct = pct(sellTotal, totalZano);
|
||||
|
||||
return {
|
||||
buyTotal,
|
||||
sellTotal,
|
||||
totalZano,
|
||||
buyPct,
|
||||
sellPct,
|
||||
maxBuyRow,
|
||||
maxSellRow,
|
||||
};
|
||||
}, [ordersHistory]);
|
||||
|
||||
const toDisplayPair = (buyPctDec: Decimal, sellPctDec: Decimal) => {
|
||||
const MIN_DISPLAY_PCT = 1;
|
||||
const buyRaw = buyPctDec.toNumber();
|
||||
const sellRaw = sellPctDec.toNumber();
|
||||
|
||||
if (!Number.isFinite(buyRaw) || !Number.isFinite(sellRaw)) return { buy: 0, sell: 0 };
|
||||
|
||||
if (buyRaw === 0 && sellRaw === 0) return { buy: 0, sell: 0 };
|
||||
if (buyRaw === 0) return { buy: 0, sell: 100 };
|
||||
if (sellRaw === 0) return { buy: 100, sell: 0 };
|
||||
|
||||
let buyDisp = Math.floor(buyRaw);
|
||||
let sellDisp = Math.floor(sellRaw);
|
||||
|
||||
if (buyDisp < MIN_DISPLAY_PCT) buyDisp = MIN_DISPLAY_PCT;
|
||||
if (sellDisp < MIN_DISPLAY_PCT) sellDisp = MIN_DISPLAY_PCT;
|
||||
|
||||
const diff = 100 - (buyDisp + sellDisp);
|
||||
if (diff !== 0) {
|
||||
if (buyRaw >= sellRaw) buyDisp += diff;
|
||||
else sellDisp += diff;
|
||||
}
|
||||
|
||||
buyDisp = Math.max(0, Math.min(100, buyDisp));
|
||||
sellDisp = Math.max(0, Math.min(100, sellDisp));
|
||||
|
||||
return { buy: buyDisp, sell: sellDisp };
|
||||
};
|
||||
|
||||
const { buy: buyDisp, sell: sellDisp } = toDisplayPair(totals.buyPct, totals.sellPct);
|
||||
|
||||
const moveInfoTooltip = (event: React.MouseEvent) => {
|
||||
setInfoTooltipPos({ x: event.clientX, y: event.clientY });
|
||||
|
|
@ -84,6 +144,30 @@ const OrdersPool = (props: OrdersPoolProps) => {
|
|||
[firstCurrencyName, secondCurrencyName],
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!scrollRef.current) return;
|
||||
|
||||
const parent = scrollRef.current;
|
||||
|
||||
if (ordersBuySell.code === 'all' && ordersMiddleRef.current) {
|
||||
const child = ordersMiddleRef.current;
|
||||
|
||||
const parentRect = parent.getBoundingClientRect();
|
||||
const childRect = child.getBoundingClientRect();
|
||||
|
||||
const scrollTop =
|
||||
childRect.top -
|
||||
parentRect.top +
|
||||
parent.scrollTop -
|
||||
parent.clientHeight / 2 +
|
||||
childRect.height / 2;
|
||||
|
||||
parent.scrollTop = Math.round(scrollTop);
|
||||
} else {
|
||||
parent.scrollTop = 0;
|
||||
}
|
||||
}, [ordersLoading, filteredOrdersHistory.length, ordersBuySell.code]);
|
||||
|
||||
const sortedTrades = createOrderSorter<PageOrderData>({
|
||||
getPrice: (e) => e.price,
|
||||
getSide: (e) => e.type,
|
||||
|
|
@ -104,33 +188,42 @@ const OrdersPool = (props: OrdersPoolProps) => {
|
|||
columns={ordersPool}
|
||||
data={filteredOrdersHistory.sort(sortedTrades)}
|
||||
getRowKey={(r) => r.id}
|
||||
getRowProps={(row) => ({
|
||||
className: styles[row.type],
|
||||
style: {
|
||||
'--precentage': `${(
|
||||
(parseFloat(String(row.left)) /
|
||||
(row.type === 'buy'
|
||||
? maxBuyLeftValue
|
||||
: maxSellLeftValue)) *
|
||||
100
|
||||
).toFixed(2)}%`,
|
||||
} as React.CSSProperties,
|
||||
onClick: (event) => {
|
||||
takeOrderClick(event, row);
|
||||
},
|
||||
onMouseMove: (event) => {
|
||||
const tr = event.target as HTMLElement;
|
||||
if (tr.classList.contains('alias')) {
|
||||
setOrdersInfoTooltip(null);
|
||||
}
|
||||
},
|
||||
onMouseEnter: () => {
|
||||
setOrdersInfoTooltip(row);
|
||||
},
|
||||
onMouseLeave: () => {
|
||||
setOrdersInfoTooltip(null);
|
||||
},
|
||||
})}
|
||||
groupBy={(r) => r.type}
|
||||
scrollRef={scrollRef}
|
||||
renderGroupHeader={({ groupKey }) => {
|
||||
if (groupKey === 'buy') {
|
||||
return (
|
||||
<div ref={ordersMiddleRef} style={{ height: 0 }} />
|
||||
);
|
||||
}
|
||||
}}
|
||||
getRowProps={(row) => {
|
||||
const rowTotalZano = new Decimal(row.left || 0).mul(
|
||||
new Decimal(row.price || 0),
|
||||
);
|
||||
const denom =
|
||||
row.type === 'buy'
|
||||
? totals.maxBuyRow
|
||||
: totals.maxSellRow;
|
||||
const widthPct = denom.gt(0)
|
||||
? rowTotalZano.mul(100).div(denom)
|
||||
: new Decimal(0);
|
||||
|
||||
return {
|
||||
className: styles[row.type],
|
||||
style: {
|
||||
'--precentage': `${widthPct.toDecimalPlaces(2).toString()}%`,
|
||||
} as React.CSSProperties,
|
||||
onClick: (event) => takeOrderClick(event, row),
|
||||
onMouseMove: (event) => {
|
||||
const tr = event.target as HTMLElement;
|
||||
if (tr.classList.contains('alias'))
|
||||
setOrdersInfoTooltip(null);
|
||||
},
|
||||
onMouseEnter: () => setOrdersInfoTooltip(row),
|
||||
onMouseLeave: () => setOrdersInfoTooltip(null),
|
||||
};
|
||||
}}
|
||||
responsive={{
|
||||
query: '(max-width: 640px)',
|
||||
hiddenKeys: ['total'],
|
||||
|
|
@ -213,63 +306,24 @@ const OrdersPool = (props: OrdersPoolProps) => {
|
|||
<div className={styles.ordersPool__content}>
|
||||
{renderTable()}
|
||||
|
||||
{currentOrder.type === 'orders' &&
|
||||
!ordersLoading &&
|
||||
totalLeft > 0 &&
|
||||
(() => {
|
||||
const buy = new Decimal(maxBuyLeftValue || 0);
|
||||
const sell = new Decimal(maxSellLeftValue || 0);
|
||||
const total = new Decimal(totalLeft);
|
||||
{currentOrder.type === 'orders' && !ordersLoading && totals.totalZano.gt(0) && (
|
||||
<div className={styles.ordersPool__content_stats}>
|
||||
<div
|
||||
style={{ '--width': `${buyDisp}%` } as React.CSSProperties}
|
||||
className={classes(styles.stat_item, styles.buy)}
|
||||
>
|
||||
<div className={styles.stat_item__badge}>B</div>
|
||||
{buyDisp}%
|
||||
</div>
|
||||
|
||||
let buyPct = total.gt(0) ? buy.mul(100).div(total) : new Decimal(0);
|
||||
let sellPct = total.gt(0) ? sell.mul(100).div(total) : new Decimal(0);
|
||||
|
||||
if (buy.isZero() && sell.gt(0)) {
|
||||
buyPct = new Decimal(0);
|
||||
sellPct = new Decimal(100);
|
||||
} else if (sell.isZero() && buy.gt(0)) {
|
||||
sellPct = new Decimal(0);
|
||||
buyPct = new Decimal(100);
|
||||
}
|
||||
|
||||
const clamp = (d: Decimal) => Decimal.max(0, Decimal.min(100, d));
|
||||
|
||||
const buyPctClamped = clamp(buyPct);
|
||||
const sellPctClamped = clamp(sellPct);
|
||||
|
||||
const buyLabel = buyPctClamped
|
||||
.toDecimalPlaces(0, Decimal.ROUND_DOWN)
|
||||
.toString();
|
||||
const sellLabel = sellPctClamped
|
||||
.toDecimalPlaces(0, Decimal.ROUND_DOWN)
|
||||
.toString();
|
||||
|
||||
return (
|
||||
<div className={styles.ordersPool__content_stats}>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--width': `${buyPctClamped.toNumber()}%`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={classes(styles.stat_item, styles.buy)}
|
||||
>
|
||||
<div className={styles.stat_item__badge}>B</div> {buyLabel}%
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--width': `${sellPctClamped.toNumber()}%`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={classes(styles.stat_item, styles.sell)}
|
||||
>
|
||||
{sellLabel}%{' '}
|
||||
<div className={styles.stat_item__badge}>S</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div
|
||||
style={{ '--width': `${sellDisp}%` } as React.CSSProperties}
|
||||
className={classes(styles.stat_item, styles.sell)}
|
||||
>
|
||||
{sellDisp}%<div className={styles.stat_item__badge}>S</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -335,7 +389,7 @@ const OrdersPool = (props: OrdersPoolProps) => {
|
|||
</span>
|
||||
|
||||
<h6>Amount ({firstCurrencyName})</h6>
|
||||
<p>{notationToString(ordersInfoTooltip?.amount)}</p>
|
||||
<p>{notationToString(ordersInfoTooltip?.left)}</p>
|
||||
|
||||
<h6>Total ({secondCurrencyName})</h6>
|
||||
<p>{notationToString(totalDecimal.toString())}</p>
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
position: fixed;
|
||||
border: 1px solid var(--dex-tooltip-border-color);
|
||||
min-width: 140px;
|
||||
max-width: 180px;
|
||||
max-width: 300px;
|
||||
padding: 10px;
|
||||
transform: translateX(-50%);
|
||||
background-color: var(--dex-tooltip-bg);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export interface OrdersPoolProps {
|
|||
secondCurrencyName: string;
|
||||
};
|
||||
ordersLoading: boolean;
|
||||
OrdersHistory: PageOrderData[];
|
||||
ordersHistory: PageOrderData[];
|
||||
filteredOrdersHistory: PageOrderData[];
|
||||
trades: Trade[];
|
||||
tradesLoading: boolean;
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export function buildUserColumns({
|
|||
key: 'quantity',
|
||||
header: <>Quantity ({firstCurrencyName})</>,
|
||||
width: '160px',
|
||||
cell: (row) => <p>{notationToString(row.amount)}</p>,
|
||||
cell: (row) => <p>{notationToString(row.left)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
|
|
@ -289,10 +289,7 @@ export function buildMyRequestsColumns({
|
|||
width: '80px',
|
||||
align: 'left',
|
||||
cell: (row) => (
|
||||
<CancelActionCell
|
||||
id={String(row.creator === 'sell' ? row.sell_order_id : row.buy_order_id)}
|
||||
onAfter={onAfter}
|
||||
/>
|
||||
<CancelActionCell type="cancel_tx" id={row.id.toString()} onAfter={onAfter} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
@ -345,7 +342,7 @@ export function buildOrderHistoryColumns({
|
|||
key: 'quantity',
|
||||
header: <>Quantity ({firstCurrencyName})</>,
|
||||
width: '160px',
|
||||
cell: (row) => <p>{notationToString(row.amount)}</p>,
|
||||
cell: (row) => <p>{notationToString(row.left)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import { useAlert } from '@/hook/useAlert';
|
||||
import { cancelOrder } from '@/utils/methods';
|
||||
import { cancelOrder, cancelTransaction } from '@/utils/methods';
|
||||
import ActionBtn from '@/components/UI/ActionBtn';
|
||||
import { CancelActionCellProps } from './types';
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ export default function CancelActionCell({ type = 'cancel', id, onAfter }: Cance
|
|||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await cancelOrder(id);
|
||||
const result = type === 'cancel' ? await cancelOrder(id) : await cancelTransaction(id);
|
||||
if (!result.success) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error while cancelling order');
|
||||
|
|
@ -35,7 +35,7 @@ export default function CancelActionCell({ type = 'cancel', id, onAfter }: Cance
|
|||
disabled={loading}
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
{type === 'cancel' ? 'Cancel' : 'Reject'}
|
||||
{type === 'cancel' || type === 'cancel_tx' ? 'Cancel' : 'Reject'}
|
||||
</ActionBtn>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export interface CancelActionCellProps {
|
||||
type?: 'cancel' | 'reject';
|
||||
type?: 'cancel' | 'reject' | 'cancel_tx';
|
||||
id: string;
|
||||
onAfter: () => Promise<void>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default function OrderGroupHeader({
|
|||
<p className={styles.header__label}>Quantity</p>
|
||||
|
||||
<p className={styles.header__value}>
|
||||
{notationToString(order.amount)} {firstCurrencyName}
|
||||
{notationToString(order.left)} {firstCurrencyName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const useFilteredData = ({ ordersHistory, ordersBuySell }: useFilteredDataParams
|
|||
const filteredOrdersHistory = ordersHistory
|
||||
?.filter((e) => (ordersBuySell.code === 'all' ? e : e.type === ordersBuySell.code))
|
||||
?.filter((e) => e.user.address !== state.wallet?.address)
|
||||
?.filter((e) => parseFloat(e.left.toString()) !== 0)
|
||||
?.sort((a, b) => {
|
||||
if (ordersBuySell.code === 'buy') {
|
||||
return parseFloat(b.price.toString()) - parseFloat(a.price.toString());
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ interface UseOrderFormParams {
|
|||
assetsRates: Map<string, number>;
|
||||
}
|
||||
|
||||
function clamp12(str: string) {
|
||||
try {
|
||||
return new Decimal(str || '0').toDecimalPlaces(12, Decimal.ROUND_DOWN).toString();
|
||||
} catch {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
export function useOrderForm({
|
||||
pairData,
|
||||
balance,
|
||||
|
|
@ -47,7 +55,7 @@ export function useOrderForm({
|
|||
thisDP: priceDP,
|
||||
totalDP: priceDP,
|
||||
setThisState: setPrice,
|
||||
setTotalState: setTotal,
|
||||
setTotalState: (v: string) => setTotal(clamp12(v)),
|
||||
setThisValid: setPriceValid,
|
||||
setTotalValid,
|
||||
});
|
||||
|
|
@ -61,7 +69,7 @@ export function useOrderForm({
|
|||
thisDP: amountDP,
|
||||
totalDP: priceDP,
|
||||
setThisState: setAmount,
|
||||
setTotalState: setTotal,
|
||||
setTotalState: (v: string) => setTotal(clamp12(v)),
|
||||
setThisValid: setAmountValid,
|
||||
setTotalValid,
|
||||
balance,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Store } from '@/store/store-reducer';
|
|||
import { useContext } from 'react';
|
||||
import Decimal from 'decimal.js';
|
||||
import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes';
|
||||
import { ZANO_ASSET_ID } from '@/utils/utils';
|
||||
import { useOrderForm } from './useOrdereForm';
|
||||
|
||||
interface useTradeInitParams {
|
||||
|
|
@ -18,8 +19,11 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => {
|
|||
secondCurrencyName: pairData?.second_currency?.name || '',
|
||||
};
|
||||
|
||||
const firstCurrencyAssetID = pairData?.first_currency?.asset_id;
|
||||
|
||||
const assets = state.wallet?.connected ? state.wallet?.assets || [] : [];
|
||||
const balance = assets.find((e) => e.ticker === currencyNames.firstCurrencyName)?.balance;
|
||||
const balance = assets.find((e) => e.assetId === firstCurrencyAssetID)?.balance;
|
||||
const zanoBalance = assets.find((e) => e.assetId === ZANO_ASSET_ID)?.balance || 0;
|
||||
|
||||
const firstAssetId = pairData ? pairData.first_currency?.asset_id : undefined;
|
||||
const secondAssetId = pairData ? pairData.second_currency?.asset_id : undefined;
|
||||
|
|
@ -49,6 +53,7 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => {
|
|||
secondAssetLink,
|
||||
secondAssetUsdPrice,
|
||||
balance,
|
||||
zanoBalance,
|
||||
orderForm,
|
||||
pairRateUsd,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ export function useTradingData({
|
|||
}
|
||||
|
||||
async function updateOrders() {
|
||||
setOrdersLoading(true);
|
||||
const result = await getOrdersPage(pairId);
|
||||
if (!result.success) return;
|
||||
setOrdersHistory(result?.data || []);
|
||||
|
|
@ -83,7 +82,7 @@ export function useTradingData({
|
|||
}
|
||||
|
||||
async function fetchTrades() {
|
||||
setTradesLoading(true);
|
||||
// setTradesLoading(true);
|
||||
const result = await getTrades(pairId);
|
||||
|
||||
if (result.success) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface InputPanelItemProps {
|
|||
setRangeInputValue: Dispatch<SetStateAction<string>>;
|
||||
rangeInputValue: string;
|
||||
balance: number | undefined;
|
||||
zanoBalance: number | undefined;
|
||||
amountValid: boolean;
|
||||
priceValid: boolean;
|
||||
totalValid: boolean;
|
||||
|
|
|
|||
|
|
@ -165,13 +165,21 @@ function Orders() {
|
|||
setAlertState('loading');
|
||||
setAlertSubtitle('Canceling all orders...');
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
activeOrders.map(async (e) => {
|
||||
await cancelOrder(e.id);
|
||||
}),
|
||||
);
|
||||
// const results = await Promise.allSettled(
|
||||
// activeOrders.map(async (e) => {
|
||||
// await cancelOrder(e.id);
|
||||
// }),
|
||||
// );
|
||||
|
||||
if (results.some((e) => e.status === 'rejected')) {
|
||||
const results = await (async () => {
|
||||
const res = [];
|
||||
for (const order of activeOrders) {
|
||||
res.push(await cancelOrder(order.id).catch(() => null));
|
||||
}
|
||||
return res;
|
||||
})();
|
||||
|
||||
if (results.some((e) => e === null)) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Some of the orders were not canceled');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ function Trading() {
|
|||
const fetchUser = useUpdateUser();
|
||||
const [ordersHistory, setOrdersHistory] = useState<PageOrderData[]>([]);
|
||||
const [userOrders, setUserOrders] = useState<OrderRow[]>([]);
|
||||
const [periodsState, setPeriodsState] = useState<PeriodState>(periods[6]);
|
||||
const [periodsState, setPeriodsState] = useState<PeriodState>(periods[4]);
|
||||
const [pairData, setPairData] = useState<PairData | null>(null);
|
||||
const [candles, setCandles] = useState<CandleRow[]>([]);
|
||||
const [trades, setTrades] = useState<Trade[]>([]);
|
||||
|
|
@ -59,6 +59,7 @@ function Trading() {
|
|||
secondAssetLink,
|
||||
secondAssetUsdPrice,
|
||||
balance,
|
||||
zanoBalance,
|
||||
pairRateUsd,
|
||||
} = useTradeInit({ pairData, pairStats });
|
||||
|
||||
|
|
@ -116,7 +117,10 @@ function Trading() {
|
|||
setMyOrdersLoading(true);
|
||||
|
||||
try {
|
||||
await Promise.all(userOrders.map((order) => cancelOrder(order.id)));
|
||||
for (const order of userOrders) {
|
||||
await cancelOrder(order.id);
|
||||
}
|
||||
|
||||
await updateUserOrders();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
|
@ -160,11 +164,11 @@ function Trading() {
|
|||
ordersBuySell={ordersBuySell}
|
||||
ordersLoading={ordersLoading}
|
||||
filteredOrdersHistory={filteredOrdersHistory}
|
||||
ordersHistory={ordersHistory}
|
||||
trades={trades}
|
||||
tradesLoading={tradesLoading}
|
||||
setOrdersBuySell={setOrdersBuySell}
|
||||
takeOrderClick={onHandleTakeOrder}
|
||||
OrdersHistory={ordersHistory}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -203,6 +207,7 @@ function Trading() {
|
|||
setRangeInputValue={orderForm.setRangeInputValue}
|
||||
rangeInputValue={orderForm.rangeInputValue}
|
||||
balance={Number(balance)}
|
||||
zanoBalance={Number(zanoBalance)}
|
||||
priceValid={orderForm.priceValid}
|
||||
amountValid={orderForm.amountValid}
|
||||
totalValid={orderForm.totalValid}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
height: 54px;
|
||||
padding: 0 12px;
|
||||
|
||||
> div {
|
||||
>div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
|
@ -136,12 +136,12 @@
|
|||
width: 240px;
|
||||
height: 42px;
|
||||
|
||||
> div:nth-child(1) {
|
||||
>div:nth-child(1) {
|
||||
border-radius: 10px;
|
||||
padding: 11px 20px;
|
||||
|
||||
> div {
|
||||
> div p {
|
||||
>div {
|
||||
>div p {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
@ -160,12 +160,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
> div:nth-child(2) {
|
||||
> div {
|
||||
> div {
|
||||
>div:nth-child(2) {
|
||||
>div {
|
||||
>div {
|
||||
padding: 11px 20px;
|
||||
|
||||
> div {
|
||||
>div {
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
|
@ -233,7 +233,7 @@
|
|||
gap: 20px;
|
||||
|
||||
.input_wrapper {
|
||||
padding: 13px;
|
||||
padding-inline: 13px;
|
||||
max-width: 240px;
|
||||
max-height: 42px;
|
||||
display: flex;
|
||||
|
|
@ -244,7 +244,9 @@
|
|||
border-radius: 10px;
|
||||
|
||||
.input {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
padding-inline: 0;
|
||||
padding-block: 13px;
|
||||
max-width: 185px;
|
||||
background-color: transparent;
|
||||
font-size: 14px;
|
||||
|
|
@ -303,7 +305,7 @@
|
|||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
> p {
|
||||
>p {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
text-wrap: wrap;
|
||||
|
|
@ -321,11 +323,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
> p {
|
||||
>p {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
> input {
|
||||
>input {
|
||||
margin-left: 33px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
|
@ -387,7 +389,7 @@
|
|||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
> p {
|
||||
>p {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
text-wrap: wrap;
|
||||
|
|
@ -399,14 +401,14 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
> svg > * {
|
||||
>svg>* {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .curve__chart {
|
||||
>.curve__chart {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
|
|
@ -417,7 +419,7 @@
|
|||
align-items: center;
|
||||
gap: 15px;
|
||||
|
||||
> div:first-child {
|
||||
>div:first-child {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
|
|
@ -427,13 +429,13 @@
|
|||
background: var(--icon-bg-color);
|
||||
border-radius: 50%;
|
||||
|
||||
> img {
|
||||
>img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
>div:last-child {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -502,4 +504,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,15 @@ import Decimal from 'decimal.js';
|
|||
import { isPositiveFloatStr } from '@/utils/utils';
|
||||
import { validateTokensInput } from 'shared/utils';
|
||||
|
||||
type SetStr = (_v: string) => void;
|
||||
interface HandleInputChangeParams {
|
||||
inputValue: string;
|
||||
priceOrAmount: 'price' | 'amount';
|
||||
otherValue: string;
|
||||
thisDP: number;
|
||||
totalDP: number;
|
||||
setThisState: Dispatch<SetStateAction<string>>;
|
||||
setTotalState: Dispatch<SetStateAction<string>>;
|
||||
setThisState: SetStr;
|
||||
setTotalState: SetStr;
|
||||
setThisValid: Dispatch<SetStateAction<boolean>>;
|
||||
setTotalValid: Dispatch<SetStateAction<boolean>>;
|
||||
balance?: string | undefined;
|
||||
|
|
@ -68,18 +69,22 @@ export function handleInputChange({
|
|||
setThisValid(true);
|
||||
|
||||
if (!thisDecimal.isNaN() && !otherDecimal.isNaN() && otherValue !== '') {
|
||||
const total =
|
||||
const rawTotal =
|
||||
priceOrAmount === 'price'
|
||||
? thisDecimal.mul(otherDecimal)
|
||||
: otherDecimal.mul(thisDecimal);
|
||||
|
||||
setTotalState(total.toString());
|
||||
const totalClamped = rawTotal.toDecimalPlaces(totalDP, Decimal.ROUND_DOWN);
|
||||
|
||||
const totalValid = validateTokensInput(total.toFixed(totalDP), totalDP);
|
||||
setTotalValid(totalValid.valid);
|
||||
setTotalState(totalClamped.toString());
|
||||
|
||||
const fmtOk = validateTokensInput(totalClamped.toFixed(totalDP), totalDP).valid;
|
||||
const gtZero = totalClamped.gt(0);
|
||||
setTotalValid(fmtOk && gtZero);
|
||||
|
||||
if (priceOrAmount === 'amount' && balance && setRangeInputValue) {
|
||||
const percent = thisDecimal.div(balance).mul(100);
|
||||
const bal = new Decimal(balance || '0');
|
||||
const percent = bal.gt(0) ? thisDecimal.div(bal).mul(100) : new Decimal(0);
|
||||
setRangeInputValue(percent.toFixed());
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -259,6 +259,15 @@ export async function cancelOrder(id: string): Promise<ErrorRes | { success: tru
|
|||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function cancelTransaction(id: string): Promise<ErrorRes | { success: true }> {
|
||||
return axios
|
||||
.post('/api/transactions/cancel', {
|
||||
token: sessionStorage.getItem('token'),
|
||||
transactionId: id,
|
||||
})
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function getCandles(
|
||||
pairId: string,
|
||||
period: Period,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function takeOrderClick({
|
|||
const e = PageOrderData;
|
||||
|
||||
const priceStr = notationToString(new Decimal(e.price).toString()) || '';
|
||||
const amountStr = notationToString(new Decimal(e.amount).toString()) || '';
|
||||
const amountStr = notationToString(new Decimal(e.left).toString()) || '';
|
||||
|
||||
const secondCurrencyDP = pairData?.second_currency?.asset_info?.decimal_point || 12;
|
||||
const firstCurrencyDP = pairData?.first_currency?.asset_info?.decimal_point || 12;
|
||||
|
|
|
|||
|
|
@ -183,15 +183,15 @@ export function createOrderSorter<T>({ getSide, getPrice }: Getters<T>) {
|
|||
return (a: T, b: T) => {
|
||||
const aSide = getSide(a);
|
||||
const bSide = getSide(b);
|
||||
if (aSide !== bSide) return aSide === 'buy' ? -1 : 1;
|
||||
|
||||
if (aSide !== bSide) {
|
||||
return aSide === 'sell' ? -1 : 1;
|
||||
}
|
||||
|
||||
const ap = new Decimal(getPrice(a));
|
||||
const bp = new Decimal(getPrice(b));
|
||||
|
||||
if (aSide === 'buy') {
|
||||
return bp.comparedTo(ap);
|
||||
}
|
||||
return ap.comparedTo(bp);
|
||||
return bp.comparedTo(ap);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue