WIP: dex redesign

This commit is contained in:
AzizbekFayziyev 2025-08-25 17:51:34 +05:00
parent 61b3208e0d
commit 04abd67496
42 changed files with 925 additions and 678 deletions

View file

@ -0,0 +1,22 @@
import React from 'react';
import { classes } from '@/utils/utils';
import styles from './styles.module.scss';
import { TabsProps } from './types';
const Tabs = ({ data, value, setValue }: TabsProps) => {
return (
<div className={styles.tabs}>
{data.map((tab) => (
<button
key={tab.type}
onClick={() => setValue(tab)}
className={classes(styles.tabs__item, value.type === tab.type && styles.active)}
>
{tab.title} {Number.isFinite(tab.length) && <>({tab.length})</>}
</button>
))}
</div>
);
};
export default Tabs;

View file

@ -0,0 +1,30 @@
.tabs {
width: 100%;
border-bottom: 1px solid var(--delimiter-color);
display: flex;
align-items: center;
gap: 22px;
&__item {
cursor: pointer;
padding-bottom: 7px;
position: relative;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
font-size: 14px;
font-weight: 500;
border-bottom: 2px solid transparent;
background-color: transparent;
color: #1f8feb;
&.active {
color: var(--text-color);
border-color: #1f8feb;
}
&:hover {
border-color: #1f8feb;
background-color: transparent;
}
}
}

View file

@ -0,0 +1,13 @@
import { Dispatch, SetStateAction } from 'react';
export type tabsType = {
title: string;
type: string;
length?: number;
};
export interface TabsProps {
value: tabsType;
setValue: Dispatch<SetStateAction<tabsType>>;
data: tabsType[];
}

View file

