feat: redesign and grouping orders table
This commit is contained in:
parent
6bd495c3e9
commit
d4a703f5a8
31 changed files with 1319 additions and 689 deletions
14
src/components/UI/ActionBtn/index.tsx
Normal file
14
src/components/UI/ActionBtn/index.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import { classes } from '@/utils/utils';
|
||||
import { ActionBtnProps } from './types';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const ActionBtn = ({ children, variant = 'primary', className, ...props }: ActionBtnProps) => {
|
||||
return (
|
||||
<button className={classes(styles.btn, className, styles[variant])} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionBtn;
|
||||
34
src/components/UI/ActionBtn/styles.module.scss
Normal file
34
src/components/UI/ActionBtn/styles.module.scss
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
.btn {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 5px;
|
||||
background: #273666;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
min-width: max-content;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
background: #273666;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: #1f8feb;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: #16d1d6;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #ff6767;
|
||||
}
|
||||
}
|
||||
5
src/components/UI/ActionBtn/types.ts
Normal file
5
src/components/UI/ActionBtn/types.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { ButtonHTMLAttributes } from 'react';
|
||||
|
||||
export interface ActionBtnProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'success' | 'danger';
|
||||
}
|
||||
95
src/components/default/GenericTable/index.tsx
Normal file
95
src/components/default/GenericTable/index.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { classes } from '@/utils/utils';
|
||||
import React from 'react';
|
||||
import EmptyMessage from '@/components/UI/EmptyMessage';
|
||||
import { GenericTableProps } from './types';
|
||||
|
||||
export default function GenericTable<T>(props: GenericTableProps<T>) {
|
||||
const {
|
||||
className,
|
||||
tableClassName,
|
||||
theadClassName,
|
||||
tbodyClassName,
|
||||
columns,
|
||||
data,
|
||||
getRowKey,
|
||||
emptyMessage = 'No data',
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{data.length > 0 ? (
|
||||
<div className="orders-scroll" style={{ maxHeight: '100%', overflowY: 'auto' }}>
|
||||
<table
|
||||
className={tableClassName}
|
||||
style={{
|
||||
tableLayout: 'fixed',
|
||||
width: '100%',
|
||||
borderCollapse: 'separate',
|
||||
borderSpacing: 0,
|
||||
}}
|
||||
>
|
||||
<colgroup>
|
||||
{columns.map((col) => (
|
||||
<col
|
||||
key={col.key}
|
||||
style={col.width ? { width: col.width } : undefined}
|
||||
/>
|
||||
))}
|
||||
</colgroup>
|
||||
|
||||
<thead className={theadClassName}>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.key}
|
||||
className={col.className}
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 2,
|
||||
background: 'var(--window-bg-color)',
|
||||
textAlign: col.align ?? 'left',
|
||||
whiteSpace: 'nowrap',
|
||||
overflowX: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
padding: '6px 10px',
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{col.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody className={tbodyClassName}>
|
||||
{data.map((row, i) => (
|
||||
<tr key={getRowKey(row, i)}>
|
||||
{columns.map((col) => (
|
||||
<td
|
||||
key={col.key}
|
||||
className={classes(col.className)}
|
||||
style={{
|
||||
textAlign: col.align ?? 'left',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
padding: '6px 10px',
|
||||
verticalAlign: 'middle',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{col.cell(row, i)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyMessage text={emptyMessage} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/components/default/GenericTable/types.ts
Normal file
21
src/components/default/GenericTable/types.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export type Align = 'left' | 'center' | 'right';
|
||||
|
||||
export type ColumnDef<T> = {
|
||||
key: string;
|
||||
header: React.ReactNode;
|
||||
width?: string;
|
||||
align?: Align;
|
||||
className?: string;
|
||||
cell: (_row: T, _rowIndex: number) => React.ReactNode;
|
||||
};
|
||||
|
||||
export type GenericTableProps<T> = {
|
||||
className?: string;
|
||||
tableClassName?: string;
|
||||
theadClassName?: string;
|
||||
tbodyClassName?: string;
|
||||
columns: ColumnDef<T>[];
|
||||
data: T[];
|
||||
getRowKey: (_row: T, _rowIndex: number) => React.Key;
|
||||
emptyMessage?: string;
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import Tooltip from '@/components/UI/Tooltip/Tooltip';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ReactComponent as ConnectionIcon } from '@/assets/images/UI/connection.svg';
|
||||
import styles from './styles.module.scss';
|
||||
import { MatrixConnectionBadgeProps } from './types';
|
||||
|
|
@ -12,29 +13,71 @@ function MatrixConnectionBadge({
|
|||
const hasConnection = (address: string) =>
|
||||
matrixAddresses.some((item) => item.address === address && item.registered);
|
||||
|
||||
const [connectionTooltip, setConnectionTooltip] = useState(false);
|
||||
return userAdress && hasConnection(userAdress) ? (
|
||||
const [open, setOpen] = useState(false);
|
||||
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
|
||||
const anchorRef = useRef<HTMLAnchorElement | 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]);
|
||||
|
||||
if (!userAdress || !hasConnection(userAdress)) return <></>;
|
||||
|
||||
return (
|
||||
<div className={styles.badge}>
|
||||
<a
|
||||
ref={anchorRef}
|
||||
href={`https://matrix.to/#/@${userAlias}:zano.org`}
|
||||
target="_blank"
|
||||
onMouseEnter={() => setConnectionTooltip(true)}
|
||||
onMouseLeave={() => setConnectionTooltip(false)}
|
||||
onMouseEnter={() => {
|
||||
setOpen(true);
|
||||
requestAnimationFrame(updatePosition);
|
||||
}}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
className={styles.badge__link}
|
||||
>
|
||||
<ConnectionIcon />
|
||||
</a>
|
||||
|
||||
<Tooltip
|
||||
className={styles.badge__tooltip}
|
||||
arrowClass={styles.badge__tooltip_arrow}
|
||||
shown={connectionTooltip}
|
||||
>
|
||||
<p className={styles.badge__tooltip_text}>Matrix connection</p>
|
||||
</Tooltip>
|
||||
{open &&
|
||||
pos &&
|
||||
createPortal(
|
||||
<Tooltip
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: pos.top,
|
||||
left: pos.left,
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
className={styles.badge__tooltip}
|
||||
arrowClass={styles.badge__tooltip_arrow}
|
||||
shown={true}
|
||||
>
|
||||
<p className={styles.badge__tooltip_text}>Matrix connection</p>
|
||||
</Tooltip>,
|
||||
document.body,
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
}
|
||||
|
||||
&__tooltip {
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
|
|
|
|||
386
src/components/dex/UserOrders/columns/index.tsx
Normal file
386
src/components/dex/UserOrders/columns/index.tsx
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
import { notationToString, formatTimestamp } from '@/utils/utils';
|
||||
import type OrderRow from '@/interfaces/common/OrderRow';
|
||||
import type { ColumnDef } from '@/components/default/GenericTable/types';
|
||||
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 RequestActionCell from '../components/RequestActionCell';
|
||||
import {
|
||||
BuildApplyTipsColumnsArgs,
|
||||
BuildMyRequestsColumnsArgs,
|
||||
BuildOrderHistoryColumnsArgs,
|
||||
BuildUserColumnsArgs,
|
||||
} from './types';
|
||||
|
||||
export function buildUserColumns({
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
secondAssetUsdPrice,
|
||||
offersCountByOrderId,
|
||||
onAfter,
|
||||
}: BuildUserColumnsArgs): ColumnDef<OrderRow>[] {
|
||||
return [
|
||||
{
|
||||
key: 'pair',
|
||||
header: 'Pair',
|
||||
width: '120px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={
|
||||
{
|
||||
'--direction-color': row.type === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{firstCurrencyName}/{secondCurrencyName}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'direction',
|
||||
header: 'Direction',
|
||||
width: '110px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={{
|
||||
color: row.type === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
>
|
||||
{row.type}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
header: <>Price ({secondCurrencyName})</>,
|
||||
width: '150px',
|
||||
cell: (row) => <p>{notationToString(row.price)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
header: <>Quantity ({firstCurrencyName})</>,
|
||||
width: '160px',
|
||||
cell: (row) => <p>{notationToString(row.amount)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
header: <>Total ({secondCurrencyName})</>,
|
||||
width: '180px',
|
||||
cell: (row) => (
|
||||
<TotalUsdCell
|
||||
amount={row.left}
|
||||
price={row.price}
|
||||
secondAssetUsdPrice={secondAssetUsdPrice}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'offers',
|
||||
header: 'Offers',
|
||||
width: '90px',
|
||||
cell: (row) => (
|
||||
<p style={{ fontWeight: 500, color: '#1F8FEB' }}>
|
||||
{offersCountByOrderId.get(String(row.id)) ?? 0}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'orderid',
|
||||
header: 'Order ID',
|
||||
width: '140px',
|
||||
cell: (row) => <p style={{ color: '#1F8FEB' }}>{row.pair_id}</p>,
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
header: 'Time',
|
||||
width: '180px',
|
||||
cell: (row) => <p>{formatTimestamp(row.timestamp)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
header: 'Action',
|
||||
width: '80px',
|
||||
align: 'left',
|
||||
cell: (row) => <CancelActionCell id={row.id} onAfter={onAfter} />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function buildApplyTipsColumns({
|
||||
type,
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
matrixAddresses,
|
||||
secondAssetUsdPrice,
|
||||
userOrders,
|
||||
pairData,
|
||||
onAfter,
|
||||
}: BuildApplyTipsColumnsArgs): ColumnDef<ApplyTip>[] {
|
||||
return [
|
||||
{
|
||||
key: 'pair',
|
||||
header: 'Pair',
|
||||
width: '120px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={
|
||||
{
|
||||
'--direction-color': row.type === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{firstCurrencyName}/{secondCurrencyName}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'direction',
|
||||
header: 'Direction',
|
||||
width: '110px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={{
|
||||
color: row.type === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
>
|
||||
{row.type}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'alias',
|
||||
header: 'Alias',
|
||||
width: '180px',
|
||||
cell: (row) => (
|
||||
<AliasCell
|
||||
alias={row.user?.alias}
|
||||
address={row.user?.address}
|
||||
matrixAddresses={matrixAddresses}
|
||||
isInstant={row.isInstant}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
header: <>Price ({secondCurrencyName})</>,
|
||||
width: '150px',
|
||||
cell: (row) => <p>{notationToString(row.price)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
header: <>Quantity ({firstCurrencyName})</>,
|
||||
width: '160px',
|
||||
cell: (row) => <p>{notationToString(row.left)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
header: <>Total ({secondCurrencyName})</>,
|
||||
width: '180px',
|
||||
cell: (row) => (
|
||||
<TotalUsdCell
|
||||
amount={row.left}
|
||||
price={row.price}
|
||||
secondAssetUsdPrice={secondAssetUsdPrice}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'orderid',
|
||||
header: 'Order ID',
|
||||
width: '140px',
|
||||
cell: (row) => <p style={{ color: '#1F8FEB' }}>{row.connected_order_id}</p>,
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
header: 'Time',
|
||||
width: '180px',
|
||||
cell: (row) => <p>{formatTimestamp(Number(row.timestamp))}</p>,
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
header: 'Action',
|
||||
width: type === 'offers' ? '150px' : '90px',
|
||||
cell: (row) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
<RequestActionCell
|
||||
type={type === 'suitables' ? 'request' : 'accept'}
|
||||
row={row}
|
||||
pairData={pairData}
|
||||
connectedOrder={userOrders.find((o) => o.id === row.connected_order_id)}
|
||||
onAfter={onAfter}
|
||||
/>
|
||||
{type === 'offers' && (
|
||||
<CancelActionCell
|
||||
type="reject"
|
||||
id={row.connected_order_id}
|
||||
onAfter={onAfter}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function buildMyRequestsColumns({
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
onAfter,
|
||||
}: BuildMyRequestsColumnsArgs): ColumnDef<UserPendingType>[] {
|
||||
return [
|
||||
{
|
||||
key: 'pair',
|
||||
header: 'Pair',
|
||||
width: '120px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={
|
||||
{
|
||||
'--direction-color': row.creator === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{firstCurrencyName}/{secondCurrencyName}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'direction',
|
||||
header: 'Direction',
|
||||
width: '110px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={{
|
||||
color: row.creator === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
>
|
||||
{row.creator}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
header: <>Quantity ({firstCurrencyName})</>,
|
||||
width: '160px',
|
||||
cell: (row) => <p>{notationToString(row.amount)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'sell_order_id',
|
||||
header: 'Sell Order ID',
|
||||
width: '140px',
|
||||
cell: (row) => <p style={{ color: '#1F8FEB' }}>{row.sell_order_id}</p>,
|
||||
},
|
||||
{
|
||||
key: 'buy_order_id',
|
||||
header: 'Buy Order ID',
|
||||
width: '140px',
|
||||
cell: (row) => <p style={{ color: '#1F8FEB' }}>{row.buy_order_id}</p>,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
width: '140px',
|
||||
cell: (row) => <p style={{ textTransform: 'capitalize' }}>{row.status}</p>,
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
header: 'Time',
|
||||
width: '180px',
|
||||
cell: (row) => <p>{formatTimestamp(row.timestamp)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
header: 'Action',
|
||||
width: '80px',
|
||||
align: 'left',
|
||||
cell: (row) => (
|
||||
<CancelActionCell
|
||||
id={String(row.creator === 'sell' ? row.sell_order_id : row.buy_order_id)}
|
||||
onAfter={onAfter}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function buildOrderHistoryColumns({
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
secondAssetUsdPrice,
|
||||
}: BuildOrderHistoryColumnsArgs): ColumnDef<UserOrderData>[] {
|
||||
return [
|
||||
{
|
||||
key: 'pair',
|
||||
header: 'Pair',
|
||||
width: '120px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={
|
||||
{
|
||||
'--direction-color': row.type === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{firstCurrencyName}/{secondCurrencyName}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'direction',
|
||||
header: 'Direction',
|
||||
width: '110px',
|
||||
cell: (row) => (
|
||||
<p
|
||||
style={{
|
||||
color: row.type === 'buy' ? '#16D1D6' : '#FF6767',
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
>
|
||||
{row.type}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'price',
|
||||
header: <>Price ({secondCurrencyName})</>,
|
||||
width: '150px',
|
||||
cell: (row) => <p>{notationToString(row.price)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
header: <>Quantity ({firstCurrencyName})</>,
|
||||
width: '160px',
|
||||
cell: (row) => <p>{notationToString(row.amount)}</p>,
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
header: <>Total ({secondCurrencyName})</>,
|
||||
width: '180px',
|
||||
cell: (row) => (
|
||||
<TotalUsdCell
|
||||
amount={row.left}
|
||||
price={row.price}
|
||||
secondAssetUsdPrice={secondAssetUsdPrice}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'orderid',
|
||||
header: 'Order ID',
|
||||
width: '140px',
|
||||
cell: (row) => <p style={{ color: '#1F8FEB' }}>{row.pair_id}</p>,
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
header: 'Time',
|
||||
width: '180px',
|
||||
cell: (row) => <p>{formatTimestamp(row.timestamp)}</p>,
|
||||
},
|
||||
];
|
||||
}
|
||||
34
src/components/dex/UserOrders/columns/types.ts
Normal file
34
src/components/dex/UserOrders/columns/types.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import MatrixAddress from '@/interfaces/common/MatrixAddress';
|
||||
import OrderRow from '@/interfaces/common/OrderRow';
|
||||
import PairData from '@/interfaces/common/PairData';
|
||||
|
||||
export interface BuildUserColumnsArgs {
|
||||
firstCurrencyName: string;
|
||||
secondCurrencyName: string;
|
||||
secondAssetUsdPrice?: number;
|
||||
offersCountByOrderId: Map<string, number>;
|
||||
onAfter: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface BuildApplyTipsColumnsArgs {
|
||||
type: 'suitables' | 'offers';
|
||||
firstCurrencyName: string;
|
||||
secondCurrencyName: string;
|
||||
matrixAddresses: MatrixAddress[];
|
||||
secondAssetUsdPrice?: number;
|
||||
userOrders: OrderRow[];
|
||||
pairData: PairData | null;
|
||||
onAfter: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface BuildMyRequestsColumnsArgs {
|
||||
firstCurrencyName: string;
|
||||
secondCurrencyName: string;
|
||||
onAfter: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface BuildOrderHistoryColumnsArgs {
|
||||
firstCurrencyName: string;
|
||||
secondCurrencyName: string;
|
||||
secondAssetUsdPrice?: number;
|
||||
}
|
||||
47
src/components/dex/UserOrders/components/AliasCell/index.tsx
Normal file
47
src/components/dex/UserOrders/components/AliasCell/index.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import MatrixAddress from '@/interfaces/common/MatrixAddress';
|
||||
|
||||
export interface AliasCellProps {
|
||||
alias?: string;
|
||||
address?: string;
|
||||
matrixAddresses: MatrixAddress[];
|
||||
isInstant?: boolean;
|
||||
max?: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { useState } from 'react';
|
||||
import { useAlert } from '@/hook/useAlert';
|
||||
import { cancelOrder } from '@/utils/methods';
|
||||
import ActionBtn from '@/components/UI/ActionBtn';
|
||||
import { CancelActionCellProps } from './types';
|
||||
|
||||
export default function CancelActionCell({ type = 'cancel', id, onAfter }: CancelActionCellProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { setAlertState, setAlertSubtitle } = useAlert();
|
||||
|
||||
const onClick = async () => {
|
||||
if (loading) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await cancelOrder(id);
|
||||
if (!result.success) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error while cancelling order');
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
await onAfter();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionBtn
|
||||
variant={type === 'reject' ? 'danger' : 'primary'}
|
||||
disabled={loading}
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
{type === 'cancel' ? 'Cancel' : 'Reject'}
|
||||
</ActionBtn>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface CancelActionCellProps {
|
||||
type?: 'cancel' | 'reject';
|
||||
id: string;
|
||||
onAfter: () => Promise<void>;
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
import React, { useContext, useState } from 'react';
|
||||
import { cutAddress, formatDollarValue, notationToString } from '@/utils/utils';
|
||||
import { nanoid } from 'nanoid';
|
||||
import Tooltip from '@/components/UI/Tooltip/Tooltip';
|
||||
import Decimal from 'decimal.js';
|
||||
import { confirmIonicSwap, ionicSwap } from '@/utils/wallet';
|
||||
import { applyOrder, confirmTransaction } from '@/utils/methods';
|
||||
import { updateAutoClosedNotification } from '@/store/actions';
|
||||
import Link from 'next/link';
|
||||
import { Store } from '@/store/store-reducer';
|
||||
import { useAlert } from '@/hook/useAlert';
|
||||
import OrderRowTooltipCell from '../../../OrderRowTooltipCell';
|
||||
import MatrixConnectionBadge from '../../../MatrixConnectionBadge';
|
||||
import styles from '../../styles.module.scss';
|
||||
import { MyOrdersApplyRowProps } from './types';
|
||||
import BadgeStatus from '../../../BadgeStatus';
|
||||
|
||||
function MyOrdersApplyRow(props: MyOrdersApplyRowProps) {
|
||||
const {
|
||||
orderData,
|
||||
fetchUser,
|
||||
matrixAddresses,
|
||||
secondAssetUsdPrice,
|
||||
updateOrders,
|
||||
updateUserOrders,
|
||||
fetchTrades,
|
||||
pairData,
|
||||
userOrders,
|
||||
} = props;
|
||||
const e = orderData || {};
|
||||
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const { setAlertState, setAlertSubtitle } = useAlert();
|
||||
const [applyingState, setApplyingState] = useState(false);
|
||||
|
||||
const connectedOrder = userOrders.find((order) => order.id === e.connected_order_id);
|
||||
|
||||
const totalDecimal = new Decimal(e.left).mul(new Decimal(e.price));
|
||||
const totalValue = secondAssetUsdPrice
|
||||
? totalDecimal.mul(secondAssetUsdPrice).toFixed(2)
|
||||
: undefined;
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
async function applyClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
||||
event.preventDefault();
|
||||
|
||||
if (e.id) {
|
||||
updateAutoClosedNotification(dispatch, [
|
||||
...state.closed_notifications,
|
||||
parseInt(e.id, 10),
|
||||
]);
|
||||
}
|
||||
|
||||
function alertErr(subtitle: string) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle(subtitle);
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
setApplyingState(true);
|
||||
interface SwapOperationResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
errorCode?: number;
|
||||
data?: unknown;
|
||||
}
|
||||
|
||||
let result: SwapOperationResult | null = null;
|
||||
|
||||
await (async () => {
|
||||
if (e.transaction) {
|
||||
if (!e.hex_raw_proposal) {
|
||||
alertErr('Invalid transaction data received');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(e.hex_raw_proposal);
|
||||
|
||||
const confirmSwapResult = await confirmIonicSwap(e.hex_raw_proposal);
|
||||
|
||||
console.log(confirmSwapResult);
|
||||
|
||||
if (confirmSwapResult.data?.error?.code === -7) {
|
||||
alertErr('Insufficient funds');
|
||||
return;
|
||||
}
|
||||
if (!confirmSwapResult.data?.result) {
|
||||
alertErr('Companion responded with an error');
|
||||
return;
|
||||
}
|
||||
|
||||
result = await confirmTransaction(e.id);
|
||||
} else {
|
||||
const firstCurrencyId = pairData?.first_currency.asset_id;
|
||||
const secondCurrencyId = pairData?.second_currency.asset_id;
|
||||
|
||||
console.log(firstCurrencyId, secondCurrencyId);
|
||||
|
||||
if (!(firstCurrencyId && secondCurrencyId)) {
|
||||
alertErr('Invalid transaction data received');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connectedOrder) return;
|
||||
|
||||
const leftDecimal = new Decimal(e.left);
|
||||
const priceDecimal = new Decimal(e.price);
|
||||
|
||||
const params = {
|
||||
destinationAssetID: e.type === 'buy' ? secondCurrencyId : firstCurrencyId,
|
||||
destinationAssetAmount: notationToString(
|
||||
e.type === 'buy'
|
||||
? leftDecimal.mul(priceDecimal).toString()
|
||||
: leftDecimal.toString(),
|
||||
),
|
||||
currentAssetID: e.type === 'buy' ? firstCurrencyId : secondCurrencyId,
|
||||
currentAssetAmount: notationToString(
|
||||
e.type === 'buy'
|
||||
? leftDecimal.toString()
|
||||
: leftDecimal.mul(priceDecimal).toString(),
|
||||
),
|
||||
|
||||
destinationAddress: e.user.address,
|
||||
};
|
||||
|
||||
console.log(params);
|
||||
|
||||
const createSwapResult = await ionicSwap(params);
|
||||
|
||||
console.log(createSwapResult);
|
||||
|
||||
const hex = createSwapResult?.data?.result?.hex_raw_proposal;
|
||||
|
||||
if (createSwapResult?.data?.error?.code === -7) {
|
||||
alertErr('Insufficient funds');
|
||||
return;
|
||||
}
|
||||
if (!hex) {
|
||||
alertErr('Companion responded with an error');
|
||||
return;
|
||||
}
|
||||
|
||||
result = await applyOrder({
|
||||
...e,
|
||||
hex_raw_proposal: hex,
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
setApplyingState(false);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(result as { success: boolean }).success) {
|
||||
alertErr('Server responded with an error');
|
||||
return;
|
||||
}
|
||||
|
||||
await updateOrders();
|
||||
await updateUserOrders();
|
||||
await fetchUser();
|
||||
await fetchTrades();
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={nanoid(16)}>
|
||||
<td>
|
||||
<p className={styles.alias}>
|
||||
<span
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
className={styles.alias__text}
|
||||
>
|
||||
@{cutAddress(e.user.alias, 12) || 'no alias'}
|
||||
</span>
|
||||
|
||||
<MatrixConnectionBadge
|
||||
userAdress={e.user.address}
|
||||
userAlias={e.user.alias}
|
||||
matrixAddresses={matrixAddresses}
|
||||
/>
|
||||
{e.isInstant && (
|
||||
<div style={{ marginLeft: 2 }}>
|
||||
<BadgeStatus type="instant" icon />
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{(e.isInstant || e.transaction) && <BadgeStatus type="instant" />}
|
||||
|
||||
{e.user?.alias.length > 12 && (
|
||||
<Tooltip
|
||||
className={styles.tooltip}
|
||||
arrowClass={styles.tooltip__arrow}
|
||||
shown={showTooltip}
|
||||
>
|
||||
<p className={styles.tooltip__text}>{e.user?.alias}</p>
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
|
||||
<OrderRowTooltipCell style={{ color: e.type === 'buy' ? '#16D1D6' : '#FF6767' }}>
|
||||
{notationToString(e.price)}
|
||||
</OrderRowTooltipCell>
|
||||
<OrderRowTooltipCell>{notationToString(e.left)}</OrderRowTooltipCell>
|
||||
|
||||
<OrderRowTooltipCell
|
||||
noTooltip
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
|
||||
>
|
||||
{notationToString(totalDecimal.toString())}{' '}
|
||||
<span>~ ${totalValue && formatDollarValue(totalValue)}</span>
|
||||
</OrderRowTooltipCell>
|
||||
|
||||
<td></td>
|
||||
<td>
|
||||
<Link href="/" onClick={applyClick}>
|
||||
{applyingState ? 'Process' : 'Apply'}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyOrdersApplyRow;
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import ApplyTip from '@/interfaces/common/ApplyTip';
|
||||
import MatrixAddress from '@/interfaces/common/MatrixAddress';
|
||||
import OrderRow from '@/interfaces/common/OrderRow';
|
||||
import PairData from '@/interfaces/common/PairData';
|
||||
|
||||
export interface MyOrdersApplyRowProps {
|
||||
orderData: ApplyTip;
|
||||
secondAssetUsdPrice: number | undefined;
|
||||
updateOrders: () => Promise<void>;
|
||||
updateUserOrders: () => Promise<void>;
|
||||
fetchUser: () => Promise<boolean>;
|
||||
fetchTrades: () => Promise<void>;
|
||||
matrixAddresses: MatrixAddress[];
|
||||
pairData: PairData | null;
|
||||
userOrders: OrderRow[];
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
import Decimal from 'decimal.js';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useContext, useState } from 'react';
|
||||
import { cutAddress, formatDollarValue, notationToString } from '@/utils/utils';
|
||||
import Tooltip from '@/components/UI/Tooltip/Tooltip';
|
||||
import Link from 'next/link';
|
||||
import { cancelOrder } from '@/utils/methods';
|
||||
import { Store } from '@/store/store-reducer';
|
||||
import { useAlert } from '@/hook/useAlert';
|
||||
import BadgeStatus from '../../../BadgeStatus';
|
||||
import styles from '../../styles.module.scss';
|
||||
import MatrixConnectionBadge from '../../../MatrixConnectionBadge';
|
||||
import OrderRowTooltipCell from '../../../OrderRowTooltipCell';
|
||||
import { MyOrdersRowProps } from './types';
|
||||
|
||||
function MyOrdersRow(props: MyOrdersRowProps) {
|
||||
const {
|
||||
orderData,
|
||||
secondAssetUsdPrice,
|
||||
fetchUser,
|
||||
updateOrders,
|
||||
updateUserOrders,
|
||||
matrixAddresses,
|
||||
applyTips,
|
||||
} = props;
|
||||
|
||||
const e = orderData || {};
|
||||
const { state } = useContext(Store);
|
||||
const { setAlertState, setAlertSubtitle } = useAlert();
|
||||
const [cancellingState, setCancellingState] = useState(false);
|
||||
|
||||
const totalDecimal = new Decimal(e.left).mul(new Decimal(e.price));
|
||||
const totalValue = secondAssetUsdPrice
|
||||
? totalDecimal.mul(secondAssetUsdPrice).toFixed(2)
|
||||
: undefined;
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
async function cancelClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
||||
event.preventDefault();
|
||||
|
||||
if (cancellingState) return;
|
||||
|
||||
try {
|
||||
setCancellingState(true);
|
||||
const result = await cancelOrder(e.id);
|
||||
|
||||
if (!result.success) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error while cancelling order');
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
await updateOrders();
|
||||
await updateUserOrders();
|
||||
await fetchUser();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setCancellingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={nanoid(16)}>
|
||||
<td>
|
||||
<p className={styles.alias}>
|
||||
<span
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
className={styles.alias__text}
|
||||
>
|
||||
@
|
||||
{cutAddress(
|
||||
state.wallet?.connected && state.wallet?.alias
|
||||
? state.wallet.alias
|
||||
: 'no alias',
|
||||
12,
|
||||
)}
|
||||
</span>
|
||||
|
||||
<MatrixConnectionBadge
|
||||
userAdress={state?.wallet?.address}
|
||||
userAlias={state.wallet?.alias}
|
||||
matrixAddresses={matrixAddresses}
|
||||
/>
|
||||
{e.isInstant && (
|
||||
<div style={{ marginLeft: 2 }}>
|
||||
<BadgeStatus type="instant" icon />
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{(state.wallet?.connected && state.wallet?.alias ? state.wallet?.alias : '')
|
||||
?.length > 12 && (
|
||||
<Tooltip
|
||||
className={styles.tooltip}
|
||||
arrowClass={styles.tooltip__arrow}
|
||||
shown={showTooltip}
|
||||
>
|
||||
<p className={styles.tooltip__text}>
|
||||
{state.wallet?.connected && state.wallet?.alias}
|
||||
</p>
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
|
||||
<OrderRowTooltipCell style={{ color: e.type === 'buy' ? '#16D1D6' : '#FF6767' }}>
|
||||
{notationToString(e.price)}
|
||||
</OrderRowTooltipCell>
|
||||
|
||||
<OrderRowTooltipCell>{notationToString(e.amount)}</OrderRowTooltipCell>
|
||||
|
||||
<OrderRowTooltipCell
|
||||
noTooltip
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
|
||||
>
|
||||
{notationToString(totalDecimal.toString())}{' '}
|
||||
<span>~ ${totalValue && formatDollarValue(totalValue)}</span>
|
||||
</OrderRowTooltipCell>
|
||||
|
||||
<td>
|
||||
<p style={{ fontWeight: 700, color: '#1F8FEB' }}>
|
||||
{applyTips?.filter((tip) => tip.connected_order_id === e.id)?.length || 0}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<Link href="/" onClick={cancelClick}>
|
||||
{cancellingState ? 'Process' : 'Cancel'}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyOrdersRow;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import ApplyTip from '@/interfaces/common/ApplyTip';
|
||||
import MatrixAddress from '@/interfaces/common/MatrixAddress';
|
||||
import OrderRow from '@/interfaces/common/OrderRow';
|
||||
|
||||
export interface MyOrdersRowProps {
|
||||
orderData: OrderRow;
|
||||
secondAssetUsdPrice: number | undefined;
|
||||
updateOrders: () => Promise<void>;
|
||||
updateUserOrders: () => Promise<void>;
|
||||
fetchUser: () => Promise<boolean>;
|
||||
matrixAddresses: MatrixAddress[];
|
||||
applyTips: ApplyTip[];
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import Link from 'next/link';
|
||||
import Decimal from 'decimal.js';
|
||||
import { useState, useContext } from 'react';
|
||||
import { Store } from '@/store/store-reducer';
|
||||
import { useAlert } from '@/hook/useAlert';
|
||||
import { applyOrder, confirmTransaction } from '@/utils/methods';
|
||||
import { confirmIonicSwap, ionicSwap } from '@/utils/wallet';
|
||||
import { updateAutoClosedNotification } from '@/store/actions';
|
||||
import { notationToString } from '@/utils/utils';
|
||||
import ActionBtn from '@/components/UI/ActionBtn';
|
||||
import { RequestActionCellProps } from './types';
|
||||
|
||||
export default function RequestActionCell({
|
||||
type = 'request',
|
||||
row,
|
||||
pairData,
|
||||
onAfter,
|
||||
connectedOrder,
|
||||
userOrders,
|
||||
}: RequestActionCellProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const { setAlertState, setAlertSubtitle } = useAlert();
|
||||
|
||||
const _connectedOrder =
|
||||
connectedOrder ?? userOrders?.find((o) => o.id === row.connected_order_id);
|
||||
|
||||
const alertErr = (subtitle: string) => {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle(subtitle);
|
||||
setTimeout(() => {
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const onClick = async () => {
|
||||
setLoading(true);
|
||||
|
||||
let result: { success: boolean } | null = null;
|
||||
|
||||
try {
|
||||
if (row.id) {
|
||||
updateAutoClosedNotification(dispatch, [
|
||||
...state.closed_notifications,
|
||||
parseInt(String(row.id), 10),
|
||||
]);
|
||||
}
|
||||
|
||||
if (row.transaction) {
|
||||
if (!row.hex_raw_proposal) {
|
||||
alertErr('Invalid transaction data received');
|
||||
return;
|
||||
}
|
||||
const confirmSwapResult = await confirmIonicSwap(row.hex_raw_proposal);
|
||||
if (confirmSwapResult.data?.error?.code === -7) {
|
||||
alertErr('Insufficient funds');
|
||||
return;
|
||||
}
|
||||
if (!confirmSwapResult.data?.result) {
|
||||
alertErr('Companion responded with an error');
|
||||
return;
|
||||
}
|
||||
result = await confirmTransaction(row.id);
|
||||
} else {
|
||||
const firstCurrencyId = pairData?.first_currency.asset_id;
|
||||
const secondCurrencyId = pairData?.second_currency.asset_id;
|
||||
if (!(firstCurrencyId && secondCurrencyId)) {
|
||||
alertErr('Invalid transaction data received');
|
||||
return;
|
||||
}
|
||||
if (!_connectedOrder) return;
|
||||
|
||||
const leftDecimal = new Decimal(row.left);
|
||||
const priceDecimal = new Decimal(row.price);
|
||||
|
||||
const params = {
|
||||
destinationAssetID: row.type === 'buy' ? secondCurrencyId : firstCurrencyId,
|
||||
destinationAssetAmount: notationToString(
|
||||
row.type === 'buy'
|
||||
? leftDecimal.mul(priceDecimal).toString()
|
||||
: leftDecimal.toString(),
|
||||
),
|
||||
currentAssetID: row.type === 'buy' ? firstCurrencyId : secondCurrencyId,
|
||||
currentAssetAmount: notationToString(
|
||||
row.type === 'buy'
|
||||
? leftDecimal.toString()
|
||||
: leftDecimal.mul(priceDecimal).toString(),
|
||||
),
|
||||
destinationAddress: row.user.address,
|
||||
};
|
||||
|
||||
const createSwapResult = await ionicSwap(params);
|
||||
const hex = createSwapResult?.data?.result?.hex_raw_proposal;
|
||||
|
||||
if (createSwapResult?.data?.error?.code === -7) {
|
||||
alertErr('Insufficient funds');
|
||||
return;
|
||||
}
|
||||
if (!hex) {
|
||||
alertErr('Companion responded with an error');
|
||||
return;
|
||||
}
|
||||
|
||||
result = await applyOrder({ ...row, hex_raw_proposal: hex });
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
if (!result) return;
|
||||
if (!result.success) {
|
||||
alertErr('Server responded with an error');
|
||||
return;
|
||||
}
|
||||
await onAfter();
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionBtn variant="success" disabled={loading} onClick={() => onClick()}>
|
||||
{type === 'request' ? 'Request' : 'Accept'}
|
||||
</ActionBtn>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import ApplyTip from '@/interfaces/common/ApplyTip';
|
||||
import OrderRow from '@/interfaces/common/OrderRow';
|
||||
import PairData from '@/interfaces/common/PairData';
|
||||
|
||||
export interface RequestActionCellProps {
|
||||
type?: 'request' | 'accept';
|
||||
row: ApplyTip;
|
||||
pairData: PairData | null;
|
||||
onAfter: () => Promise<void>;
|
||||
connectedOrder?: OrderRow;
|
||||
userOrders?: OrderRow[];
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { useMemo } from 'react';
|
||||
import Decimal from 'decimal.js';
|
||||
import { notationToString, formatDollarValue } from '@/utils/utils';
|
||||
import { TotalUsdCellProps } from './types';
|
||||
|
||||
export default function TotalUsdCell({ amount, price, secondAssetUsdPrice }: TotalUsdCellProps) {
|
||||
const total = useMemo(
|
||||
() => new Decimal(amount || 0).mul(new Decimal(price || 0)),
|
||||
[amount, price],
|
||||
);
|
||||
const usd = secondAssetUsdPrice ? total.mul(secondAssetUsdPrice).toFixed(2) : undefined;
|
||||
|
||||
return (
|
||||
<p>
|
||||
{notationToString(total.toString())} <span>~ ${usd && formatDollarValue(usd)}</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface TotalUsdCellProps {
|
||||
amount: string | number;
|
||||
price: string | number;
|
||||
secondAssetUsdPrice?: number;
|
||||
}
|
||||
|
|
@ -2,16 +2,28 @@ 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 GenericTable from '@/components/default/GenericTable';
|
||||
import ActionBtn from '@/components/UI/ActionBtn';
|
||||
import { getUserOrders, getUserPendings } from '@/utils/methods';
|
||||
import UserPendingType from '@/interfaces/common/UserPendingType';
|
||||
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 styles from './styles.module.scss';
|
||||
import { UserOrdersProps } from './types';
|
||||
import MyOrdersRow from './components/MyOrdersRow';
|
||||
import MyOrdersApplyRow from './components/MyOrdersApplyRow';
|
||||
import {
|
||||
buildApplyTipsColumns,
|
||||
buildMyRequestsColumns,
|
||||
buildOrderHistoryColumns,
|
||||
buildUserColumns,
|
||||
} from './columns';
|
||||
|
||||
const UserOrders = ({
|
||||
userOrders,
|
||||
applyTips,
|
||||
myOrdersLoading,
|
||||
loggedIn,
|
||||
ordersType,
|
||||
setOrdersType,
|
||||
handleCancelAllOrders,
|
||||
|
|
@ -23,110 +35,271 @@ const UserOrders = ({
|
|||
fetchTrades,
|
||||
pairData,
|
||||
}: UserOrdersProps) => {
|
||||
const { state } = useContext(Store);
|
||||
const loggedIn = !!state.wallet?.connected;
|
||||
const { setAlertState, setAlertSubtitle, alertState, alertSubtitle } = useAlert();
|
||||
|
||||
const fetchUser = useUpdateUser();
|
||||
const firstCurrencyName = pairData?.first_currency?.name || '';
|
||||
const secondCurrencyName = pairData?.second_currency?.name || '';
|
||||
const suitables = applyTips.filter((s) => !s.transaction);
|
||||
const offers = applyTips.filter((s) => s.transaction);
|
||||
const [userRequests, setUserRequests] = useState<UserPendingType[]>([]);
|
||||
const [ordersHistory, setOrdersHistory] = useState<UserOrderData[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const requestsData = await getUserPendings();
|
||||
|
||||
if (requestsData.success) {
|
||||
setUserRequests(requestsData.data);
|
||||
}
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
const result = await getUserOrders();
|
||||
|
||||
if (!result.success) {
|
||||
setAlertState('error');
|
||||
setAlertSubtitle('Error loading orders data');
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
setAlertState(null);
|
||||
setAlertSubtitle('');
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredOrdersHistory = result.data
|
||||
.filter((s) => s.pair_id === pairData?.id)
|
||||
.filter((s) => s.status === 'finished');
|
||||
|
||||
fetchUser();
|
||||
|
||||
setOrdersHistory(filteredOrdersHistory);
|
||||
})();
|
||||
}, [userOrders, applyTips]);
|
||||
|
||||
const firstCurrencyName = pairData?.first_currency?.name ?? '';
|
||||
const secondCurrencyName = pairData?.second_currency?.name ?? '';
|
||||
|
||||
const onAfter = useCallback(async () => {
|
||||
await updateOrders();
|
||||
await updateUserOrders();
|
||||
await fetchUser();
|
||||
await fetchTrades();
|
||||
}, [updateOrders, updateUserOrders, fetchUser, fetchTrades]);
|
||||
|
||||
const offersCountByOrderId = useMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
for (const tip of applyTips) {
|
||||
const key = String(tip.connected_order_id);
|
||||
map.set(key, (map.get(key) ?? 0) + 1);
|
||||
}
|
||||
return map;
|
||||
}, [applyTips]);
|
||||
|
||||
const columnsOpened = useMemo(
|
||||
() =>
|
||||
buildUserColumns({
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
secondAssetUsdPrice,
|
||||
offersCountByOrderId,
|
||||
onAfter,
|
||||
}),
|
||||
[firstCurrencyName, secondCurrencyName, secondAssetUsdPrice, offersCountByOrderId, onAfter],
|
||||
);
|
||||
|
||||
const columnsSuitables = useMemo(
|
||||
() =>
|
||||
buildApplyTipsColumns({
|
||||
type: 'suitables',
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
matrixAddresses,
|
||||
secondAssetUsdPrice,
|
||||
userOrders,
|
||||
pairData,
|
||||
onAfter,
|
||||
}),
|
||||
[
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
matrixAddresses,
|
||||
secondAssetUsdPrice,
|
||||
userOrders,
|
||||
pairData,
|
||||
onAfter,
|
||||
],
|
||||
);
|
||||
|
||||
const columnsMyRequests = useMemo(
|
||||
() =>
|
||||
buildMyRequestsColumns({
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
onAfter,
|
||||
}),
|
||||
[firstCurrencyName, secondCurrencyName, onAfter],
|
||||
);
|
||||
|
||||
const columnsOffers = useMemo(
|
||||
() =>
|
||||
buildApplyTipsColumns({
|
||||
type: 'offers',
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
matrixAddresses,
|
||||
secondAssetUsdPrice,
|
||||
userOrders,
|
||||
pairData,
|
||||
onAfter,
|
||||
}),
|
||||
[
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
matrixAddresses,
|
||||
secondAssetUsdPrice,
|
||||
userOrders,
|
||||
pairData,
|
||||
onAfter,
|
||||
],
|
||||
);
|
||||
|
||||
const columnsOrderHistory = useMemo(
|
||||
() =>
|
||||
buildOrderHistoryColumns({
|
||||
firstCurrencyName,
|
||||
secondCurrencyName,
|
||||
secondAssetUsdPrice,
|
||||
}),
|
||||
[firstCurrencyName, secondCurrencyName, secondAssetUsdPrice],
|
||||
);
|
||||
|
||||
const renderTable = () => {
|
||||
switch (ordersType) {
|
||||
case 'opened':
|
||||
return (
|
||||
<GenericTable
|
||||
className={styles.userOrders__body}
|
||||
columns={columnsOpened}
|
||||
data={userOrders}
|
||||
getRowKey={(r) => r.id}
|
||||
emptyMessage="No orders"
|
||||
/>
|
||||
);
|
||||
case 'suitable':
|
||||
return (
|
||||
<GenericTable
|
||||
className={styles.userOrders__body}
|
||||
columns={columnsSuitables}
|
||||
data={suitables}
|
||||
getRowKey={(r) => r.id}
|
||||
emptyMessage="No suitables"
|
||||
/>
|
||||
);
|
||||
case 'requests':
|
||||
return (
|
||||
<GenericTable
|
||||
className={styles.userOrders__body}
|
||||
columns={columnsMyRequests}
|
||||
data={userRequests}
|
||||
getRowKey={(r) => r.id}
|
||||
emptyMessage="No requests"
|
||||
/>
|
||||
);
|
||||
case 'offers':
|
||||
return (
|
||||
<GenericTable
|
||||
className={styles.userOrders__body}
|
||||
columns={columnsOffers}
|
||||
data={offers}
|
||||
getRowKey={(r) => r.id}
|
||||
emptyMessage="No offers"
|
||||
/>
|
||||
);
|
||||
case 'history':
|
||||
return (
|
||||
<GenericTable
|
||||
className={styles.userOrders__body}
|
||||
columns={columnsOrderHistory}
|
||||
data={ordersHistory}
|
||||
getRowKey={(r) => r.id}
|
||||
emptyMessage="No data"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
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}>
|
||||
<button
|
||||
onClick={() => setOrdersType('opened')}
|
||||
className={classes(
|
||||
styles.navItem,
|
||||
ordersType === 'opened' && styles.active,
|
||||
)}
|
||||
>
|
||||
Opened orders {applyTips?.length ? <span>{applyTips?.length}</span> : ''}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setOrdersType('history')}
|
||||
className={classes(
|
||||
styles.navItem,
|
||||
ordersType === 'history' && styles.active,
|
||||
)}
|
||||
>
|
||||
Orders History
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.trading__user_cancelOrder}>
|
||||
<button
|
||||
className={styles.userOrders__header_btn}
|
||||
onClick={handleCancelAllOrders}
|
||||
>
|
||||
Cancel all orders
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alias</th>
|
||||
<th>Price ({secondCurrencyName})</th>
|
||||
<th>Amount ({firstCurrencyName})</th>
|
||||
<th>Total ({secondCurrencyName})</th>
|
||||
<th>Offers</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
{!myOrdersLoading && loggedIn && !!userOrders.length && (
|
||||
<div className={`${styles.userOrders__body} orders-scroll`}>
|
||||
<table>
|
||||
<tbody className={styles.incoming}>
|
||||
{userOrders.map((e) => (
|
||||
<MyOrdersRow
|
||||
key={e.id}
|
||||
orderData={e}
|
||||
applyTips={applyTips}
|
||||
fetchUser={fetchUser}
|
||||
matrixAddresses={matrixAddresses}
|
||||
secondAssetUsdPrice={secondAssetUsdPrice}
|
||||
updateOrders={updateOrders}
|
||||
updateUserOrders={updateUserOrders}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{!!applyTips.length && (
|
||||
<table className={styles.apply}>
|
||||
<tbody>
|
||||
{applyTips.map((e) => (
|
||||
<MyOrdersApplyRow
|
||||
key={e.id}
|
||||
pairData={pairData}
|
||||
orderData={e}
|
||||
userOrders={userOrders}
|
||||
fetchTrades={fetchTrades}
|
||||
fetchUser={fetchUser}
|
||||
matrixAddresses={matrixAddresses}
|
||||
secondAssetUsdPrice={secondAssetUsdPrice}
|
||||
updateOrders={updateOrders}
|
||||
updateUserOrders={updateUserOrders}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{ordersType === 'opened' && userOrders.length > 0 && (
|
||||
<ActionBtn
|
||||
className={styles.userOrders__header_btn}
|
||||
onClick={handleCancelAllOrders}
|
||||
>
|
||||
Cancel all
|
||||
</ActionBtn>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!myOrdersLoading && loggedIn && renderTable()}
|
||||
|
||||
{myOrdersLoading && loggedIn && <ContentPreloader style={{ marginTop: 40 }} />}
|
||||
|
||||
{!loggedIn && <EmptyMessage text="Connect wallet to see your orders" />}
|
||||
|
||||
{loggedIn && !userOrders.length && !myOrdersLoading && (
|
||||
<EmptyMessage text="No orders" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{alertState && (
|
||||
<Alert
|
||||
type={alertState}
|
||||
subtitle={alertSubtitle || ''}
|
||||
close={() => setAlertState(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
.userOrders {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
background: var(--window-bg-color);
|
||||
border: 1px solid var(--delimiter-color);
|
||||
border-radius: 10px;
|
||||
min-height: 310px;
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
width: 520px;
|
||||
&__body {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 20px;
|
||||
height: 260px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__header {
|
||||
border-bottom: 1px solid var(--delimiter-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 0;
|
||||
border-bottom: 1px solid var(--delimiter-color);
|
||||
|
||||
&_nav {
|
||||
display: flex;
|
||||
|
|
@ -24,37 +31,24 @@
|
|||
gap: 22px;
|
||||
|
||||
.navItem {
|
||||
cursor: pointer;
|
||||
padding-bottom: 7px;
|
||||
position: relative;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-weight: 600;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
top: -10px;
|
||||
right: -15px;
|
||||
position: absolute;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
color: #fff;
|
||||
background-color: #ff6767;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
border-radius: 50%;
|
||||
}
|
||||
color: #1f8feb;
|
||||
|
||||
&.active {
|
||||
color: var(--text-color);
|
||||
border-color: #1f8feb;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #1f8feb;
|
||||
border-color: #1f8feb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,128 +57,62 @@
|
|||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
background-color: #1f8feb33;
|
||||
color: #1f8feb;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: #1f8feb43;
|
||||
}
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
|
||||
thead {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-inline: 10px;
|
||||
padding-bottom: 5px;
|
||||
margin-top: 10px;
|
||||
th {
|
||||
min-width: 100px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
color: var(--table-th-color);
|
||||
padding: 6px 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
tr {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
th {
|
||||
min-width: 100px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
color: var(--table-th-color);
|
||||
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
min-width: 50px;
|
||||
}
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
min-width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
a {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
td {
|
||||
position: relative;
|
||||
|
||||
&__body {
|
||||
padding: 10px;
|
||||
padding-bottom: 20px;
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
&.apply {
|
||||
border-top: 1px solid #1f8feb40;
|
||||
border-bottom: 1px solid #1f8feb40;
|
||||
background-color: var(--blur-color) !important;
|
||||
|
||||
tr {
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--delimiter-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.incoming {
|
||||
tr {
|
||||
&:nth-child(even) {
|
||||
background-color: var(--table-even-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
> p {
|
||||
width: 100%;
|
||||
padding: 4px 0;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
|
||||
&:last-child {
|
||||
min-width: 50px;
|
||||
&:first-child {
|
||||
&::before {
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 50%;
|
||||
width: 2px;
|
||||
background-color: var(--direction-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
min-width: 0;
|
||||
|
||||
&:last-child {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
> span {
|
||||
line-height: 1;
|
||||
color: var(--font-dimmed-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
> span {
|
||||
line-height: 1;
|
||||
color: var(--font-dimmed-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -212,7 +140,6 @@
|
|||
top: 30px;
|
||||
left: 5%;
|
||||
background-color: var(--trade-table-tooltip);
|
||||
|
||||
z-index: 9999;
|
||||
|
||||
&__text {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,21 @@ import OrderRow from '@/interfaces/common/OrderRow';
|
|||
import PairData from '@/interfaces/common/PairData';
|
||||
import { Dispatch, ForwardedRef, SetStateAction } 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;
|
||||
loggedIn: boolean;
|
||||
ordersType: 'opened' | 'history';
|
||||
setOrdersType: Dispatch<SetStateAction<'opened' | 'history'>>;
|
||||
ordersType: OrderType;
|
||||
setOrdersType: Dispatch<SetStateAction<OrderType>>;
|
||||
handleCancelAllOrders: () => void;
|
||||
secondAssetUsdPrice: number | undefined;
|
||||
updateOrders: () => Promise<void>;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => {
|
|||
firstCurrencyName: pairData?.first_currency?.name || '',
|
||||
secondCurrencyName: pairData?.second_currency?.name || '',
|
||||
};
|
||||
const loggedIn = !!state.wallet?.connected;
|
||||
|
||||
const assets = state.wallet?.connected ? state.wallet?.assets || [] : [];
|
||||
const balance = assets.find((e) => e.ticker === currencyNames.firstCurrencyName)?.balance;
|
||||
|
|
@ -57,7 +56,6 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => {
|
|||
firstAssetLink,
|
||||
secondAssetLink,
|
||||
secondAssetUsdPrice,
|
||||
loggedIn,
|
||||
balance,
|
||||
buyForm,
|
||||
sellForm,
|
||||
|
|
|
|||
12
src/interfaces/common/UserPendingType.ts
Normal file
12
src/interfaces/common/UserPendingType.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
interface UserPendingType {
|
||||
id: number;
|
||||
amount: string;
|
||||
buy_order_id: number;
|
||||
sell_order_id: number;
|
||||
creator: 'sell' | 'buy';
|
||||
hex_raw_proposal: string;
|
||||
status: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export default UserPendingType;
|
||||
|
|
@ -30,6 +30,7 @@ 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';
|
||||
|
||||
const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }];
|
||||
const DEFAULT_CHART = CHART_OPTIONS[0];
|
||||
|
|
@ -50,7 +51,7 @@ function Trading() {
|
|||
const [myOrdersLoading, setMyOrdersLoading] = useState(true);
|
||||
const [ordersBuySell, setOrdersBuySell] = useState(buySellValues[0]);
|
||||
const [tradesType, setTradesType] = useState<'all' | 'my'>('all');
|
||||
const [ordersType, setOrdersType] = useState<'opened' | 'history'>('opened');
|
||||
const [ordersType, setOrdersType] = useState<OrderType>('opened');
|
||||
const [pairStats, setPairStats] = useState<PairStats | null>(null);
|
||||
const [applyTips, setApplyTips] = useState<ApplyTip[]>([]);
|
||||
const matrixAddresses = useMatrixAddresses(ordersHistory);
|
||||
|
|
@ -63,7 +64,6 @@ function Trading() {
|
|||
secondAssetLink,
|
||||
secondAssetUsdPrice,
|
||||
balance,
|
||||
loggedIn,
|
||||
pairRateUsd,
|
||||
} = useTradeInit({ pairData, pairStats });
|
||||
|
||||
|
|
@ -204,7 +204,6 @@ function Trading() {
|
|||
userOrders={userOrders}
|
||||
applyTips={applyTips}
|
||||
myOrdersLoading={myOrdersLoading}
|
||||
loggedIn={loggedIn}
|
||||
ordersType={ordersType}
|
||||
setOrdersType={setOrdersType}
|
||||
handleCancelAllOrders={handleCancelAllOrders}
|
||||
|
|
@ -215,35 +214,34 @@ function Trading() {
|
|||
fetchTrades={fetchTrades}
|
||||
pairData={pairData}
|
||||
/>
|
||||
</div>
|
||||
<div ref={orderFormRef} className={styles.trading__info_createOrders}>
|
||||
{['buy', 'sell'].map((type) => {
|
||||
const isBuy = type === 'buy';
|
||||
const form = isBuy ? buyForm : sellForm;
|
||||
|
||||
<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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{alertState && (
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@
|
|||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
height: 405px;
|
||||
|
||||
&_createOrders {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -233,9 +233,8 @@ svg {
|
|||
|
||||
.orders-scroll::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
|
||||
height: 6px;
|
||||
background-color: transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.orders-scroll::-webkit-scrollbar-thumb {
|
||||
|
|
|
|||
|
|
@ -320,14 +320,6 @@ export async function getChatChunk(
|
|||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function getZanoPrice() {
|
||||
return axios
|
||||
.get(
|
||||
'https://explorer.zano.org/api/price?asset_id=d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a',
|
||||
)
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function getTrades(pairId: string) {
|
||||
return axios
|
||||
.post(`/api/orders/get-trades`, {
|
||||
|
|
@ -336,6 +328,22 @@ export async function getTrades(pairId: string) {
|
|||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function getUserPendings() {
|
||||
return axios
|
||||
.post('/api/transactions/get-my-pending', {
|
||||
token: sessionStorage.getItem('token'),
|
||||
})
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function getZanoPrice() {
|
||||
return axios
|
||||
.get(
|
||||
'https://explorer.zano.org/api/price?asset_id=d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a',
|
||||
)
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export async function getMatrixAddresses(addresses: string[]) {
|
||||
try {
|
||||
const { data } = await axios.post('https://messenger.zano.org/api/get-addresses', {
|
||||
|
|
|
|||
|
|
@ -144,6 +144,21 @@ export function formatTime(ts: string | number) {
|
|||
return date.toLocaleTimeString('ru-RU', { hour12: false });
|
||||
}
|
||||
|
||||
export function formatTimestamp(ms: number | string) {
|
||||
if (Number.isNaN(Number(ms))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const date = new Date(Number(ms));
|
||||
const YYYY = date.getFullYear();
|
||||
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const DD = String(date.getDate()).padStart(2, '0');
|
||||
const HH = String(date.getHours()).padStart(2, '0');
|
||||
const mm = String(date.getMinutes()).padStart(2, '0');
|
||||
const ss = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
|
||||
}
|
||||
|
||||
export function classes(...classes: (string | boolean | undefined)[]): string {
|
||||
// boolean for constructions like [predicate] && [className]
|
||||
return classes.filter((className) => className).join(' ');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue