diff --git a/src/components/UI/ActionBtn/index.tsx b/src/components/UI/ActionBtn/index.tsx new file mode 100644 index 0000000..54052ea --- /dev/null +++ b/src/components/UI/ActionBtn/index.tsx @@ -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 ( + + ); +}; + +export default ActionBtn; diff --git a/src/components/UI/ActionBtn/styles.module.scss b/src/components/UI/ActionBtn/styles.module.scss new file mode 100644 index 0000000..416c224 --- /dev/null +++ b/src/components/UI/ActionBtn/styles.module.scss @@ -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; + } +} diff --git a/src/components/UI/ActionBtn/types.ts b/src/components/UI/ActionBtn/types.ts new file mode 100644 index 0000000..2d807fd --- /dev/null +++ b/src/components/UI/ActionBtn/types.ts @@ -0,0 +1,5 @@ +import { ButtonHTMLAttributes } from 'react'; + +export interface ActionBtnProps extends ButtonHTMLAttributes { + variant?: 'primary' | 'success' | 'danger'; +} diff --git a/src/components/default/GenericTable/index.tsx b/src/components/default/GenericTable/index.tsx new file mode 100644 index 0000000..da606e8 --- /dev/null +++ b/src/components/default/GenericTable/index.tsx @@ -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(props: GenericTableProps) { + const { + className, + tableClassName, + theadClassName, + tbodyClassName, + columns, + data, + getRowKey, + emptyMessage = 'No data', + } = props; + + return ( +
+ {data.length > 0 ? ( +
+ + + {columns.map((col) => ( + + ))} + + + + + {columns.map((col) => ( + + ))} + + + + + {data.map((row, i) => ( + + {columns.map((col) => ( + + ))} + + ))} + +
+ {col.header} +
+ {col.cell(row, i)} +
+
+ ) : ( + + )} +
+ ); +} diff --git a/src/components/default/GenericTable/types.ts b/src/components/default/GenericTable/types.ts new file mode 100644 index 0000000..238ff86 --- /dev/null +++ b/src/components/default/GenericTable/types.ts @@ -0,0 +1,21 @@ +export type Align = 'left' | 'center' | 'right'; + +export type ColumnDef = { + key: string; + header: React.ReactNode; + width?: string; + align?: Align; + className?: string; + cell: (_row: T, _rowIndex: number) => React.ReactNode; +}; + +export type GenericTableProps = { + className?: string; + tableClassName?: string; + theadClassName?: string; + tbodyClassName?: string; + columns: ColumnDef[]; + data: T[]; + getRowKey: (_row: T, _rowIndex: number) => React.Key; + emptyMessage?: string; +}; diff --git a/src/components/dex/MatrixConnectionBadge/index.tsx b/src/components/dex/MatrixConnectionBadge/index.tsx index 4839242..5d47033 100644 --- a/src/components/dex/MatrixConnectionBadge/index.tsx +++ b/src/components/dex/MatrixConnectionBadge/index.tsx @@ -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(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 ( - ) : ( - <> ); } diff --git a/src/components/dex/MatrixConnectionBadge/styles.module.scss b/src/components/dex/MatrixConnectionBadge/styles.module.scss index 9d64826..323ddc6 100644 --- a/src/components/dex/MatrixConnectionBadge/styles.module.scss +++ b/src/components/dex/MatrixConnectionBadge/styles.module.scss @@ -7,6 +7,7 @@ } &__tooltip { + padding: 10px; position: absolute; top: 30px; left: 50%; diff --git a/src/components/dex/UserOrders/columns/index.tsx b/src/components/dex/UserOrders/columns/index.tsx new file mode 100644 index 0000000..092a917 --- /dev/null +++ b/src/components/dex/UserOrders/columns/index.tsx @@ -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[] { + return [ + { + key: 'pair', + header: 'Pair', + width: '120px', + cell: (row) => ( +

+ {firstCurrencyName}/{secondCurrencyName} +

+ ), + }, + { + key: 'direction', + header: 'Direction', + width: '110px', + cell: (row) => ( +

+ {row.type} +

+ ), + }, + { + key: 'price', + header: <>Price ({secondCurrencyName}), + width: '150px', + cell: (row) =>

{notationToString(row.price)}

, + }, + { + key: 'quantity', + header: <>Quantity ({firstCurrencyName}), + width: '160px', + cell: (row) =>

{notationToString(row.amount)}

, + }, + { + key: 'total', + header: <>Total ({secondCurrencyName}), + width: '180px', + cell: (row) => ( + + ), + }, + { + key: 'offers', + header: 'Offers', + width: '90px', + cell: (row) => ( +

+ {offersCountByOrderId.get(String(row.id)) ?? 0} +

+ ), + }, + { + key: 'orderid', + header: 'Order ID', + width: '140px', + cell: (row) =>

{row.pair_id}

, + }, + { + key: 'time', + header: 'Time', + width: '180px', + cell: (row) =>

{formatTimestamp(row.timestamp)}

, + }, + { + key: 'action', + header: 'Action', + width: '80px', + align: 'left', + cell: (row) => , + }, + ]; +} + +export function buildApplyTipsColumns({ + type, + firstCurrencyName, + secondCurrencyName, + matrixAddresses, + secondAssetUsdPrice, + userOrders, + pairData, + onAfter, +}: BuildApplyTipsColumnsArgs): ColumnDef[] { + return [ + { + key: 'pair', + header: 'Pair', + width: '120px', + cell: (row) => ( +

+ {firstCurrencyName}/{secondCurrencyName} +

+ ), + }, + { + key: 'direction', + header: 'Direction', + width: '110px', + cell: (row) => ( +

+ {row.type} +

+ ), + }, + { + key: 'alias', + header: 'Alias', + width: '180px', + cell: (row) => ( + + ), + }, + { + key: 'price', + header: <>Price ({secondCurrencyName}), + width: '150px', + cell: (row) =>

{notationToString(row.price)}

, + }, + { + key: 'quantity', + header: <>Quantity ({firstCurrencyName}), + width: '160px', + cell: (row) =>

{notationToString(row.left)}

, + }, + { + key: 'total', + header: <>Total ({secondCurrencyName}), + width: '180px', + cell: (row) => ( + + ), + }, + { + key: 'orderid', + header: 'Order ID', + width: '140px', + cell: (row) =>

{row.connected_order_id}

, + }, + { + key: 'time', + header: 'Time', + width: '180px', + cell: (row) =>

{formatTimestamp(Number(row.timestamp))}

, + }, + { + key: 'action', + header: 'Action', + width: type === 'offers' ? '150px' : '90px', + cell: (row) => ( +
+ o.id === row.connected_order_id)} + onAfter={onAfter} + /> + {type === 'offers' && ( + + )} +
+ ), + }, + ]; +} + +export function buildMyRequestsColumns({ + firstCurrencyName, + secondCurrencyName, + onAfter, +}: BuildMyRequestsColumnsArgs): ColumnDef[] { + return [ + { + key: 'pair', + header: 'Pair', + width: '120px', + cell: (row) => ( +

+ {firstCurrencyName}/{secondCurrencyName} +

+ ), + }, + { + key: 'direction', + header: 'Direction', + width: '110px', + cell: (row) => ( +

+ {row.creator} +

+ ), + }, + { + key: 'quantity', + header: <>Quantity ({firstCurrencyName}), + width: '160px', + cell: (row) =>

{notationToString(row.amount)}

, + }, + { + key: 'sell_order_id', + header: 'Sell Order ID', + width: '140px', + cell: (row) =>

{row.sell_order_id}

, + }, + { + key: 'buy_order_id', + header: 'Buy Order ID', + width: '140px', + cell: (row) =>

{row.buy_order_id}

, + }, + { + key: 'status', + header: 'Status', + width: '140px', + cell: (row) =>

{row.status}

, + }, + { + key: 'time', + header: 'Time', + width: '180px', + cell: (row) =>

{formatTimestamp(row.timestamp)}

, + }, + { + key: 'action', + header: 'Action', + width: '80px', + align: 'left', + cell: (row) => ( + + ), + }, + ]; +} + +export function buildOrderHistoryColumns({ + firstCurrencyName, + secondCurrencyName, + secondAssetUsdPrice, +}: BuildOrderHistoryColumnsArgs): ColumnDef[] { + return [ + { + key: 'pair', + header: 'Pair', + width: '120px', + cell: (row) => ( +

+ {firstCurrencyName}/{secondCurrencyName} +

+ ), + }, + { + key: 'direction', + header: 'Direction', + width: '110px', + cell: (row) => ( +

+ {row.type} +

+ ), + }, + { + key: 'price', + header: <>Price ({secondCurrencyName}), + width: '150px', + cell: (row) =>

{notationToString(row.price)}

, + }, + { + key: 'quantity', + header: <>Quantity ({firstCurrencyName}), + width: '160px', + cell: (row) =>

{notationToString(row.amount)}

, + }, + { + key: 'total', + header: <>Total ({secondCurrencyName}), + width: '180px', + cell: (row) => ( + + ), + }, + { + key: 'orderid', + header: 'Order ID', + width: '140px', + cell: (row) =>

{row.pair_id}

, + }, + { + key: 'time', + header: 'Time', + width: '180px', + cell: (row) =>

{formatTimestamp(row.timestamp)}

, + }, + ]; +} diff --git a/src/components/dex/UserOrders/columns/types.ts b/src/components/dex/UserOrders/columns/types.ts new file mode 100644 index 0000000..63a6624 --- /dev/null +++ b/src/components/dex/UserOrders/columns/types.ts @@ -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; + onAfter: () => Promise; +} + +export interface BuildApplyTipsColumnsArgs { + type: 'suitables' | 'offers'; + firstCurrencyName: string; + secondCurrencyName: string; + matrixAddresses: MatrixAddress[]; + secondAssetUsdPrice?: number; + userOrders: OrderRow[]; + pairData: PairData | null; + onAfter: () => Promise; +} + +export interface BuildMyRequestsColumnsArgs { + firstCurrencyName: string; + secondCurrencyName: string; + onAfter: () => Promise; +} + +export interface BuildOrderHistoryColumnsArgs { + firstCurrencyName: string; + secondCurrencyName: string; + secondAssetUsdPrice?: number; +} diff --git a/src/components/dex/UserOrders/components/AliasCell/index.tsx b/src/components/dex/UserOrders/components/AliasCell/index.tsx new file mode 100644 index 0000000..169e95b --- /dev/null +++ b/src/components/dex/UserOrders/components/AliasCell/index.tsx @@ -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 ( +

+ setShow(true)} + onMouseLeave={() => setShow(false)} + className={styles.alias__text} + > + @{display} + + + + {isInstant && ( +

+ +
+ )} + + {alias && alias.length > max && ( + +

{alias}

+
+ )} +

