Merge branch 'staging' into dev

This commit is contained in:
jejolare 2025-09-14 19:28:54 +07:00
commit 02f3c95f0c
24 changed files with 292 additions and 166 deletions

View file

@ -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={{

View file

@ -38,4 +38,5 @@ export type GenericTableProps<T> = {
alignOverride?: Record<string, 'left' | 'center' | 'right'>;
tableLayout?: 'auto' | 'fixed';
};
scrollRef?: React.RefObject<HTMLDivElement>;
};

View file

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

View file

@ -108,6 +108,11 @@
}
}
.disabled {
opacity: .5;
pointer-events: none;
}
.applyAlert {
display: flex;
gap: 20px;

View file

@ -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',

View file

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

View file

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

View file

@ -12,7 +12,7 @@ export interface OrdersPoolProps {
secondCurrencyName: string;
};
ordersLoading: boolean;
OrdersHistory: PageOrderData[];
ordersHistory: PageOrderData[];
filteredOrdersHistory: PageOrderData[];
trades: Trade[];
tradesLoading: boolean;

View file

@ -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',

View file

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

View file

@ -1,5 +1,5 @@
export interface CancelActionCellProps {
type?: 'cancel' | 'reject';
type?: 'cancel' | 'reject' | 'cancel_tx';
id: string;
onAfter: () => Promise<void>;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {

View file

@ -16,6 +16,7 @@ interface InputPanelItemProps {
setRangeInputValue: Dispatch<SetStateAction<string>>;
rangeInputValue: string;
balance: number | undefined;
zanoBalance: number | undefined;
amountValid: boolean;
priceValid: boolean;
totalValid: boolean;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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