@ -13,6 +13,7 @@ export default function GenericTable<T>(props: GenericTableProps<T>) {
data,
getRowKey,
emptyMessage = 'No data',
getRowProps,
} = props;
return (
@ -65,7 +66,10 @@ export default function GenericTable<T>(props: GenericTableProps<T>) {
<tbody className={tbodyClassName}>
{data.map((row, i) => (
<tr key={getRowKey(row, i)}>
<tr
{...(getRowProps ? getRowProps(row, i) : {})}
key={getRowKey(row, i)}
>
{columns.map((col) => (
<td
key={col.key}

View file

@ -9,6 +9,10 @@ export type ColumnDef<T> = {
cell: (_row: T, _rowIndex: number) => React.ReactNode;
};
export type RowProps = React.HTMLAttributes<HTMLTableRowElement> & {
className?: string;
};
export type GenericTableProps<T> = {
className?: string;
tableClassName?: string;
@ -18,4 +22,5 @@ export type GenericTableProps<T> = {
data: T[];
getRowKey: (_row: T, _rowIndex: number) => React.Key;
emptyMessage?: string;
getRowProps?: (_row: T, _index: number) => RowProps | undefined;
};

View file

@ -0,0 +1,96 @@
import { useEffect, useRef, useState } from 'react';
import Tooltip from '@/components/UI/Tooltip/Tooltip';
import { cutAddress } from '@/utils/utils';
import MatrixConnectionBadge from '@/components/dex/MatrixConnectionBadge';
import BadgeStatus from '@/components/dex/BadgeStatus';
import { createPortal } from 'react-dom';
import styles from './styles.module.scss';
import { AliasCellProps } from './types';
export default function AliasCell({
alias,
address,
matrixAddresses,
isInstant,
max = 12,
}: AliasCellProps) {
const display = alias ? cutAddress(alias, max) : 'no alias';
const [open, setOpen] = useState(false);
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
const anchorRef = useRef<HTMLParagraphElement | null>(null);
const updatePosition = () => {
const el = anchorRef.current;
if (!el) return;
const rect = el.getBoundingClientRect();
setPos({
top: rect.bottom + 8,
left: rect.left + rect.width / 2,
});
};
useEffect(() => {
if (!open) return;
updatePosition();
const onScrollOrResize = () => updatePosition();
window.addEventListener('scroll', onScrollOrResize, true);
window.addEventListener('resize', onScrollOrResize);
return () => {
window.removeEventListener('scroll', onScrollOrResize, true);
window.removeEventListener('resize', onScrollOrResize);
};
}, [open]);
return (
<p ref={anchorRef} className={styles.alias}>
<span
onMouseEnter={() => {
setOpen(true);
requestAnimationFrame(updatePosition);
}}
onMouseLeave={() => setOpen(false)}
className={styles.alias__text}
>
@{display}
</span>
<MatrixConnectionBadge
userAdress={address}
userAlias={alias}
matrixAddresses={matrixAddresses}
/>
{isInstant && (
<div style={{ marginLeft: 2 }}>
<BadgeStatus type="instant" icon />
</div>
)}
{open &&
pos &&
createPortal(
<>
{alias && alias.length > max && (
<Tooltip
style={{
position: 'fixed',
top: pos.top,
left: pos.left,
transform: 'translateX(-50%)',
zIndex: 9999,
pointerEvents: 'auto',
}}
className={styles.tooltip}
arrowClass={styles.tooltip__arrow}
shown={true}
>
<p className={styles.tooltip__text}>{alias}</p>
</Tooltip>
)}
</>,
document.body,
)}
</p>
);
}

View file

@ -0,0 +1,33 @@
.alias {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
&__text {
color: var(--font-main-color) !important;
}
path {
fill: none;
}
}
.tooltip {
position: absolute;
top: 30px;
left: 5%;
background-color: var(--trade-table-tooltip);
z-index: 9999 !important;
&__text {
font-size: 12px !important;
color: var(--font-main-color) !important;
}
&__arrow {
border-radius: 2px;
left: 30% !important;
background-color: var(--trade-table-tooltip) !important;
}
}

View file

@ -4,8 +4,6 @@
height: auto;
height: 100%;
height: 515px;
> canvas {
width: 100%;
height: 100%;

View file

@ -1,21 +1,12 @@
import LabeledInputProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps';
import { classes, formatDollarValue } from '@/utils/utils';
import { classes } from '@/utils/utils';
import { useRef } from 'react';
import Input from '@/components/UI/Input/Input';
import styles from './styles.module.scss';
function LabeledInput(props: LabeledInputProps) {
const labelRef = useRef<HTMLParagraphElement>(null);
const {
label = '',
placeholder = '',
currency = '',
value,
readonly,
usd,
setValue,
invalid,
} = props;
const { label = '', currency = '', value, readonly, setValue, invalid } = props;
const handleInput = (e: React.FormEvent<HTMLInputElement>) => {
if (!readonly && setValue) {
@ -29,18 +20,12 @@ function LabeledInput(props: LabeledInputProps) {
<div className={classes(styles.labeledInput__wrapper, invalid && styles.invalid)}>
<Input
bordered
placeholder={placeholder}
placeholder="0.00"
value={value}
readOnly={readonly}
onInput={handleInput}
/>
{usd && (
<div className={styles.labeledInput__value}>
<p>~${formatDollarValue(usd)}</p>
</div>
)}
<div className={styles.labeledInput__currency} ref={labelRef}>
<p>{currency}</p>
</div>

View file

@ -4,8 +4,9 @@
gap: 8px;
&__label {
font-size: 11px;
font-family: 700;
font-size: 12px;
font-family: 500;
line-height: 100%;
color: var(--table-th-color);
}
@ -13,47 +14,43 @@
width: 100%;
position: relative;
background-color: var(--bordered-input-bg);
border: 1px solid var(--window-border-color);
border: 1px solid transparent;
border-radius: 8px;
display: flex;
overflow: hidden;
&:hover {
border-color: var(--window-border-color);
}
&:focus-within {
border-color: #1f8feb;
}
&.invalid {
border-color: #ff6767;
}
input {
width: 100%;
padding: 13px 15px;
padding: 15px;
background-color: transparent;
border: none;
font-size: 16px;
font-weight: 400;
}
}
&__value {
padding-right: 10px;
display: flex;
align-items: center;
> p {
color: var(--table-th-color);
font-size: 12px;
font-size: 14px;
font-weight: 400;
}
}
&__currency {
min-width: 82px;
max-width: 150px;
padding: 0 15px;
background-color: var(--dex-input-currency);
display: flex;
align-items: center;
justify-content: center;
> p {
font-size: 14px;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View file

@ -5,7 +5,7 @@ import RangeInput from '@/components/UI/RangeInput/RangeInput';
import ConnectButton from '@/components/UI/ConnectButton/ConnectButton';
import Button from '@/components/UI/Button/Button';
import { useRouter } from 'next/router';
import { classes } from '@/utils/utils';
import { classes, formatDollarValue } from '@/utils/utils';
import InputPanelItemProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps';
import CreateOrderData from '@/interfaces/fetch-data/create-order/CreateOrderData';
import Decimal from 'decimal.js';
@ -13,6 +13,7 @@ import Alert from '@/components/UI/Alert/Alert';
import infoIcon from '@/assets/images/UI/info_alert_icon.svg';
import Image from 'next/image';
import { useAlert } from '@/hook/useAlert';
import { buySellValues } from '@/constants';
import styles from './styles.module.scss';
import LabeledInput from './components/LabeledInput';
@ -21,8 +22,8 @@ function InputPanelItem(props: InputPanelItemProps) {
priceState = '',
amountState = '',
totalState = '',
buySellValues,
buySellState = buySellValues[0],
setBuySellState,
setPriceFunction,
setAmountFunction,
setRangeInputValue,
@ -138,46 +139,83 @@ function InputPanelItem(props: InputPanelItemProps) {
)}
<div className={styles.inputPanel__header}>
<h5 className={styles.title}>
{isBuy ? 'Buy' : 'Sell'} {secondCurrencyName}
</h5>
<p className={styles.inputPanel__fees}>
Fee: <span>0.01 Zano</span>
</p>
<h5 className={styles.title}>Trade</h5>
</div>
<div className={styles.inputPanel__body}>
{LabeledInput({
value: priceState,
setValue: setPriceFunction,
currency: secondCurrencyName,
placeholder: '0.00',
label: 'Price',
invalid: !!priceState && !priceValid,
})}
<div className={styles.inputPanel__selector}>
<button
onClick={() => setBuySellState(buySellValues[1])}
className={classes(
styles.inputPanel__selector_item,
buySellState.code === 'buy' && styles.buy,
)}
>
Buy
</button>
<button
onClick={() => setBuySellState(buySellValues[2])}
className={classes(
styles.inputPanel__selector_item,
buySellState.code === 'sell' && styles.sell,
)}
>
Sell
</button>
</div>
{LabeledInput({
value: amountState,
setValue: setAmountFunction,
currency: firstCurrencyName,
placeholder: '0.00',
label: 'Amount',
invalid: !!amountState && !amountValid,
})}
<LabeledInput
value={priceState}
setValue={setPriceFunction}
currency={secondCurrencyName}
label="Price"
invalid={!!priceState && !priceValid}
/>
<RangeInput value={rangeInputValue} onInput={onRangeInput} />
<LabeledInput
value={amountState}
setValue={setAmountFunction}
currency={firstCurrencyName}
label="Quantity"
invalid={!!amountState && !amountValid}
/>
{LabeledInput({
value: totalState,
setValue: () => undefined,
currency: secondCurrencyName,
placeholder: '0.00',
label: 'Total',
readonly: true,
invalid: !!totalState && !totalValid,
usd: totalUsd,
})}
<div>
<RangeInput value={rangeInputValue} 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>
</div>
</div>
<div className={styles.inputPanel__body_labels}>
<p className={styles.inputPanel__body_labels__item}>Available Balance</p>
<p className={styles.inputPanel__body_labels__item}>
<span>{balance || 0}</span> {firstCurrencyName}
</p>
</div>
<div className={styles.inputPanel__body_total}>
<LabeledInput
value={totalState}
setValue={() => undefined}
currency={secondCurrencyName}
label="Total"
readonly={true}
invalid={!!totalState && !totalValid}
/>
<div className={styles.inputPanel__body_labels}>
<p className={styles.inputPanel__body_labels__item}>
Fee: <span>0.01</span> ZANO
</p>
{totalUsd && (
<p className={styles.inputPanel__body_labels__item}>
~ ${formatDollarValue(totalUsd)}
</p>
)}
</div>
</div>
{state.wallet?.connected ? (
<Button
disabled={isButtonDisabled}
@ -190,12 +228,7 @@ function InputPanelItem(props: InputPanelItemProps) {
{buttonText}
</Button>
) : (
<ConnectButton
className={classes(
styles.inputPanel__body_btn,
isBuy ? styles.buy : styles.sell,
)}
/>
<ConnectButton className={styles.inputPanel__body_btn} />
)}
</div>
</div>

View file

@ -1,5 +1,7 @@
.inputPanel {
width: 100%;
max-width: 415px;
height: 100%;
padding: 15px;
background: var(--window-bg-color);
border: 1px solid var(--delimiter-color);
@ -9,7 +11,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 10px;
@ -19,21 +21,65 @@
}
}
&__fees {
color: var(--table-th-color);
font-weight: 400;
font-size: 12px;
&__selector {
display: flex;
background-color: var(--main-bg-color);
padding: 4px;
border-radius: 20px;
span {
font-weight: 400;
font-size: 12px;
&_item {
cursor: pointer;
width: 50%;
background-color: transparent;
border-radius: 100px;
padding-block: 9px;
font-size: 14px;
font-weight: 500;
line-height: 100%;
&:hover {
background-color: transparent;
opacity: 0.8;
}
&.buy {
background-color: #16d1d6;
}
&.sell {
background-color: #ff6767;
}
}
}
&__body {
display: flex;
flex-direction: column;
gap: 10px;
gap: 15px;
&_labels {
margin-top: 5px;
display: flex;
align-items: center;
justify-content: space-between;
&__item {
font-size: 12px;
font-weight: 500;
line-height: 100%;
color: var(--table-th-color);
span {
font-size: 12px;
}
}
}
&_total {
display: flex;
flex-direction: column;
gap: 5px;
}
&_btn {
margin-top: 10px;

View file

@ -2,6 +2,7 @@ import Tooltip from '@/components/UI/Tooltip/Tooltip';
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { ReactComponent as ConnectionIcon } from '@/assets/images/UI/connection.svg';
import { classes } from '@/utils/utils';
import styles from './styles.module.scss';
import { MatrixConnectionBadgeProps } from './types';
@ -9,13 +10,14 @@ function MatrixConnectionBadge({
userAdress,
userAlias,
matrixAddresses,
className,
}: MatrixConnectionBadgeProps) {
const hasConnection = (address: string) =>
matrixAddresses.some((item) => item.address === address && item.registered);
const [open, setOpen] = useState(false);
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
const anchorRef = useRef<HTMLAnchorElement | null>(null);
const anchorRef = useRef<HTMLParagraphElement | null>(null);
const updatePosition = () => {
const el = anchorRef.current;
@ -42,11 +44,13 @@ function MatrixConnectionBadge({
if (!userAdress || !hasConnection(userAdress)) return <></>;
return (
<div className={styles.badge}>
<a
<div className={classes(styles.badge, className)}>
<p
ref={anchorRef}
href={`https://matrix.to/#/@${userAlias}:zano.org`}
target="_blank"
onClick={(e) => {
e.preventDefault();
window.open(`https://matrix.to/#/@${userAlias}:zano.org`);
}}
onMouseEnter={() => {
setOpen(true);
requestAnimationFrame(updatePosition);
@ -55,7 +59,7 @@ function MatrixConnectionBadge({
className={styles.badge__link}
>
<ConnectionIcon />
</a>
</p>
{open &&
pos &&

View file

@ -8,13 +8,8 @@
&__tooltip {
padding: 10px;
position: absolute;
top: 30px;
left: 50%;
transform: translateX(-50%);
background-color: var(--trade-table-tooltip);
font-size: 12px;
z-index: 9999;
&_text {
font-size: 12px !important;
@ -26,4 +21,4 @@
background-color: var(--trade-table-tooltip) !important;
}
}
}
}

View file

@ -1,6 +1,7 @@
import MatrixAddress from '@/interfaces/common/MatrixAddress';
export interface MatrixConnectionBadgeProps {
className?: string;
userAdress?: string;
userAlias?: string;
matrixAddresses: MatrixAddress[];

View file

@ -0,0 +1,83 @@
import { formatTimestamp, notationToString } from '@/utils/utils';
import { ColumnDef } from '@/components/default/GenericTable/types';
import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes';
import { Trade } from '@/interfaces/responses/trades/GetTradeRes';
import { BuildOrderPoolColumnsArgs, BuildTradesColumnsArgs } from './types';
import TotalUsdCell from '../../TotalUsdCell';
import AliasCell from '../../AliasCell';
export function buildOrderPoolColumns({
firstCurrencyName,
secondCurrencyName,
matrixAddresses,
}: BuildOrderPoolColumnsArgs): ColumnDef<PageOrderData>[] {
return [
{
key: 'alias',
header: 'Alias',
width: '100px',
className: 'alias',
cell: (row) => (
<AliasCell
alias={row.user?.alias}
address={row.user?.address}
matrixAddresses={matrixAddresses}
isInstant={row.isInstant}
max={7}
/>
),
},
{
key: 'price',
header: <>Price ({secondCurrencyName})</>,
width: '80px',
cell: (row) => (
<p style={{ color: row.type === 'buy' ? '#16D1D6' : '#FF6767' }}>
{notationToString(row.price, 8)}
</p>
),
},
{
key: 'quantity',
header: <>Qty ({firstCurrencyName})</>,
width: '80px',
cell: (row) => <p>{notationToString(row.amount, 8)}</p>,
},
{
key: 'total',
header: <>Total ({secondCurrencyName})</>,
width: '80px',
cell: (row) => <TotalUsdCell amount={row.left} price={row.price} fixed={4} />,
},
];
}
export function buildTradesColumns({
firstCurrencyName,
secondCurrencyName,
}: BuildTradesColumnsArgs): ColumnDef<Trade>[] {
return [
{
key: 'price',
header: <>Price ({secondCurrencyName})</>,
width: '80px',
cell: (row) => (
<p style={{ color: row.type === 'buy' ? '#16D1D6' : '#FF6767' }}>
{notationToString(row.price)}
</p>
),
},
{
key: 'quantity',
header: <>Qty ({firstCurrencyName})</>,
width: '80px',
cell: (row) => <p>{notationToString(row.amount)}</p>,
},
{
key: 'time',
header: <>Time</>,
width: '80px',
cell: (row) => <p>{formatTimestamp(row.timestamp)}</p>,
},
];
}

View file

@ -0,0 +1,12 @@
import MatrixAddress from '@/interfaces/common/MatrixAddress';
export interface BuildOrderPoolColumnsArgs {
firstCurrencyName: string;
secondCurrencyName: string;
matrixAddresses: MatrixAddress[];
}
export interface BuildTradesColumnsArgs {
firstCurrencyName: string;
secondCurrencyName: string;
}

View file

@ -1,54 +0,0 @@
import React from 'react';
import { classes, notationToString } from '@/utils/utils';
import { nanoid } from 'nanoid';
import Decimal from 'decimal.js';
import styles from './styles.module.scss';
import { OrdersRowProps } from './types';
import OrderRowTooltipCell from '../../../OrderRowTooltipCell';
function OrdersRow({
orderData,
percentage,
takeOrderClick,
setOrdersInfoTooltip,
}: OrdersRowProps) {
const e = orderData || {};
const totalDecimal = new Decimal(e.left).mul(new Decimal(e.price));
return (
<tr
onMouseEnter={() => setOrdersInfoTooltip(e)}
onClick={(event) => takeOrderClick(event, e)}
className={classes(styles.row, e.type === 'sell' && styles.sell_section)}
style={{ '--line-width': `${percentage}%` } as React.CSSProperties}
key={nanoid(16)}
>
<OrderRowTooltipCell
style={{
color: e.type === 'buy' ? '#16D1D6' : '#FF6767',
display: 'flex',
flexDirection: 'column',
gap: '8px',
maxWidth: 'max-content',
}}
>
{notationToString(e.price)}
</OrderRowTooltipCell>
<OrderRowTooltipCell>{notationToString(e.amount)}</OrderRowTooltipCell>
<OrderRowTooltipCell
noTooltip
style={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
maxWidth: 'max-content',
}}
>
{notationToString(totalDecimal.toString())}
</OrderRowTooltipCell>
</tr>
);
}
export default OrdersRow;

View file

@ -1,48 +0,0 @@
.row {
cursor: pointer;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 4px 0;
&:nth-child(even) {
background-color: var(--table-even-bg);
}
&::after {
content: '';
pointer-events: none;
position: absolute;
z-index: 1;
right: 0;
top: 0;
width: var(--line-width, 0%);
height: 100%;
background: #16d1d61a;
}
&.sell_section {
&::after {
background: #ff67671a;
}
}
td {
position: relative;
&:last-child {
> p {
text-align: right;
}
}
> p {
min-width: 80px;
width: 100%;
font-size: 12px;
font-weight: 400;
}
}
}

View file

@ -1,14 +0,0 @@
import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes';
import { Dispatch, SetStateAction } from 'react';
export interface OrdersRowProps {
orderData: PageOrderData;
percentage: number;
takeOrderClick: (
_event:
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
| React.MouseEvent<HTMLTableRowElement, MouseEvent>,
_e: PageOrderData,
) => void;
setOrdersInfoTooltip: Dispatch<SetStateAction<PageOrderData | null>>;
}

View file

@ -1,17 +1,30 @@
import React, { useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { classes, cutAddress, formatDollarValue, notationToString } from '@/utils/utils';
import { nanoid } from 'nanoid';
import Decimal from 'decimal.js';
import Tooltip from '@/components/UI/Tooltip/Tooltip';
import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader';
import { buySellValues } from '@/constants';
import EmptyMessage from '@/components/UI/EmptyMessage';
import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes';
import useMouseLeave from '@/hook/useMouseLeave';
import OrdersRow from './components/OrdersRow';
import { tabsType } from '@/components/UI/Tabs/types';
import Tabs from '@/components/UI/Tabs';
import GenericTable from '@/components/default/GenericTable';
import styles from './styles.module.scss';
import BadgeStatus from '../BadgeStatus';
import { OrdersPoolProps } from './types';
import { buildOrderPoolColumns, buildTradesColumns } from './columns';
const tabsData: tabsType[] = [
{
title: 'Order Pool',
type: 'orders',
},
{
title: 'Recent Trades',
type: 'trades',
},
];
const OrdersPool = (props: OrdersPoolProps) => {
const {
@ -22,103 +35,197 @@ const OrdersPool = (props: OrdersPoolProps) => {
filteredOrdersHistory,
secondAssetUsdPrice,
takeOrderClick,
matrixAddresses,
trades,
tradesLoading,
} = props;
const ordersInfoRef = useRef<HTMLTableSectionElement | null>(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 } = filteredOrdersHistory.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 moveInfoTooltip = (event: React.MouseEvent) => {
setInfoTooltipPos({ x: event.clientX, y: event.clientY });
};
const ordersPool = useMemo(
() =>
buildOrderPoolColumns({
firstCurrencyName,
secondCurrencyName,
matrixAddresses,
}),
[firstCurrencyName, secondCurrencyName, matrixAddresses],
);
const tradeOrders = useMemo(
() =>
buildTradesColumns({
firstCurrencyName,
secondCurrencyName,
}),
[firstCurrencyName, secondCurrencyName],
);
const renderTable = () => {
switch (currentOrder.type) {
case 'orders':
return (
<>
{!ordersLoading ? (
<div onMouseMove={moveInfoTooltip} ref={ordersInfoRef}>
<GenericTable
className={styles.ordersPool__content_orders}
tableClassName={styles.table}
tbodyClassName={styles.table__body}
theadClassName={styles.table__header}
columns={ordersPool}
data={filteredOrdersHistory.sort((a, b) => {
if (a.type === b.type) return 0;
return a.type === 'buy' ? -1 : 1;
})}
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);
},
})}
/>
</div>
) : (
<ContentPreloader style={{ marginTop: 40 }} />
)}
</>
);
case 'trades':
return (
<>
{!tradesLoading ? (
<GenericTable
className={styles.ordersPool__content_orders}
tableClassName={styles.table}
tbodyClassName={styles.table__body}
theadClassName={styles.table__header}
columns={tradeOrders}
data={trades}
getRowKey={(r) => r.id}
/>
) : (
<ContentPreloader style={{ marginTop: 40 }} />
)}
</>
);
default:
return null;
}
};
useMouseLeave(ordersInfoRef, () => setOrdersInfoTooltip(null));
return (
<>
<div className={styles.ordersPool}>
<div className={styles.ordersPool__header}>
<h5 className={styles.ordersPool__header_title}>Orders pool</h5>
<Tabs value={currentOrder} setValue={setCurrentOrder} data={tabsData} />
<div className={styles.ordersPool__header_type}>
<button
onClick={() => setOrdersBuySell(buySellValues[0])}
className={classes(
styles.btn,
styles.all,
ordersBuySell.code === 'all' && styles.selected,
)}
></button>
{currentOrder.type === 'orders' && (
<div className={styles.ordersPool__header_type}>
<button
onClick={() => setOrdersBuySell(buySellValues[0])}
className={classes(
styles.btn,
styles.all,
ordersBuySell.code === 'all' && styles.selected,
)}
></button>
<button
onClick={() => setOrdersBuySell(buySellValues[1])}
className={classes(
styles.btn,
styles.buy,
ordersBuySell.code === 'buy' && styles.selected,
)}
>
B
</button>
<button
onClick={() => setOrdersBuySell(buySellValues[1])}
className={classes(
styles.btn,
styles.buy,
ordersBuySell.code === 'buy' && styles.selected,
)}
>
B
</button>
<button
onClick={() => setOrdersBuySell(buySellValues[2])}
className={classes(
styles.btn,
styles.sell,
ordersBuySell.code === 'sell' && styles.selected,
)}
>
S
</button>
</div>
<button
onClick={() => setOrdersBuySell(buySellValues[2])}
className={classes(
styles.btn,
styles.sell,
ordersBuySell.code === 'sell' && styles.selected,
)}
>
S
</button>
</div>
)}
</div>
<div className={styles.ordersPool__content}>
<table>
<thead>
<tr>
<th>Price ({secondCurrencyName})</th>
<th>Amount ({firstCurrencyName})</th>
<th>Total ({secondCurrencyName})</th>
</tr>
</thead>
{renderTable()}
{!ordersLoading && !!filteredOrdersHistory.length && (
<tbody
ref={ordersInfoRef}
onMouseMove={moveInfoTooltip}
onMouseLeave={() => setOrdersInfoTooltip(null)}
className="orders-scroll"
{currentOrder.type === 'orders' && (
<div className={styles.ordersPool__content_stats}>
<div
style={
{
'--width': `${(maxBuyLeftValue / totalLeft) * 100}%`,
} as React.CSSProperties
}
className={classes(styles.stat_item, styles.buy)}
>
{filteredOrdersHistory?.map((e) => {
const maxValue = Math.max(
...filteredOrdersHistory.map((order) =>
parseFloat(String(order.left)),
),
);
const percentage = (
(parseFloat(String(e.left)) / maxValue) *
100
).toFixed(2);
return (
<OrdersRow
orderData={e}
percentage={Number(percentage)}
key={nanoid(16)}
takeOrderClick={takeOrderClick}
setOrdersInfoTooltip={setOrdersInfoTooltip}
/>
);
})}
</tbody>
)}
</table>
{!filteredOrdersHistory.length && !ordersLoading && (
<EmptyMessage text="No orders" />
<div className={styles.stat_item__badge}>B</div>{' '}
{notationToString((maxBuyLeftValue / totalLeft) * 100, 0)}%
</div>
<div
style={
{
'--width': `${(maxSellLeftValue / totalLeft) * 100}%`,
} as React.CSSProperties
}
className={classes(styles.stat_item, styles.sell)}
>
{notationToString((maxSellLeftValue / totalLeft) * 100, 0)}%{' '}
<div className={styles.stat_item__badge}>S</div>
</div>
</div>
)}
{ordersLoading && <ContentPreloader style={{ marginTop: 40 }} />}
</div>
</div>

View file

@ -1,28 +1,18 @@
.ordersPool {
position: relative;
max-width: 415px;
width: 100%;
height: 100%;
padding: 5px;
background: var(--window-bg-color);
border: 1px solid var(--delimiter-color);
border-radius: 10px;
@media screen and (max-width: 1480px) {
max-width: 340px;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
gap: 10px;
padding: 10px;
padding-bottom: 10px;
border-bottom: 1px solid var(--delimiter-color);
&_title {
font-size: 18px;
font-weight: 600;
}
&_type {
display: flex;
@ -38,10 +28,11 @@
font-weight: 700;
transition: 0.3s opacity ease;
color: #ffffff;
opacity: 60%;
&.selected,
&:hover {
opacity: 80%;
opacity: 100%;
}
&.all {
@ -63,44 +54,138 @@
display: flex;
flex-direction: column;
padding-top: 10px;
width: 100%;
table {
&_orders {
width: 100%;
height: 410px;
thead {
display: flex;
.table {
width: 100%;
padding-inline: 10px;
margin-bottom: 9px;
tr {
width: 100%;
display: flex;
justify-content: space-between;
&__header {
position: relative;
z-index: 3;
th {
font-size: 11px;
font-weight: 700;
text-align: start;
color: var(--table-th-color);
min-width: 80px;
}
}
&:last-child {
text-align: right;
&__body {
tr {
cursor: pointer;
position: relative;
&:hover {
background-color: var(--table-tr-hover-color);
}
&.buy {
td {
&:last-child {
&::before {
background-color: var(--dex-buy-percentage);
}
}
}
}
&.sell {
td {
&:last-child {
&::before {
background-color: var(--dex-sell-percentage);
}
}
}
}
td {
position: static !important;
&:last-child {
&::before {
content: '';
pointer-events: none;
position: absolute;
z-index: 1;
right: 0;
top: 0;
width: var(--precentage);
height: 100%;
}
}
p,
span {
position: relative;
z-index: 2;
font-size: 12px;
font-weight: 400;
}
}
}
}
}
}
tbody {
height: 29dvh;
min-height: 265px;
max-height: 380px;
&_stats {
position: absolute;
bottom: 15px;
left: 50%;
transform: translateX(-50%);
width: calc(100% - 30px);
display: flex;
align-items: center;
justify-content: space-between;
gap: 1px;
.stat_item {
display: flex;
flex-direction: column;
overflow: auto;
padding-bottom: 20px;
padding: 10px;
align-items: center;
gap: 6px;
font-size: 12px;
line-height: 100%;
font-weight: 400;
min-width: 60px;
&.buy {
width: var(--width);
background-color: var(--dex-buy-percentage);
color: #16d1d6;
.stat_item__badge {
background-color: #16d1d6;
}
}
&.sell {
width: var(--width);
background-color: var(--dex-sell-percentage);
justify-content: flex-end;
color: #ff6767;
.stat_item__badge {
background-color: #ff6767;
}
}
&__badge {
width: 20px;
height: 20px;
border-radius: 4px;
display: grid;
place-content: center;
font-size: 12px;
font-weight: 700;
line-height: 120%;
color: #ffffff;
}
}
}
}
@ -148,4 +233,4 @@
font-size: 11px;
font-weight: 400;
}
}
}

View file

@ -1,9 +1,12 @@
import { Dispatch, SetStateAction } from 'react';
import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue';
import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes';
import MatrixAddress from '@/interfaces/common/MatrixAddress';
import { Trade } from '@/interfaces/responses/trades/GetTradeRes';
export interface OrdersPoolProps {
ordersBuySell: SelectValue;
secondAssetUsdPrice: number | undefined;
setOrdersBuySell: Dispatch<SetStateAction<SelectValue>>;
currencyNames: {
firstCurrencyName: string;
@ -11,11 +14,11 @@ export interface OrdersPoolProps {
};
ordersLoading: boolean;
filteredOrdersHistory: PageOrderData[];
secondAssetUsdPrice: number | undefined;
trades: Trade[];
tradesLoading: boolean;
matrixAddresses: MatrixAddress[];
takeOrderClick: (
_event:
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
| React.MouseEvent<HTMLTableRowElement, MouseEvent>,
_event: React.MouseEvent<HTMLTableRowElement, MouseEvent>,
_e: PageOrderData,
) => void;
}

View file

@ -3,7 +3,12 @@ import Decimal from 'decimal.js';
import { notationToString, formatDollarValue } from '@/utils/utils';
import { TotalUsdCellProps } from './types';
export default function TotalUsdCell({ amount, price, secondAssetUsdPrice }: TotalUsdCellProps) {
export default function TotalUsdCell({
amount,
price,
secondAssetUsdPrice,
fixed,
}: TotalUsdCellProps) {
const total = useMemo(
() => new Decimal(amount || 0).mul(new Decimal(price || 0)),
[amount, price],
@ -12,7 +17,8 @@ export default function TotalUsdCell({ amount, price, secondAssetUsdPrice }: Tot
return (
<p>
{notationToString(total.toString())} <span>~ ${usd && formatDollarValue(usd)}</span>
{notationToString((fixed ? total.toFixed(fixed) : total).toString())}{' '}
{secondAssetUsdPrice && <span>~ ${usd && formatDollarValue(usd)}</span>}
</p>
);
}

View file

@ -2,4 +2,5 @@ export interface TotalUsdCellProps {
amount: string | number;
price: string | number;
secondAssetUsdPrice?: number;
fixed?: number;
}

View file

@ -5,8 +5,8 @@ import ApplyTip from '@/interfaces/common/ApplyTip';
import UserPendingType from '@/interfaces/common/UserPendingType';
import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes';
import CancelActionCell from '../components/CancelActionCell';
import AliasCell from '../components/AliasCell';
import TotalUsdCell from '../components/TotalUsdCell';
import AliasCell from '../../AliasCell';
import TotalUsdCell from '../../TotalUsdCell';
import RequestActionCell from '../components/RequestActionCell';
import {
BuildApplyTipsColumnsArgs,

View file

@ -1,47 +0,0 @@
import { useState } from 'react';
import Tooltip from '@/components/UI/Tooltip/Tooltip';
import { cutAddress } from '@/utils/utils';
import MatrixConnectionBadge from '@/components/dex/MatrixConnectionBadge';
import BadgeStatus from '@/components/dex/BadgeStatus';
import styles from '../../styles.module.scss';
import { AliasCellProps } from './types';
export default function AliasCell({
alias,
address,
matrixAddresses,
isInstant,
max = 12,
}: AliasCellProps) {
const [show, setShow] = useState(false);
const display = alias ? cutAddress(alias, max) : 'no alias';
return (
<p className={styles.alias}>
<span
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
className={styles.alias__text}
>
@{display}
</span>
<MatrixConnectionBadge
userAdress={address}
userAlias={alias}
matrixAddresses={matrixAddresses}
/>
{isInstant && (
<div style={{ marginLeft: 2 }}>
<BadgeStatus type="instant" icon />
</div>
)}
{alias && alias.length > max && (
<Tooltip className={styles.tooltip} arrowClass={styles.tooltip__arrow} shown={show}>
<p className={styles.tooltip__text}>{alias}</p>
</Tooltip>
)}
</p>
);
}

View file

@ -2,7 +2,7 @@ import { classes } from '@/utils/utils';
import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader';
import useUpdateUser from '@/hook/useUpdateUser';
import EmptyMessage from '@/components/UI/EmptyMessage';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useContext, useEffect, useMemo, useState } from 'react';
import GenericTable from '@/components/default/GenericTable';
import ActionBtn from '@/components/UI/ActionBtn';
import { getUserOrders, getUserPendings } from '@/utils/methods';
@ -11,7 +11,9 @@ import { Store } from '@/store/store-reducer';
import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes';
import { useAlert } from '@/hook/useAlert';
import Alert from '@/components/UI/Alert/Alert';
import { tabsType, UserOrdersProps } from './types';
import { tabsType } from '@/components/UI/Tabs/types';
import Tabs from '@/components/UI/Tabs';
import { UserOrdersProps } from './types';
import styles from './styles.module.scss';
import {
buildApplyTipsColumns,
@ -24,8 +26,6 @@ const UserOrders = ({
userOrders,
applyTips,
myOrdersLoading,
ordersType,
setOrdersType,
handleCancelAllOrders,
orderListRef,
matrixAddresses,
@ -43,6 +43,45 @@ const UserOrders = ({
const [userRequests, setUserRequests] = useState<UserPendingType[]>([]);
const [ordersHistory, setOrdersHistory] = useState<UserOrderData[]>([]);
const tabsData: tabsType[] = useMemo(
() => [
{
title: 'Open Orders',
type: 'opened',
length: userOrders.length,
},
{
title: 'Suitable',
type: 'suitable',
length: suitables.length,
},
{
title: 'My requests',
type: 'requests',
length: userRequests.length,
},
{
title: 'Offers',
type: 'offers',
length: offers.length,
},
{
title: 'Order history',
type: 'history',
length: ordersHistory.length,
},
],
[
offers.length,
userOrders.length,
suitables.length,
userRequests.length,
ordersHistory.length,
],
);
const [ordersType, setOrdersType] = useState<tabsType>(tabsData[0]);
useEffect(() => {
(async () => {
const requestsData = await getUserPendings();
@ -166,7 +205,7 @@ const UserOrders = ({
);
const renderTable = () => {
switch (ordersType) {
switch (ordersType.type) {
case 'opened':
return (
<GenericTable
@ -222,53 +261,13 @@ const UserOrders = ({
}
};
const tabsData: tabsType[] = [
{
title: 'Open Orders',
type: 'opened',
length: userOrders.length,
},
{
title: 'Suitable',
type: 'suitable',
length: suitables.length,
},
{
title: 'My requests',
type: 'requests',
length: userRequests.length,
},
{
title: 'Offers',
type: 'offers',
length: offers.length,
},
{
title: 'Order history',
type: 'history',
length: ordersHistory.length,
},
];
return (
<>
<div ref={orderListRef} className={styles.userOrders}>
<div className={styles.userOrders__header}>
<div className={styles.userOrders__header_nav}>
{tabsData.map((tab) => (
<button
key={tab.type}
onClick={() => setOrdersType(tab.type)}
className={classes(
styles.navItem,
ordersType === tab.type && styles.active,
)}
>
{tab.title} ({tab.length})
</button>
))}
</div>
<Tabs data={tabsData} value={ordersType} setValue={setOrdersType} />
{ordersType === 'opened' && userOrders.length > 0 && (
{ordersType?.type === 'opened' && userOrders.length > 0 && (
<ActionBtn
className={styles.userOrders__header_btn}
onClick={handleCancelAllOrders}

View file

@ -1,62 +1,29 @@
.userOrders {
position: relative;
width: 100%;
padding: 5px;
background: var(--window-bg-color);
border: 1px solid var(--delimiter-color);
border-radius: 10px;
padding: 1px;
min-height: 310px;
&__body {
position: relative;
padding: 10px;
padding-top: 5px;
padding-bottom: 20px;
height: 260px;
overflow: auto;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
padding-top: 10px;
padding-top: 14px;
margin-inline: 14px;
padding-bottom: 0;
border-bottom: 1px solid var(--delimiter-color);
&_nav {
display: flex;
align-items: center;
gap: 22px;
.navItem {
cursor: pointer;
padding-bottom: 7px;
position: relative;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
font-size: 14px;
font-weight: 500;
border-bottom: 2px solid transparent;
background-color: transparent;
color: #1f8feb;
&.active {
color: var(--text-color);
border-color: #1f8feb;
}
&:hover {
border-color: #1f8feb;
}
}
}
&_btn {
position: absolute;
right: 5px;
top: 5px;
right: 0px;
top: 7px;
z-index: 2;
}
}
@ -90,7 +57,7 @@
td {
position: relative;
> p {
>p {
width: 100%;
font-size: 12px;
font-weight: 400;
@ -109,7 +76,7 @@
}
}
> span {
>span {
line-height: 1;
color: var(--font-dimmed-color);
font-size: 11px;
@ -118,37 +85,4 @@
}
}
}
}
.alias {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
&__text {
color: var(--font-main-color) !important;
}
path {
fill: none;
}
}
.tooltip {
position: absolute;
top: 30px;
left: 5%;
background-color: var(--trade-table-tooltip);
z-index: 9999;
&__text {
font-size: 12px !important;
}
&__arrow {
border-radius: 2px;
left: 30% !important;
background-color: var(--trade-table-tooltip) !important;
}
}
}

View file

@ -2,24 +2,14 @@ import ApplyTip from '@/interfaces/common/ApplyTip';
import MatrixAddress from '@/interfaces/common/MatrixAddress';
import OrderRow from '@/interfaces/common/OrderRow';
import PairData from '@/interfaces/common/PairData';
import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes';
import { Dispatch, ForwardedRef, SetStateAction } from 'react';
import { ForwardedRef } from 'react';
export type OrderType = 'opened' | 'suitable' | 'requests' | 'offers' | 'history';
export type tabsType = {
title: string;
type: OrderType;
length: number;
};
export interface UserOrdersProps {
orderListRef: ForwardedRef<HTMLDivElement>;
userOrders: OrderRow[];
applyTips: ApplyTip[];
myOrdersLoading: boolean;
ordersType: OrderType;
setOrdersType: Dispatch<SetStateAction<OrderType>>;
handleCancelAllOrders: () => void;
secondAssetUsdPrice: number | undefined;
matrixAddresses: MatrixAddress[];

View file

@ -1,33 +1,16 @@
import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes';
import { Trade } from '@/interfaces/responses/trades/GetTradeRes';
import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue';
import { Store } from '@/store/store-reducer';
import { useContext } from 'react';
interface useFilteredDataParams {
trades: Trade[];
ordersHistory: PageOrderData[];
ordersBuySell: SelectValue;
tradesType: 'all' | 'my';
}
const useFilteredData = ({
ordersHistory,
trades,
ordersBuySell,
tradesType,
}: useFilteredDataParams) => {
const useFilteredData = ({ ordersHistory, ordersBuySell }: useFilteredDataParams) => {
const { state } = useContext(Store);
const filteredTrades =
tradesType === 'my'
? trades.filter(
(trade) =>
trade.buyer.address === state.wallet?.address ||
trade.seller.address === state.wallet?.address,
)
: trades;
const filteredOrdersHistory = ordersHistory
?.filter((e) => (ordersBuySell.code === 'all' ? e : e.type === ordersBuySell.code))
?.filter((e) => e.user.address !== state.wallet?.address)
@ -40,7 +23,6 @@ const useFilteredData = ({
return {
filteredOrdersHistory,
filteredTrades,
};
};

View file

@ -5,14 +5,12 @@ import OrderFormOutput from '@/interfaces/common/orderFormOutput';
import { handleInputChange } from '@/utils/handleInputChange';
interface UseOrderFormParams {
type: 'buy' | 'sell';
pairData: PairData | null;
balance: string | undefined;
assetsRates: Map<string, number>;
}
export function useOrderForm({
type,
pairData,
balance,
assetsRates,

View file

@ -37,15 +37,7 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => {
? new Decimal(pairStats.rate).mul(secondAssetUsdPrice).toFixed(2)
: undefined;
const buyForm = useOrderForm({
type: 'buy',
pairData,
balance,
assetsRates: state.assetsRates,
});
const sellForm = useOrderForm({
type: 'sell',
const orderForm = useOrderForm({
pairData,
balance,
assetsRates: state.assetsRates,
@ -57,8 +49,7 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => {
secondAssetLink,
secondAssetUsdPrice,
balance,
buyForm,
sellForm,
orderForm,
pairRateUsd,
};
};

View file

@ -9,8 +9,8 @@ interface InputPanelItemProps {
priceState: string;
amountState: string;
totalState: string;
buySellValues: SelectValue[];
buySellState: SelectValue;
setBuySellState: Dispatch<SetStateAction<SelectValue>>;
setPriceFunction: (_value: string) => void;
setAmountFunction: (_value: string) => void;
setRangeInputValue: Dispatch<SetStateAction<string>>;

View file

@ -2,9 +2,7 @@ interface LabeledInputProps {
label: string;
value: string;
setValue: (_value: string) => void;
placeholder: string;
currency: string;
usd?: string;
readonly?: boolean;
invalid?: boolean;
}

View file

@ -30,7 +30,6 @@ import useFilteredData from '@/hook/useFilteredData';
import useTradeInit from '@/hook/useTradeInit';
import useMatrixAddresses from '@/hook/useMatrixAddresses';
import takeOrderClick from '@/utils/takeOrderClick';
import { OrderType } from '@/components/dex/UserOrders/types';
import useUpdateUser from '@/hook/useUpdateUser';
const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }];
@ -52,15 +51,13 @@ function Trading() {
const [trades, setTrades] = useState<Trade[]>([]);
const [myOrdersLoading, setMyOrdersLoading] = useState(true);
const [ordersBuySell, setOrdersBuySell] = useState(buySellValues[0]);
const [tradesType, setTradesType] = useState<'all' | 'my'>('all');
const [ordersType, setOrdersType] = useState<OrderType>('opened');
const [pairStats, setPairStats] = useState<PairStats | null>(null);
const [applyTips, setApplyTips] = useState<ApplyTip[]>([]);
const matrixAddresses = useMatrixAddresses(ordersHistory);
const [orderFormType, setOrderFormType] = useState(buySellValues[1]);
const {
buyForm,
sellForm,
orderForm,
currencyNames,
firstAssetLink,
secondAssetLink,
@ -99,23 +96,21 @@ function Trading() {
// Take order from trades
const onHandleTakeOrder = useCallback(
(
event:
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
| React.MouseEvent<HTMLTableRowElement, MouseEvent>,
e: PageOrderData,
) => {
(event: React.MouseEvent<HTMLTableRowElement, MouseEvent>, e: PageOrderData) => {
setOrderFormType(() => {
return e.type === 'buy' ? buySellValues[2] : buySellValues[1];
});
takeOrderClick({
event,
PageOrderData: e,
balance,
buyForm,
orderForm,
pairData,
scrollToOrderForm,
sellForm,
});
},
[balance, buyForm, pairData, scrollToOrderForm, sellForm],
[balance, orderForm, pairData, scrollToOrderForm],
);
// Cancel all user orders
@ -134,11 +129,9 @@ function Trading() {
}
}, [userOrders, updateUserOrders]);
const { filteredOrdersHistory, filteredTrades } = useFilteredData({
const { filteredOrdersHistory } = useFilteredData({
ordersBuySell,
ordersHistory,
trades,
tradesType,
});
const onAfter = async () => {
@ -166,12 +159,15 @@ function Trading() {
<div className={styles.trading__top}>
<OrdersPool
currencyNames={currencyNames}
filteredOrdersHistory={filteredOrdersHistory}
secondAssetUsdPrice={secondAssetUsdPrice}
ordersBuySell={ordersBuySell}
ordersLoading={ordersLoading}
secondAssetUsdPrice={secondAssetUsdPrice}
filteredOrdersHistory={filteredOrdersHistory}
trades={trades}
tradesLoading={tradesLoading}
setOrdersBuySell={setOrdersBuySell}
takeOrderClick={onHandleTakeOrder}
matrixAddresses={matrixAddresses}
/>
<div className={styles.trading__top_chart}>
@ -198,13 +194,34 @@ function Trading() {
)}
</div>
<AllTrades
<div ref={orderFormRef} className={styles.trading__top_form}>
<InputPanelItem
currencyNames={currencyNames}
priceState={orderForm.price}
amountState={orderForm.amount}
totalState={orderForm.total}
buySellState={orderFormType}
setBuySellState={setOrderFormType}
setPriceFunction={orderForm.onPriceChange}
setAmountFunction={orderForm.onAmountChange}
setRangeInputValue={orderForm.setRangeInputValue}
rangeInputValue={orderForm.rangeInputValue}
balance={Number(balance)}
priceValid={orderForm.priceValid}
amountValid={orderForm.amountValid}
totalValid={orderForm.totalValid}
totalUsd={orderForm.totalUsd}
scrollToOrderList={scrollToOrdersList}
onAfter={onAfter}
/>
</div>
{/* <AllTrades
currencyNames={currencyNames}
filteredTrades={filteredTrades}
setTradesType={setTradesType}
tradesLoading={tradesLoading}
tradesType={tradesType}
/>
/> */}
</div>
<div className={styles.trading__info}>
@ -213,8 +230,6 @@ function Trading() {
userOrders={userOrders}
applyTips={applyTips}
myOrdersLoading={myOrdersLoading}
ordersType={ordersType}
setOrdersType={setOrdersType}
handleCancelAllOrders={handleCancelAllOrders}
matrixAddresses={matrixAddresses}
secondAssetUsdPrice={secondAssetUsdPrice}
@ -222,35 +237,6 @@ function Trading() {
onAfter={onAfter}
/>
</div>
<div ref={orderFormRef} className={styles.trading__info_createOrders}>
{['buy', 'sell'].map((type) => {
const isBuy = type === 'buy';
const form = isBuy ? buyForm : sellForm;
return (
<InputPanelItem
key={type}
currencyNames={currencyNames}
priceState={form.price}
amountState={form.amount}
totalState={form.total}
buySellValues={buySellValues}
buySellState={isBuy ? buySellValues[1] : buySellValues[2]}
setPriceFunction={form.onPriceChange}
setAmountFunction={form.onAmountChange}
setRangeInputValue={form.setRangeInputValue}
rangeInputValue={form.rangeInputValue}
balance={Number(balance)}
priceValid={form.priceValid}
amountValid={form.amountValid}
totalValid={form.totalValid}
totalUsd={form.totalUsd}
scrollToOrderList={scrollToOrdersList}
onAfter={onAfter}
/>
);
})}
</div>
{alertState && (
<Alert

View file

@ -17,9 +17,7 @@
display: flex;
gap: 20px;
padding-bottom: 20px;
height: 40dvh;
min-height: 380px;
max-height: 500px;
height: 570px;
&_chart {
width: 100%;
@ -42,17 +40,17 @@
}
}
}
&_form {
width: 100%;
height: 100%;
max-width: 415px;
}
}
&__info {
display: flex;
gap: 20px;
margin-bottom: 40px;
&_createOrders {
display: flex;
gap: 20px;
width: 100%;
}
}
}

View file

@ -50,4 +50,6 @@
--table-tr-hover-color: #172a66;
--dex-tooltip-bg: #11316b;
--dex-tooltip-border-color: #1f8feb26;
}
--dex-sell-percentage: #272757;
--dex-buy-percentage: #103262;
}

View file

@ -50,4 +50,6 @@
--table-tr-hover-color: #dcf0ff;
--dex-tooltip-bg: #eff8ff;
--dex-tooltip-border-color: #1f8feb33;
}
--dex-sell-percentage: #FCEDED;
--dex-buy-percentage: #E5F8F8;
}

View file

@ -12,8 +12,7 @@ interface takeOrderClickParams {
| React.MouseEvent<HTMLTableRowElement, MouseEvent>;
PageOrderData: PageOrderData;
pairData: PairDataType | null;
buyForm: OrderFormOutput;
sellForm: OrderFormOutput;
orderForm: OrderFormOutput;
balance: string | undefined;
scrollToOrderForm: () => void;
}
@ -22,8 +21,7 @@ function takeOrderClick({
event,
PageOrderData,
pairData,
buyForm,
sellForm,
orderForm,
balance,
scrollToOrderForm,
}: takeOrderClickParams) {
@ -36,59 +34,31 @@ function takeOrderClick({
const secondCurrencyDP = pairData?.second_currency?.asset_info?.decimal_point || 12;
const firstCurrencyDP = pairData?.first_currency?.asset_info?.decimal_point || 12;
if (e.type === 'sell') {
handleInputChange({
inputValue: priceStr,
priceOrAmount: 'price',
otherValue: amountStr,
thisDP: secondCurrencyDP,
totalDP: secondCurrencyDP,
setThisState: buyForm.setPrice,
setTotalState: buyForm.setTotal,
setThisValid: buyForm.setPriceValid,
setTotalValid: buyForm.setTotalValid,
});
handleInputChange({
inputValue: priceStr,
priceOrAmount: 'price',
otherValue: amountStr,
thisDP: secondCurrencyDP,
totalDP: secondCurrencyDP,
setThisState: orderForm.setPrice,
setTotalState: orderForm.setTotal,
setThisValid: orderForm.setPriceValid,
setTotalValid: orderForm.setTotalValid,
});
handleInputChange({
inputValue: amountStr,
priceOrAmount: 'amount',
otherValue: priceStr,
thisDP: firstCurrencyDP,
totalDP: secondCurrencyDP,
setThisState: buyForm.setAmount,
setTotalState: buyForm.setTotal,
setThisValid: buyForm.setAmountValid,
setTotalValid: buyForm.setTotalValid,
balance,
setRangeInputValue: buyForm.setRangeInputValue,
});
} else {
handleInputChange({
inputValue: priceStr,
priceOrAmount: 'price',
otherValue: amountStr,
thisDP: secondCurrencyDP,
totalDP: secondCurrencyDP,
setThisState: sellForm.setPrice,
setTotalState: sellForm.setTotal,
setThisValid: sellForm.setPriceValid,
setTotalValid: sellForm.setTotalValid,
});
handleInputChange({
inputValue: amountStr,
priceOrAmount: 'amount',
otherValue: priceStr,
thisDP: firstCurrencyDP,
totalDP: secondCurrencyDP,
setThisState: sellForm.setAmount,
setTotalState: sellForm.setTotal,
setThisValid: sellForm.setAmountValid,
setTotalValid: sellForm.setTotalValid,
balance,
setRangeInputValue: sellForm.setRangeInputValue,
});
}
handleInputChange({
inputValue: amountStr,
priceOrAmount: 'amount',
otherValue: priceStr,
thisDP: firstCurrencyDP,
totalDP: secondCurrencyDP,
setThisState: orderForm.setAmount,
setTotalState: orderForm.setTotal,
setThisValid: orderForm.setAmountValid,
setTotalValid: orderForm.setTotalValid,
balance,
setRangeInputValue: orderForm.setRangeInputValue,
});
scrollToOrderForm();
}

View file

@ -86,13 +86,14 @@ export const roundTo = (x: number | string, digits = 7) => {
return fixedValue.replace(/(\.\d*?[1-9])0+$/g, '$1').replace(/\.0+$/, '');
};
export const notationToString = (notation: number | string) => {
export const notationToString = (notation: number | string, fixed?: number) => {
const decimalValue = new Decimal(notation || '0');
const fixedValue = decimalValue.toFixed();
if (fixed !== undefined) {
return decimalValue.toDecimalPlaces(fixed).toString();
}
// Remove trailing zeros
return fixedValue;
return decimalValue.toString();
};
export const localeTimeLeft = (now: number | null, timestamp: number) => {