+ ); +} diff --git a/src/components/dex/UserOrders/components/AliasCell/types.ts b/src/components/dex/UserOrders/components/AliasCell/types.ts new file mode 100644 index 0000000..c661e7b --- /dev/null +++ b/src/components/dex/UserOrders/components/AliasCell/types.ts @@ -0,0 +1,9 @@ +import MatrixAddress from '@/interfaces/common/MatrixAddress'; + +export interface AliasCellProps { + alias?: string; + address?: string; + matrixAddresses: MatrixAddress[]; + isInstant?: boolean; + max?: number; +} diff --git a/src/components/dex/UserOrders/components/CancelActionCell/index.tsx b/src/components/dex/UserOrders/components/CancelActionCell/index.tsx new file mode 100644 index 0000000..13373d3 --- /dev/null +++ b/src/components/dex/UserOrders/components/CancelActionCell/index.tsx @@ -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 ( + onClick()} + > + {type === 'cancel' ? 'Cancel' : 'Reject'} + + ); +} diff --git a/src/components/dex/UserOrders/components/CancelActionCell/types.ts b/src/components/dex/UserOrders/components/CancelActionCell/types.ts new file mode 100644 index 0000000..bad984a --- /dev/null +++ b/src/components/dex/UserOrders/components/CancelActionCell/types.ts @@ -0,0 +1,5 @@ +export interface CancelActionCellProps { + type?: 'cancel' | 'reject'; + id: string; + onAfter: () => Promise; +} diff --git a/src/components/dex/UserOrders/components/MyOrdersApplyRow/index.tsx b/src/components/dex/UserOrders/components/MyOrdersApplyRow/index.tsx deleted file mode 100644 index d1b447d..0000000 --- a/src/components/dex/UserOrders/components/MyOrdersApplyRow/index.tsx +++ /dev/null @@ -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) { - 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 ( - - -

- setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - className={styles.alias__text} - > - @{cutAddress(e.user.alias, 12) || 'no alias'} - - - - {e.isInstant && ( -

- -
- )} -

- - {(e.isInstant || e.transaction) && } - - {e.user?.alias.length > 12 && ( - -

{e.user?.alias}

-
- )} - - - - {notationToString(e.price)} - - {notationToString(e.left)} - - - {notationToString(totalDecimal.toString())}{' '} - ~ ${totalValue && formatDollarValue(totalValue)} - - - - - - {applyingState ? 'Process' : 'Apply'} - - - - ); -} - -export default MyOrdersApplyRow; diff --git a/src/components/dex/UserOrders/components/MyOrdersApplyRow/types.ts b/src/components/dex/UserOrders/components/MyOrdersApplyRow/types.ts deleted file mode 100644 index bec6f27..0000000 --- a/src/components/dex/UserOrders/components/MyOrdersApplyRow/types.ts +++ /dev/null @@ -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; - updateUserOrders: () => Promise; - fetchUser: () => Promise; - fetchTrades: () => Promise; - matrixAddresses: MatrixAddress[]; - pairData: PairData | null; - userOrders: OrderRow[]; -} diff --git a/src/components/dex/UserOrders/components/MyOrdersRow/index.tsx b/src/components/dex/UserOrders/components/MyOrdersRow/index.tsx deleted file mode 100644 index f697441..0000000 --- a/src/components/dex/UserOrders/components/MyOrdersRow/index.tsx +++ /dev/null @@ -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) { - 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 ( - - -

- setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - className={styles.alias__text} - > - @ - {cutAddress( - state.wallet?.connected && state.wallet?.alias - ? state.wallet.alias - : 'no alias', - 12, - )} - - - - {e.isInstant && ( -

- -
- )} -

- - {(state.wallet?.connected && state.wallet?.alias ? state.wallet?.alias : '') - ?.length > 12 && ( - -

- {state.wallet?.connected && state.wallet?.alias} -

-
- )} - - - - {notationToString(e.price)} - - - {notationToString(e.amount)} - - - {notationToString(totalDecimal.toString())}{' '} - ~ ${totalValue && formatDollarValue(totalValue)} - - - -

- {applyTips?.filter((tip) => tip.connected_order_id === e.id)?.length || 0} -

- - - - {cancellingState ? 'Process' : 'Cancel'} - - - - ); -} - -export default MyOrdersRow; diff --git a/src/components/dex/UserOrders/components/MyOrdersRow/types.ts b/src/components/dex/UserOrders/components/MyOrdersRow/types.ts deleted file mode 100644 index 9bd8026..0000000 --- a/src/components/dex/UserOrders/components/MyOrdersRow/types.ts +++ /dev/null @@ -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; - updateUserOrders: () => Promise; - fetchUser: () => Promise; - matrixAddresses: MatrixAddress[]; - applyTips: ApplyTip[]; -} diff --git a/src/components/dex/UserOrders/components/RequestActionCell/index.tsx b/src/components/dex/UserOrders/components/RequestActionCell/index.tsx new file mode 100644 index 0000000..8865b76 --- /dev/null +++ b/src/components/dex/UserOrders/components/RequestActionCell/index.tsx @@ -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 ( + onClick()}> + {type === 'request' ? 'Request' : 'Accept'} + + ); +} diff --git a/src/components/dex/UserOrders/components/RequestActionCell/types.ts b/src/components/dex/UserOrders/components/RequestActionCell/types.ts new file mode 100644 index 0000000..76d9f46 --- /dev/null +++ b/src/components/dex/UserOrders/components/RequestActionCell/types.ts @@ -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; + connectedOrder?: OrderRow; + userOrders?: OrderRow[]; +} diff --git a/src/components/dex/UserOrders/components/TotalUsdCell/index.tsx b/src/components/dex/UserOrders/components/TotalUsdCell/index.tsx new file mode 100644 index 0000000..2f494a7 --- /dev/null +++ b/src/components/dex/UserOrders/components/TotalUsdCell/index.tsx @@ -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 ( +

+ {notationToString(total.toString())} ~ ${usd && formatDollarValue(usd)} +

+ ); +} diff --git a/src/components/dex/UserOrders/components/TotalUsdCell/types.ts b/src/components/dex/UserOrders/components/TotalUsdCell/types.ts new file mode 100644 index 0000000..b09e12c --- /dev/null +++ b/src/components/dex/UserOrders/components/TotalUsdCell/types.ts @@ -0,0 +1,5 @@ +export interface TotalUsdCellProps { + amount: string | number; + price: string | number; + secondAssetUsdPrice?: number; +} diff --git a/src/components/dex/UserOrders/index.tsx b/src/components/dex/UserOrders/index.tsx index e580296..008d241 100644 --- a/src/components/dex/UserOrders/index.tsx +++ b/src/components/dex/UserOrders/index.tsx @@ -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([]); + const [ordersHistory, setOrdersHistory] = useState([]); + 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(); + 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 ( + r.id} + emptyMessage="No orders" + /> + ); + case 'suitable': + return ( + r.id} + emptyMessage="No suitables" + /> + ); + case 'requests': + return ( + r.id} + emptyMessage="No requests" + /> + ); + case 'offers': + return ( + r.id} + emptyMessage="No offers" + /> + ); + case 'history': + return ( + 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 ( -
-
-
- - - -
- -
- -
-
- -
- - - - - - - - - - - -
AliasPrice ({secondCurrencyName})Amount ({firstCurrencyName})Total ({secondCurrencyName})Offers
- - {!myOrdersLoading && loggedIn && !!userOrders.length && ( -
- - - {userOrders.map((e) => ( - - ))} - -
- - {!!applyTips.length && ( - - - {applyTips.map((e) => ( - - ))} - -
- )} + <> +
+
+
+ {tabsData.map((tab) => ( + + ))}
- )} + + {ordersType === 'opened' && userOrders.length > 0 && ( + + Cancel all + + )} +
+ + {!myOrdersLoading && loggedIn && renderTable()} {myOrdersLoading && loggedIn && } - {!loggedIn && } - - {loggedIn && !userOrders.length && !myOrdersLoading && ( - - )}
-
+ + {alertState && ( + setAlertState(null)} + /> + )} + ); }; diff --git a/src/components/dex/UserOrders/styles.module.scss b/src/components/dex/UserOrders/styles.module.scss index cb2a2c5..a5f9d1e 100644 --- a/src/components/dex/UserOrders/styles.module.scss +++ b/src/components/dex/UserOrders/styles.module.scss @@ -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 { diff --git a/src/components/dex/UserOrders/types.ts b/src/components/dex/UserOrders/types.ts index b67eb69..92aaf97 100644 --- a/src/components/dex/UserOrders/types.ts +++ b/src/components/dex/UserOrders/types.ts @@ -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; userOrders: OrderRow[]; applyTips: ApplyTip[]; myOrdersLoading: boolean; - loggedIn: boolean; - ordersType: 'opened' | 'history'; - setOrdersType: Dispatch>; + ordersType: OrderType; + setOrdersType: Dispatch>; handleCancelAllOrders: () => void; secondAssetUsdPrice: number | undefined; updateOrders: () => Promise; diff --git a/src/hook/useTradeInit.ts b/src/hook/useTradeInit.ts index f3e2732..08c7a99 100644 --- a/src/hook/useTradeInit.ts +++ b/src/hook/useTradeInit.ts @@ -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, diff --git a/src/interfaces/common/UserPendingType.ts b/src/interfaces/common/UserPendingType.ts new file mode 100644 index 0000000..bc51be8 --- /dev/null +++ b/src/interfaces/common/UserPendingType.ts @@ -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; diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index fa1f7c3..829b827 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -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('opened'); const [pairStats, setPairStats] = useState(null); const [applyTips, setApplyTips] = useState([]); 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} /> +
+
+ {['buy', 'sell'].map((type) => { + const isBuy = type === 'buy'; + const form = isBuy ? buyForm : sellForm; -
- {['buy', 'sell'].map((type) => { - const isBuy = type === 'buy'; - const form = isBuy ? buyForm : sellForm; - - return ( - - ); - })} -
+ return ( + + ); + })}
{alertState && ( diff --git a/src/styles/Trading.module.scss b/src/styles/Trading.module.scss index aeb178e..f0a30d4 100644 --- a/src/styles/Trading.module.scss +++ b/src/styles/Trading.module.scss @@ -48,7 +48,6 @@ display: flex; gap: 20px; margin-bottom: 40px; - height: 405px; &_createOrders { display: flex; diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 601f3f7..97e3749 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -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 { diff --git a/src/utils/methods.ts b/src/utils/methods.ts index 3ea4880..476bec0 100644 --- a/src/utils/methods.ts +++ b/src/utils/methods.ts @@ -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', { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 31c32e6..0daf700 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -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(' ');