From e9b6c353b2af52459b7eee54bea0d6c4102e5aaf Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Sat, 2 Aug 2025 15:27:26 +0500 Subject: [PATCH 01/27] finish: integrate dex redesign --- public/ui/premium.svg | 10 + .../HorizontalSelect.module.scss | 2 +- .../UI/RangeInput/RangeInput.module.scss | 7 + src/components/UI/RangeInput/RangeInput.tsx | 2 +- .../default/Header/Header.module.scss | 13 +- src/components/default/Header/Header.tsx | 6 +- .../InputPanelItem/InputPanelItemProps.ts | 3 +- .../responses/trades/GetTradeRes.ts | 15 + .../dex/trading/CandleChart/CandleChart.tsx | 6 +- .../InputPanelItem/InputPanelItem.module.scss | 61 +- .../trading/InputPanelItem/InputPanelItem.tsx | 58 +- src/pages/dex/trading/[id].tsx | 1003 +++++++++---- src/styles/Trading.module.scss | 1311 ++++++++--------- src/styles/themes/light.scss | 8 + src/utils/methods.ts | 8 + src/utils/utils.ts | 11 + 16 files changed, 1429 insertions(+), 1095 deletions(-) create mode 100644 public/ui/premium.svg create mode 100644 src/interfaces/responses/trades/GetTradeRes.ts diff --git a/public/ui/premium.svg b/public/ui/premium.svg new file mode 100644 index 0000000..4a0d159 --- /dev/null +++ b/public/ui/premium.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss b/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss index 967b65c..954b222 100644 --- a/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss +++ b/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss @@ -5,7 +5,7 @@ padding-bottom: 3px; &.tab { - gap: 10px; + gap: 3px; > div { a { diff --git a/src/components/UI/RangeInput/RangeInput.module.scss b/src/components/UI/RangeInput/RangeInput.module.scss index 191d792..75bd42c 100644 --- a/src/components/UI/RangeInput/RangeInput.module.scss +++ b/src/components/UI/RangeInput/RangeInput.module.scss @@ -18,6 +18,13 @@ top: 30px; transition: none; transform: translateX(-50%); + box-shadow: 0px 4px 14px 0px #07072b59; + padding: 9px 12px; + + p { + font-size: 14px; + font-weight: 500; + } } .range__slider { diff --git a/src/components/UI/RangeInput/RangeInput.tsx b/src/components/UI/RangeInput/RangeInput.tsx index cc8f8a8..d1df0f7 100644 --- a/src/components/UI/RangeInput/RangeInput.tsx +++ b/src/components/UI/RangeInput/RangeInput.tsx @@ -38,7 +38,7 @@ function RangeInput(props: RangeInputProps) { className={styles.input__range__tooltip} shown={tooltipShown} > - {realValue}% +

{realValue}%

{menuOpened &&
} -
+
Zano P2P diff --git a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts index 1173cbd..89e795b 100644 --- a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts +++ b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts @@ -8,7 +8,7 @@ interface InputPanelItemProps { totalState: string; buySellValues: SelectValue[]; buySellState: SelectValue; - setBuySellState: Dispatch>; + // setBuySellState: Dispatch>; setPriceFunction: (_value: string) => void; setAmountFunction: (_value: string) => void; setAlertState: Dispatch>; @@ -23,7 +23,6 @@ interface InputPanelItemProps { totalValid: boolean; totalUsd: string | undefined; scrollToOrderList: () => void; - updateUserOrders: () => void; } export default InputPanelItemProps; diff --git a/src/interfaces/responses/trades/GetTradeRes.ts b/src/interfaces/responses/trades/GetTradeRes.ts new file mode 100644 index 0000000..c3d898f --- /dev/null +++ b/src/interfaces/responses/trades/GetTradeRes.ts @@ -0,0 +1,15 @@ +export interface Trade { + id: number; + timestamp: number; + amount: string; + price: string; + type: string; + buyer: { + address: string; + amount: string; + }; + seller: { + address: string; + amount: string; + }; +} diff --git a/src/pages/dex/trading/CandleChart/CandleChart.tsx b/src/pages/dex/trading/CandleChart/CandleChart.tsx index fe6684e..c8dccc0 100644 --- a/src/pages/dex/trading/CandleChart/CandleChart.tsx +++ b/src/pages/dex/trading/CandleChart/CandleChart.tsx @@ -128,8 +128,8 @@ function CandleChart(props: CandleChartProps) { return { grid: { top: '5%', - left: '10%', - right: '5%', + left: '-1%', + right: '6%', bottom: '10%', }, @@ -218,7 +218,7 @@ function CandleChart(props: CandleChartProps) { div:first-child { + &_header { display: flex; justify-content: space-between; - padding-bottom: 20px; + align-items: center; + padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 20px; + margin-bottom: 10px; + + h5 { + font-size: 16px; + font-weight: 600; + } + + .input_panel__fees { + display: flex; + justify-content: space-between; + font-weight: 400; + font-size: 12px; + + p { + color: var(--table-th-color); + font-weight: 400; + font-size: 12px; + } + + span { + font-weight: 400; + font-size: 12px; + } + } } - > div:last-child { + &_body { display: flex; flex-direction: column; - gap: 20px; + gap: 10px; + + button { + margin-top: 10px; + } > .buy_btn { background-color: #16d1d6; @@ -72,7 +100,9 @@ gap: 8px; h6 { - color: var(--font-dimmed-color); + font-size: 11px; + font-family: 700; + color: var(--table-th-color); } > div { @@ -86,18 +116,22 @@ input { width: 100%; - padding: 16px 15px; + padding: 13px 15px; background-color: transparent; border: none; + font-size: 16px; + font-weight: 400; } .labeled_input__value { - padding-right: 15px; + padding-right: 10px; display: flex; align-items: center; > p { - color: var(--font-dimmed-color); + color: var(--table-th-color); + font-size: 12px; + font-weight: 400; } } @@ -140,15 +174,6 @@ } } } - - .input_panel__fees { - display: flex; - justify-content: space-between; - - p { - color: var(--font-dimmed-color); - } - } } } diff --git a/src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx b/src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx index 2d721e3..44d2eb2 100644 --- a/src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx +++ b/src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx @@ -11,37 +11,11 @@ import InputPanelItemProps from '@/interfaces/props/pages/dex/trading/InputPanel import LabeledInputProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps'; import CreateOrderData from '@/interfaces/fetch-data/create-order/CreateOrderData'; import Decimal from 'decimal.js'; -import HorizontalSelectProps from '@/interfaces/props/components/UI/HorizontalSelect/HorizontalSelectProps'; -import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; -import { nanoid } from 'nanoid'; import Alert from '@/components/UI/Alert/Alert'; import infoIcon from '@/assets/images/UI/info_alert_icon.svg'; import Image from 'next/image'; import styles from './InputPanelItem.module.scss'; -function DexBuySellSwitch({ body, value, setValue }: HorizontalSelectProps) { - return ( -
- {body.map((e) => { - let itemClass = styles['buy-sell-switch__item']; - - if (value.code === e.code) { - itemClass += - e.code === 'buy' - ? ` ${styles['item_selected-buy']}` - : ` ${styles['item_selected-sell']}`; - } - - return ( - - ); - })} -
- ); -} - function InputPanelItem(props: InputPanelItemProps) { const { state } = useContext(Store); @@ -53,7 +27,6 @@ function InputPanelItem(props: InputPanelItemProps) { totalState = '', buySellValues, buySellState = buySellValues[0], - setBuySellState, setPriceFunction, setAmountFunction, setAlertState, @@ -68,7 +41,6 @@ function InputPanelItem(props: InputPanelItemProps) { totalValid, totalUsd, scrollToOrderList, - updateUserOrders, } = props; const [creatingState, setCreatingState] = useState(false); @@ -163,8 +135,6 @@ function InputPanelItem(props: InputPanelItemProps) { setAlertSubtitle(''); }, 3000); } - - updateUserOrders(); } function onRangeInput(e: ChangeEvent) { @@ -181,10 +151,8 @@ function InputPanelItem(props: InputPanelItemProps) { if (creatingState) { buttonText = 'Creating...'; - } else if (isBuy) { - buttonText = 'Buy'; } else { - buttonText = 'Sell'; + buttonText = 'Create Order'; } return ( @@ -214,16 +182,19 @@ function InputPanelItem(props: InputPanelItemProps) { /> )} -
-
New order
- +
+
+ {isBuy ? 'Buy' : 'Sell'} {secondCurrencyName} +
+ +
+

+ Fee: 0.01 Zano +

+
-
+
{LabeledInput({ value: priceState, setValue: setPriceFunction, @@ -262,11 +233,6 @@ function InputPanelItem(props: InputPanelItemProps) { ) : ( )} -
-

- Fee: 0.01 Zano -

-
); diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 3f23c95..5cfb914 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -1,13 +1,11 @@ import styles from '@/styles/Trading.module.scss'; import Footer from '@/components/default/Footer/Footer'; import Header from '@/components/default/Header/Header'; -import PageTitle from '@/components/default/PageTitle/PageTitle'; import { ReactComponent as ClockIcon } from '@/assets/images/UI/clock_icon.svg'; import { ReactComponent as UpIcon } from '@/assets/images/UI/up_icon.svg'; import { ReactComponent as DownIcon } from '@/assets/images/UI/down_icon.svg'; import { ReactComponent as VolumeIcon } from '@/assets/images/UI/volume_icon.svg'; import { ReactComponent as NoOffersIcon } from '@/assets/images/UI/no_offers.svg'; -import { ReactComponent as ArrowRight } from '@/assets/images/UI/arrow-outlined-right.svg'; import Dropdown from '@/components/UI/Dropdown/Dropdown'; import HorizontalSelect from '@/components/UI/HorizontalSelect/HorizontalSelect'; import { useContext, useEffect, useRef, useState } from 'react'; @@ -22,13 +20,16 @@ import { getPairStats, getUserOrdersPage, getCandles, + getTrades, } from '@/utils/methods'; import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; import Link from 'next/link'; import { nanoid } from 'nanoid'; import { + classes, cutAddress, formatDollarValue, + formatTime, isPositiveFloatStr, notationToString, roundTo, @@ -56,16 +57,17 @@ import LightningImg from '@/assets/images/UI/lightning.png'; import RocketImg from '@/assets/images/UI/rocket.png'; import { ReactComponent as ConnectionIcon } from '@/assets/images/UI/connection.svg'; import Image from 'next/image'; +import BackButton from '@/components/default/BackButton/BackButton'; +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; import CandleChart from './CandleChart/CandleChart'; -import OrdersBuySellSwitch from './OrdersBuySellSwitch/OrdersBuySellSwitch'; import InputPanelItem from './InputPanelItem/InputPanelItem'; import { validateTokensInput } from '../../../../shared/utils'; -function BadgeStatus({ type = 'instant' }: { type?: 'instant' | 'high' }) { +function BadgeStatus({ type = 'instant', icon }: { type?: 'instant' | 'high'; icon?: boolean }) { return ( -
+
badge image - {type === 'instant' ? 'instant' : 'high volume'} + {!icon && {type === 'instant' ? 'instant' : 'high volume'}}
); } @@ -101,6 +103,10 @@ function Trading() { ]; const buySellValues: SelectValue[] = [ + { + name: 'All', + code: 'all', + }, { name: 'Buy', code: 'buy', @@ -125,10 +131,17 @@ function Trading() { const [ordersLoading, setOrdersLoading] = useState(true); + const [trades, setTrades] = useState([]); + const [tradesLoading, setTradesLoading] = useState(true); + 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 [pairStats, setPairStats] = useState(null); const [applyTips, setApplyTips] = useState([]); @@ -137,6 +150,54 @@ function Trading() { const [alertSubtitle, setAlertSubtitle] = useState(''); + const [ordersInfoTooltip, setOrdersInfoTooltip] = useState(null); + const ordersInfoRef = useRef(null); + const [infoTooltipPos, setInfoTooltipPos] = useState({ x: 0, y: 0 }); + + async function fetchTrades() { + setTradesLoading(true); + const result = await getTrades(pairId); + + if (result.success) { + setTrades(result.data); + } + + setTradesLoading(false); + } + + useEffect(() => { + (async () => { + await fetchTrades(); + })(); + }, [pairId]); + + const filteredTrades = + tradesType === 'my' + ? trades.filter( + (trade) => + trade.buyer.address === state.wallet?.address || + trade.seller.address === state.wallet?.address, + ) + : trades; + + useEffect(() => { + const targetEl = (event: MouseEvent) => { + if (ordersInfoRef.current && !ordersInfoRef.current.contains(event.target as Node)) { + setOrdersInfoTooltip(null); + } + }; + + window.addEventListener('mousemove', targetEl); + + return () => { + window.removeEventListener('mousemove', targetEl); + }; + }, []); + + const moveInfoTooltip = (event: React.MouseEvent) => { + setInfoTooltipPos({ x: event.clientX, y: event.clientY }); + }; + const [matrixAddresses, setMatrixAddresses] = useState([]); async function updateOrders() { @@ -211,7 +272,7 @@ function Trading() { ); const filteredOrdersHistory = ordersHistory - ?.filter((e) => e.type === ordersBuySell.code) + ?.filter((e) => (ordersBuySell.code === 'all' ? e : e.type === ordersBuySell.code)) ?.filter((e) => e.user.address !== state.wallet?.address) ?.sort((a, b) => { if (ordersBuySell.code === 'buy') { @@ -328,15 +389,24 @@ function Trading() { const [amountState, setAmountState] = useState(''); const [totalState, setTotalState] = useState(''); + const [priceSellState, setPriceSellState] = useState(''); + const [amountSellState, setAmountSellState] = useState(''); + const [totalSellState, setTotalSellState] = useState(''); + const [totalUsd, setTotalUsd] = useState(undefined); + const [totalSellUsd, setTotalSellUsd] = useState(undefined); const [priceValid, setPriceValid] = useState(false); const [amountValid, setAmountValid] = useState(false); const [totalValid, setTotalValid] = useState(false); + const [priceSellValid, setPriceSellValid] = useState(false); + const [amountSellValid, setAmountSellValid] = useState(false); + const [totalSellValid, setTotalSellValid] = useState(false); const [buySellState, setBuySellState] = useState(buySellValues[0]); const [rangeInputValue, setRangeInputValue] = useState('50'); + const [rangeInputSellValue, setRangeInputSellValue] = useState('50'); useEffect(() => { let totalDecimal: Decimal | undefined; @@ -352,6 +422,22 @@ function Trading() { setTotalUsd(zanoPrice && totalDecimal ? totalDecimal.mul(zanoPrice).toFixed(2) : undefined); }, [totalState, state.assetsRates, pairData?.second_currency?.asset_id]); + useEffect(() => { + let totalSellDecimal: Decimal | undefined; + + try { + totalSellDecimal = new Decimal(totalSellState); + } catch (error) { + console.log(error); + } + + const zanoPrice = state.assetsRates.get(pairData?.second_currency?.asset_id || ''); + + setTotalSellUsd( + zanoPrice && totalSellDecimal ? totalSellDecimal.mul(zanoPrice).toFixed(2) : undefined, + ); + }, [totalSellState, state.assetsRates, pairData?.second_currency?.asset_id]); + function setPriceFunction(inputValue: string) { if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { return; @@ -405,6 +491,60 @@ function Trading() { } } + function setPriceSellFunction(inputValue: string) { + if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { + return; + } + + try { + const value = new Decimal(inputValue || NaN); + + if (value.toString().replace('.', '').length > 18) { + console.log('TOO MANY DECIMALS'); + return; + } + } catch (error) { + console.log(error); + } + + setPriceSellState(inputValue); + + if (!inputValue) { + setTotalSellState(''); + setTotalSellValid(false); + setPriceSellValid(false); + return; + } + + const valueDecimal = new Decimal(inputValue || NaN); + const amountDecimal = new Decimal(amountSellState || NaN); + + const secondCurrencyDP = pairData?.second_currency.asset_info?.decimal_point || 12; + + const validationResult = validateTokensInput(inputValue, secondCurrencyDP); + + if (!validationResult.valid) { + setTotalSellState(''); + setTotalSellValid(false); + setPriceSellValid(false); + return; + } + + setPriceSellValid(true); + + if (!valueDecimal.isNaN() && !amountDecimal.isNaN() && amountSellState !== '') { + const total = valueDecimal.mul(amountDecimal).toFixed(); + setTotalSellState(total); + + const totalValidationResult = validateTokensInput(total, secondCurrencyDP); + + setTotalSellValid(totalValidationResult.valid); + } else { + setTotalSellState(''); + setTotalSellValid(false); + } + } + const assets = state.wallet?.connected ? state.wallet?.assets || [] : []; const balance = assets.find((e) => e.ticker === firstCurrencyName)?.balance; @@ -467,6 +607,67 @@ function Trading() { } } + function setAmountSellFunction(inputValue: string) { + if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { + return; + } + + try { + const value = new Decimal(inputValue || NaN); + + if (value.toString().replace('.', '').length > 18) { + console.log('TOO MANY DECIMALS'); + return; + } + } catch (error) { + console.log(error); + } + + setAmountSellState(inputValue); + + if (!inputValue) { + setTotalSellState(''); + setTotalSellValid(false); + setAmountSellValid(false); + return; + } + + const value = new Decimal(inputValue || NaN); + const price = new Decimal(priceSellState || NaN); + + const validationResult = validateTokensInput( + inputValue, + pairData?.first_currency.asset_info?.decimal_point || 12, + ); + console.log(validationResult); + + if (!validationResult.valid) { + setTotalSellState(''); + setTotalSellValid(false); + setAmountSellValid(false); + return; + } + + setAmountSellValid(true); + + if (balance) setRangeInputSellValue(value.div(balance).mul(100).toFixed()); + + if (!price.isNaN() && !value.isNaN() && priceSellState !== '') { + const total = value.mul(price).toFixed(); + setTotalSellState(total); + + const totalValidationResult = validateTokensInput( + total, + pairData?.second_currency.asset_info?.decimal_point || 12, + ); + + setTotalSellValid(totalValidationResult.valid); + } else { + setTotalSellState(''); + setTotalSellValid(false); + } + } + function setCorrespondingOrder(price: number, amount: number) { const priceDecimal = new Decimal(price || 0); const amountDecimal = new Decimal(amount || 0); @@ -493,24 +694,51 @@ function Trading() { setTotalValid(totalValidationResult.valid); } + function setCorrespondingSellOrder(price: number, amount: number) { + const priceDecimal = new Decimal(price || 0); + const amountDecimal = new Decimal(amount || 0); + const totalDecimal = priceDecimal.mul(amountDecimal); + + setPriceSellFunction(notationToString(priceDecimal.toString()) || ''); + setAmountSellFunction(notationToString(amountDecimal.toString()) || ''); + setTotalSellState(notationToString(totalDecimal.toString()) || ''); + + if (balance) { + const balanceDecimal = new Decimal(balance); + + const percentageDecimal = amountDecimal.div(balanceDecimal).mul(100); + setRangeInputSellValue(percentageDecimal.toFixed() || ''); + } + + const total = priceDecimal.mul(amountDecimal).toFixed(); + + const totalValidationResult = validateTokensInput( + total, + pairData?.second_currency.asset_info?.decimal_point || 12, + ); + + setTotalSellValid(totalValidationResult.valid); + } + function StatItem(props: StatItemProps) { const { Img } = props; return (
-
+

{props.title}

-
-

{props.value}

+
+

{props.value}

{props.coefficient !== undefined && (

= 0 ? styles.coefficient__green - : styles.coefficient__red - } + : styles.coefficient__red, + )} > {props.coefficient >= 0 ? '+' : ''} {props.coefficient?.toFixed(2)}% @@ -522,14 +750,20 @@ function Trading() { } function takeOrderClick( - event: React.MouseEvent, + event: + | React.MouseEvent + | React.MouseEvent, e: PageOrderData, ) { event.preventDefault(); - setCorrespondingOrder(e.price, e.amount); - setBuySellState( - buySellValues.find((e) => e.code !== ordersBuySell.code) || buySellValues[0], - ); + + if (e.type === 'buy') { + setCorrespondingOrder(e.price, e.amount); + setBuySellState(buySellValues[1]); + } else { + setCorrespondingSellOrder(e.price, e.amount); + setBuySellState(buySellValues[2]); + } if (!orderFormRef.current) return; @@ -542,11 +776,13 @@ function Trading() { children, sideText, sideTextColor, + noTooltip, }: { style?: React.CSSProperties; - children: string; + children: string | React.ReactNode; sideText?: string; sideTextColor?: string; + noTooltip?: boolean; }) { const [showTooltip, setShowTooltip] = useState(false); @@ -574,7 +810,7 @@ function Trading() { )}

- {isLongContent && ( + {isLongContent && !noTooltip && ( setOrdersInfoTooltip(e)} + onClick={(event) => takeOrderClick(event, e)} + className={e.type === 'sell' ? styles.sell_section : ''} style={{ '--line-width': `${percentage}%` } as React.CSSProperties} key={nanoid(16)} > - -

setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - > - @{cutAddress(e.user?.alias || 'no alias', 12)} - -

- {e.isInstant && } - {/* High volume */} - {/* */} - - {e.user?.alias.length > 12 && ( - - {e.user?.alias} - - )} - {notationToString(e.price)} {notationToString(e.amount)} - {notationToString(e.left)} + {/* {notationToString(e.left)} */} {notationToString(totalDecimal.toString())} - {/*

{localeTimeLeft(now, parseInt(e.expiration_timestamp, 10))}

*/} - - takeOrderClick(event, e)} - style={{ - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - width: '18px', - height: '18px', - }} - > - - - -
- ); } @@ -760,7 +932,7 @@ function Trading() { return ( - +

setShowTooltip(true)} @@ -777,8 +949,13 @@ function Trading() { userAdress={state?.wallet?.address} userAlias={state.wallet?.alias} /> + {e.isInstant && ( +

+ +
+ )}

- {e.isInstant && } + {(state.wallet?.connected && state.wallet?.alias ? state.wallet?.alias : '') ?.length > 12 && ( {notationToString(e.amount)} - {notationToString(e.left)} + - {notationToString(totalDecimal.toString())} + {notationToString(totalDecimal.toString())}{' '} + ~ ${totalValue && formatDollarValue(totalValue)} + {/* {localeTimeLeft(now, parseInt(e.expiration_timestamp, 10))} */} + -

+

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

@@ -955,11 +1135,12 @@ function Trading() { await updateOrders(); await updateUserOrders(); await fetchUser(); + await fetchTrades(); } return ( - +

setShowTooltip(true)} @@ -970,7 +1151,13 @@ function Trading() { userAdress={e.user.address} userAlias={e.user.alias} /> + {e.isInstant && ( +

+ +
+ )}

+ {(e.isInstant || e.transaction) && } {e.user?.alias.length > 12 && ( @@ -982,22 +1169,21 @@ function Trading() { {e.user?.alias}
)} - {/* High volume */} - {/* */} {notationToString(e.price)} {notationToString(e.left)} - + - {notationToString(totalDecimal.toString())} + {notationToString(totalDecimal.toString())}{' '} + ~ ${totalValue && formatDollarValue(totalValue)} - {/* {localeTimeLeft(now, parseInt(e.expiration_timestamp, 10))} */} + @@ -1013,25 +1199,17 @@ function Trading() { ? pairData.first_currency?.code : 'tsds'; + const imgCode2 = + pairData && tradingKnownCurrencies.includes(pairData.second_currency?.code) + ? pairData.second_currency?.code + : 'tsds'; + const coefficient = pairStats?.coefficient || 0; const coefficientOutput = parseFloat(coefficient?.toFixed(2) || '0') === -100 ? -99.99 : parseFloat(coefficient?.toFixed(2) || '0'); - const ordersIsBuy = ordersBuySell.code === 'buy'; - const shownOrdersAmount = filteredOrdersHistory.filter( - (e) => (e.type === 'buy') === ordersIsBuy, - ).length; - - const ordersSummaryFunds = filteredOrdersHistory - .reduce( - (acc, e) => acc.add(new Decimal(e.left).mul(new Decimal(e.price)).toNumber()), - new Decimal(0), - ) - .toDP(5) - .toFixed(); - const pairRateUsd = pairStats?.rate !== undefined && secondAssetUsdPrice !== undefined ? new Decimal(pairStats.rate) @@ -1045,80 +1223,105 @@ function Trading() { orderListRef.current.scrollIntoView({ behavior: 'smooth' }); }; + const handleCancelAllOrders = async () => { + setMyOrdersLoading(true); + + try { + await Promise.all(userOrders.map((order) => cancelOrder(order.id))); + await updateUserOrders(); + } catch (err) { + console.error(err); + } finally { + setMyOrdersLoading(false); + } + }; + return ( <> -
+
- -
-
-
- currency -
-
-

- {!( - pairData && - pairData.first_currency?.name && - pairData.second_currency?.name - ) ? ( - '...' - ) : ( - <> - {firstCurrencyName} - /{secondCurrencyName} - - )} +

+
+
+ currency +
+
+

+ {!( + pairData && + pairData.first_currency?.name && + pairData.second_currency?.name + ) ? ( + '...' + ) : ( + <> + {firstCurrencyName} + /{secondCurrencyName} + + )} +

+
+

+ {notationToString(pairStats?.rate || 0)}{' '} + {secondCurrencyName}

-
-

- {notationToString(pairStats?.rate || 0)}{' '} - {secondCurrencyName} + {pairRateUsd && ( +

+ ~ ${pairRateUsd}

- {pairRateUsd && ( - <> -
-

- ${pairRateUsd} -

- - )} -
+ )}
- {pairData && firstAssetLink && secondAssetLink && ( -
-

- {firstCurrencyName}:{' '} - - {shortenAddress(firstAssetId || '')} - -

-

- {secondCurrencyName}:{' '} - - {shortenAddress(secondAssetId || '')} - -

-
- )}
- +
+ {pairData && firstAssetLink && secondAssetLink && ( +
+
+

+ currency{' '} + {firstCurrencyName}: +

+ + {shortenAddress(firstAssetId || '')} + +
+
+

+ currency{' '} + {secondCurrencyName}: +

+ + {shortenAddress(firstAssetId || '')} + +
+
+ )} +
+ +
-
-
- {InputPanelItem({ - priceState, - amountState, - totalState, - buySellValues, - buySellState, - setBuySellState, - setPriceFunction, - setAmountFunction, - setAlertState, - setAlertSubtitle, - setRangeInputValue, - rangeInputValue, - firstCurrencyName, - secondCurrencyName, - balance: Number(balance), - priceValid, - amountValid, - totalValid, - totalUsd, - scrollToOrderList, - updateUserOrders, - })} -
-
-
- - undefined} - /> -
- - {candlesLoaded ? ( - - ) : ( - - )} -
-
- -
+
-
-
-
-
- {ordersBuySell.code === 'buy' ? 'Buy' : 'Sell'} Orders - {/* {firstCurrencyName && secondCurrencyName ? " - " + firstCurrencyName + "/" + secondCurrencyName : ""} */} -
-
-

- {firstCurrencyName && secondCurrencyName - ? `${firstCurrencyName}/${secondCurrencyName}` - : ''} -

-
+
+
+ Orders pool +
-
-
-

- {shownOrdersAmount}{' '} - {shownOrdersAmount === 1 ? 'order' : 'orders'} -

-
-
-

- {ordersSummaryFunds} {secondCurrencyName} -

-
-
+
+ + + + +
- {/* */} -
-
+
- - - - - - + + + + {!ordersLoading && !!filteredOrdersHistory.length && ( - + setOrdersInfoTooltip(null)} + className="orders-scroll" + > {filteredOrdersHistory?.map((e) => { const maxValue = Math.max( ...filteredOrdersHistory.map((order) => @@ -1306,17 +1440,224 @@ function Trading() { {ordersLoading && ( )} + + {ordersInfoTooltip && + (() => { + const totalDecimal = new Decimal(ordersInfoTooltip?.left).mul( + new Decimal(ordersInfoTooltip?.price), + ); + const totalValue = secondAssetUsdPrice + ? totalDecimal.mul(secondAssetUsdPrice).toFixed(2) + : undefined; + + return ( + +
+
Alias
+

+ @ + {cutAddress( + ordersInfoTooltip?.user?.alias || + 'no alias', + 12, + )}{' '} + {ordersInfoTooltip?.isInstant && ( + + )} +

+ +
Price ({secondCurrencyName})
+

+ {notationToString(ordersInfoTooltip?.price)} +

+ + ~ + {secondAssetUsdPrice && + ordersInfoTooltip?.price !== undefined + ? (() => { + const total = + secondAssetUsdPrice * + ordersInfoTooltip.price; + const formatted = + ordersInfoTooltip.price < 0.9 + ? `$${total.toFixed(5)}` + : `$${total.toFixed(2)}`; + return formatted; + })() + : 'undefined'} + + +
Amount ({firstCurrencyName})
+

{notationToString(ordersInfoTooltip?.amount)}

+ +
Total ({secondCurrencyName})
+

{notationToString(totalDecimal.toString())}

+ + ~{' '} + {totalValue + ? `$${formatDollarValue(totalValue)}` + : 'undefined'} + +
+
+ ); + })()} -
-
-
My Orders
-
-

- {applyTips?.length || 0} Offer - {(applyTips?.length || 0) === 1 ? '' : 's'} -

+
+
+ + undefined} + /> +
+ + {candlesLoaded ? ( + + ) : ( + + )} +
+ +
+
+ + + +
+ +
+
Alias - Price
({secondCurrencyName}) -
- Amount
({firstCurrencyName}) -
- Remaining
({firstCurrencyName}) -
- {' '} - Total
({secondCurrencyName}) -
Price ({secondCurrencyName})Amount ({firstCurrencyName})Total ({secondCurrencyName})
+ + + + + + + + + {!tradesLoading && !!filteredTrades.length && ( + + {filteredTrades.map((trade) => ( + + + + + + ))} + + )} +
Price ({secondCurrencyName})Amount ({firstCurrencyName})Time
+

+ {trade.price} +

+
+

{trade.amount}

+
+

{formatTime(trade.timestamp)}

+
+ + {!filteredTrades.length && !tradesLoading && ( +
+ +
No trades
+
+ )} + + {tradesLoading && ( + + )} +
+
+
+ +
+
+
+
+ + + +
+ +
+
@@ -1324,16 +1665,13 @@ function Trading() { - + - @@ -1342,6 +1680,7 @@ function Trading() {
AliasAlias Price
({secondCurrencyName})
Amount
({firstCurrencyName})
- Remaining
({firstCurrencyName}) -
Total
({secondCurrencyName})
+ {!myOrdersLoading && loggedIn && !!userOrders.length && (
@@ -1391,6 +1730,56 @@ function Trading() { )} + +
+
+ +
+ +
+ +
+
{alertState && ( diff --git a/src/styles/Trading.module.scss b/src/styles/Trading.module.scss index 270f4eb..d2cbdab 100644 --- a/src/styles/Trading.module.scss +++ b/src/styles/Trading.module.scss @@ -1,16 +1,19 @@ .main { + padding: 0 60px; + padding-top: 20px; display: flex; flex-direction: column; - @media screen and (max-width: 600px) { - margin-top: -50px; + @media screen and (max-width: 1060px) { + padding-right: 20px; + padding-left: 20px; } - > :first-child { - padding-bottom: 40px; - border-bottom: 1px solid var(--delimiter-color); + .orders__preloader { + margin-top: 40px; } + table { .alias { display: flex; @@ -43,100 +46,107 @@ .trading__title__wrapper { display: flex; - flex-direction: column; + align-items: center; + justify-content: space-between; gap: 25px; position: relative; - border-bottom: 1px solid var(--delimiter-color); + border: none; .currency__stats__wrapper { - max-width: 900px; display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; gap: 20px; + &_assets { + display: flex; + flex-direction: column; + gap: 7px; + + .asset { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + + p { + display: flex; + align-items: center; + gap: 5px; + font-size: 14px; + font-weight: 400; + } + + a { + font-size: 14px; + font-weight: 400; + } + } + } + > :nth-child(2), - :nth-child(4) { - padding-left: 40px; + :nth-child(4), + :nth-child(3) { + padding-left: 20px; border-left: 1px solid var(--delimiter-color); } .trading__stat__item { - padding-top: 10px; - padding-bottom: 10px; display: flex; flex-direction: column; - gap: 16px; - width: 47%; + gap: 6px; - > div { + &_nav { display: flex; align-items: center; + gap: 5px; p { + color: var(--footer-selected-link); white-space: nowrap; + font-size: 14px; + font-weight: 700; + } + } + + &_content { + display: flex; + align-items: center; + gap: 5px; + + .val { + white-space: nowrap; + font-size: 14px; + font-weight: 400; } - &:first-child { - p { - color: var(--font-dimmed-color); - } + .coefficient { + white-space: nowrap; + font-size: 14px; + font-weight: 400; - gap: 6px; - } - - &:last-child { - gap: 10px; - - p:first-child { - font-weight: 600; - } - - .coefficient__green { + &__green { color: #16d1d6; } - .coefficient__red { + &__red { color: #ff6767; } } } } - @media screen and (min-width: 1700px) { - flex-wrap: nowrap; - gap: 40px; - position: absolute; - transform: translateX(-50%); - left: 54%; - - > div { - width: auto !important; - } - - > :nth-child(3) { - padding-left: 40px; - border-left: 1px solid var(--delimiter-color); - } - } - @media screen and (max-width: 600px) { gap: 0; flex-wrap: nowrap; flex-direction: column; - > div { + >div { width: 100% !important; padding-left: 0 !important; border-left: none !important; padding: 20px 0 !important; border-bottom: 1px solid var(--delimiter-color); - - &:last-child { - border-bottom: none; - padding-bottom: 0 !important; - margin-bottom: -20px; - } } } } @@ -149,11 +159,11 @@ .trading__currency__wrapper_top { display: flex; align-items: center; - gap: 16px; + gap: 12px; - > div:first-child { - width: 68px; - height: 68px; + .coin__icon { + min-width: 48px; + min-height: 48px; display: flex; align-items: center; justify-content: center; @@ -161,342 +171,236 @@ border-radius: 50%; img { - width: 40px; + width: 25px; height: auto; } } - > div:last-child { + .coin__currency { display: flex; flex-direction: column; justify-content: space-between; - > p:first-child { - font-size: 40px; - font-weight: 500; + >p:first-child { + font-size: 18px; + font-weight: 600; span { - font-size: 40px; - color: var(--font-dimmed-color); + color: var(--footer-selected-link); } } .trading__currency__rate { display: flex; align-items: center; - gap: 10px; + gap: 5px; - > div { - background-color: var(--font-dimmed-color); - width: 1px; - height: 16px; - } - - > p { - font-weight: 600; + .trading__currency__rate_secondCurrency { + font-weight: 400; + font-size: 14px; } .trading__currency__rate_usd { - color: var(--font-dimmed-color); + color: var(--footer-selected-link); + font-size: 12px; + font-weight: 400; } } } @media screen and (max-width: 400px) { - > div:first-child { + >div:first-child { width: 48px; height: 48px; - > img { + >img { scale: 0.7; } } - > div:last-child { - > p:first-child { + >div:last-child { + >p:first-child { font-size: 24px; - > span { + >span { font-size: 24px; } } } } } - - .trading__currency__wrapper_bottom { - display: flex; - flex-direction: column; - gap: 5px; - } } } .trading__top__wrapper { + margin-top: 20px; display: flex; gap: 20px; - padding-bottom: 40px; - - > div:first-child { - padding: 30px; - width: 50%; - margin-top: 25px; - border-radius: 10px; - border: 1px solid var(--delimiter-color); - background: var(--window-bg-color); - } - - @media screen and (max-width: 1000px) { - flex-direction: column; - - > * { - width: 100% !important; - } - } - } - - .trading__chart__wrapper { - width: 100%; - overflow: hidden; - display: flex; - flex-direction: column; - - &.mobile { - display: none; - } - - .trading__chart__preloader { - height: 100%; - min-height: 545px; - } - - .trading__chart__settings { - display: flex; - align-items: center; - justify-content: space-between; - padding: 25px 0; - border-bottom: 1px solid var(--delimiter-color); - - .trading__chart__dropdown { - width: 254px; - } - } - - @media screen and (max-width: 1000px) { - &.mobile { - display: flex; - } - - &.desktop { - display: none; - } - } - } - - .trading__info { - display: flex; - gap: 40px; - - > * { - width: 100%; - } - - > div { - padding: 30px; - - background: var(--window-bg-color); - border: 1px solid var(--delimiter-color); - border-radius: 10px; - - > :first-child { - margin-right: 30px; - padding-bottom: 20px; - border-bottom: 1px solid var(--delimiter-color); - } - - > :last-child { - padding-top: 30px; - } - } - - .orders__preloader { - height: 465px; - padding-right: 30px; - } + padding-bottom: 20px; + height: 40dvh; + min-height: 380px; + max-height: 500px; .trading__orders_panel { + max-width: 415px; width: 100%; + padding: 5px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; - padding-right: 0; + @media screen and (max-width: 1480px) { + max-width: 340px; + } - .orders-panel__header { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - column-gap: 15px; - row-gap: 10px; + .tooltip__arrow { + border-top: 1px solid var(--dex-tooltip-border-color); + background-color: var(--dex-tooltip-bg); + } - .orders-panel__header__select { - overflow: initial; + .tooltip { + pointer-events: none; + position: fixed; + border: 1px solid var(--dex-tooltip-border-color); + width: 140px; + padding: 10px; + transform: translateX(-50%); + background-color: var(--dex-tooltip-bg); + + h6 { + color: var(--table-th-color); + margin-top: 12px; + font-size: 11px; + font-weight: 700; + + &:first-child { + margin-top: 0; + } } - .orders-panel__header_left { + p { + font-size: 12px; + font-weight: 400; display: flex; align-items: center; - flex-wrap: wrap; - column-gap: 10px; - row-gap: 5px; - - .header__delimiter { - width: 2px; - height: 18px; - margin-left: 17px; - margin-right: 14px; - background-color: #525e85; - } - - .header__orders_buy, - .header__orders_sell { - padding: 6px 12px; - border-radius: 8px; - - > p { - font-size: 14px; - font-weight: 400; - } - } - - .header__orders_buy { - border: 1px solid #16d1d6; - - > p { - color: #16d1d6; - } - } - - .header__orders_sell { - border: 1px solid #ff6767; - - > p { - color: #ff6767; - } - } - - > div:first-child { - display: flex; - align-items: center; - - > p { - font-size: 21px; - font-weight: 500; - color: var(--font-dimmed-color); - } - } - - .header__stats { - display: flex; - align-items: center; - gap: 5px; - - .header__summary-funds { - padding: 6px 12px; - border: 1px solid var(--font-dimmed-color); - border-radius: 8px; - - > p, - > p > span { - font-size: 14px; - color: var(--font-dimmed-color); - } - - > p { - display: flex; - gap: 5px; - } - - > p > span { - display: inline-block; - max-width: 70px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - } + gap: 5px; + margin-top: 6px; } - @media screen and (max-width: 640px) { - .orders-panel__header_left { - > div:first-child { - > p { - font-size: 14px; - } + span { + margin-top: 5px; + display: block; + color: #8d95ae; + font-size: 11px; + font-weight: 400; + } + } + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--delimiter-color); + + &_title { + font-size: 18px; + font-weight: 600; + } + + &_type { + display: flex; + align-items: center; + gap: 8px; + + button { + cursor: pointer; + width: 20px; + height: 20px; + border-radius: 4px; + font-size: 12px; + font-weight: 700; + transition: 0.3s opacity ease; + color: #ffffff; + + &.selected, + &:hover { + opacity: 80%; } - .header__delimiter { - margin-left: 12px; - margin-right: 12px; + &.all { + background: linear-gradient(to left, #ff6767 50%, #16d1d6 50%); + } + + &.buy { + background-color: #16d1d6; + } + + &.sell { + background-color: #ff6767; } } } } - > div:last-child { + .orders__panel_content { display: flex; flex-direction: column; + padding-top: 10px; } table { width: 100%; - padding-right: 12px; thead { display: flex; width: 100%; - padding-right: 18px; - margin-bottom: 8px; + padding-inline: 10px; + margin-bottom: 9px; tr { width: 100%; display: flex; + justify-content: space-between; th { - width: 100%; - font-size: 12px; + font-size: 11px; font-weight: 700; text-align: start; color: var(--table-th-color); - - br { - display: none; - } - - &:first-child { - max-width: 180px; - } + min-width: 80px; &:last-child { - max-width: 10px; + text-align: right; } } } } tbody { - height: 465px; + height: 29dvh; + min-height: 265px; + max-height: 380px; display: flex; flex-direction: column; overflow: auto; - scrollbar-gutter: stable; - padding-right: 12px; - padding-bottom: 50px; + padding-bottom: 20px; + padding: 10px; tr { + cursor: pointer; position: relative; - width: 100%; display: flex; - padding: 10px 0; align-items: center; + justify-content: space-between; + width: 100%; + padding: 4px 0; + + &:nth-child(even) { + background-color: var(--table-even-bg); + } &::after { content: ''; @@ -516,80 +420,344 @@ } } - &:not(:last-child) { - border-bottom: 1px solid var(--delimiter-color); - } - td { - flex: 1; - min-width: 0; position: relative; - > p { - overflow: hidden; - text-overflow: ellipsis; - line-height: 1; - font-size: 14px; - - > span { - margin: 5px; - line-height: 1; - vertical-align: middle; - color: var(--font-dimmed-color); - font-size: 12px; - } - } - - &:first-child { - max-width: 180px; - display: flex; - flex-direction: column; - gap: 3px; - } - &:last-child { - text-align: end; - max-width: 10px; - margin-right: 10px; - } - - svg:not(.stroked) * { - fill: transparent; - } - - svg { - transform: scale(1.2); - } - - .orders_table__buy { - &:hover { - svg path { - stroke: #56c2c6a8; - } + >p { + text-align: right; } } - .orders_table__sell { - svg { - path { - stroke: #ff6767; - } - } - - &:hover { - svg path { - stroke: #ff8585; - } - } + >p { + min-width: 80px; + width: 100%; + font-size: 12px; + font-weight: 400; } } } } } + + .orders__message { + width: 100%; + margin-top: 40px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + + svg { + transform: scale(0.8); + } + + &.all__orders__msg { + padding-right: 30px; + } + + &.user__orders__msg { + padding-right: 18px; + } + + @media screen and (max-width: 550px) { + &.all__orders__msg { + padding-right: 20px; + } + + &.user__orders__msg { + padding-right: 13px; + } + } + + >h6 { + color: var(--font-dimmed-color); + } + } } + .allTrades { + max-width: 415px; + width: 100%; + padding: 5px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + + @media screen and (max-width: 1480px) { + max-width: 340px; + } + + &__header { + border-bottom: 1px solid var(--delimiter-color); + display: flex; + align-items: center; + gap: 22px; + padding: 10px; + padding-bottom: 0; + + .navItem { + padding-bottom: 7px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + font-size: 16px; + border-bottom: 2px solid transparent; + font-weight: 600; + background-color: transparent; + cursor: pointer; + + &.active { + border-color: #1f8feb; + } + + &:hover { + color: #1f8feb; + } + } + } + + .orders__panel_content { + display: flex; + flex-direction: column; + padding-top: 10px; + } + + table { + width: 100%; + + thead { + display: flex; + width: 100%; + padding-inline: 10px; + margin-bottom: 9px; + + tr { + width: 100%; + display: flex; + justify-content: space-between; + + th { + min-width: 80px; + font-size: 11px; + font-weight: 700; + text-align: start; + color: var(--table-th-color); + + &:last-child { + text-align: right; + } + } + } + } + + tbody { + height: 29dvh; + min-height: 265px; + max-height: 380px; + display: flex; + flex-direction: column; + overflow: auto; + padding: 10px; + padding-bottom: 20px; + + tr { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 4px 0; + + &:nth-child(even) { + background-color: var(--table-even-bg); + } + + td { + position: relative; + + &:last-child { + >p { + text-align: right; + } + } + + >p { + min-width: 80px; + width: 100%; + font-size: 12px; + font-weight: 400; + } + } + } + } + } + + .orders__message { + width: 100%; + margin-top: 40px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + + svg { + transform: scale(0.8); + } + + &.all__orders__msg { + padding-right: 30px; + } + + &.user__orders__msg { + padding-right: 18px; + } + + @media screen and (max-width: 550px) { + &.all__orders__msg { + padding-right: 20px; + } + + &.user__orders__msg { + padding-right: 13px; + } + } + + >h6 { + color: var(--font-dimmed-color); + } + } + } + + .trading__chart__wrapper { + width: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + + .trading__chart__preloader { + height: 100%; + } + + .trading__chart__settings { + display: flex; + align-items: center; + justify-content: space-between; + + .trading__chart__dropdown { + width: 254px; + height: 48px; + + @media screen and (max-width: 1360px) { + width: 200px; + } + } + } + } + } + + .trading__info { + display: flex; + gap: 20px; + margin-bottom: 40px; + height: 405px; + + &_createOrders { + display: flex; + gap: 20px; + width: 100%; + } + + &_createOrder { + width: 100%; + padding: 15px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + } + + + .trading__user__orders { - padding-right: 0; + width: 100%; + padding: 5px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + + @media screen and (max-width: 1440px) { + width: 520px; + } + + &__header { + border-bottom: 1px solid var(--delimiter-color); + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + padding: 10px; + padding-bottom: 0; + + &_nav { + display: flex; + align-items: center; + gap: 22px; + + .navItem { + padding-bottom: 7px; + position: relative; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + font-size: 16px; + 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%; + } + + &.active { + border-color: #1f8feb; + } + + &:hover { + color: #1f8feb; + } + } + } + + &_btn { + 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; + } + } + } th { br { @@ -597,162 +765,112 @@ } } - > div:first-child { - margin-right: 30px; - display: flex; - align-items: center; - gap: 10px; - - > div { - padding: 5px 10px; - background-color: #ff6767; - border-radius: 100px; - - > p { - font-size: 12px; - font-weight: 700; - color: #fff; - } - } - } - - > div:last-child { - padding-right: 12px; - } - table { width: 100%; - padding-right: 33px; thead { display: flex; width: 100%; - padding-left: 15px; - margin-bottom: 8px; + padding-inline: 10px; + padding-bottom: 5px; + margin-top: 10px; tr { width: 100%; display: flex; + justify-content: space-between; th { - width: 100%; - font-size: 12px; + min-width: 100px; + font-size: 11px; font-weight: 700; text-align: start; color: var(--table-th-color); - &:first-child { - max-width: 112px; - display: flex; - flex-direction: column; - gap: 3px; - } - - &:nth-child(3) { - width: 120%; - } - - &:nth-last-child(2) { - max-width: 50px; - } - &:last-child { - max-width: 80px; + text-align: right; + min-width: 50px; } } } } tbody { - p, a { - font-size: 14px; + display: block; + text-align: right; + font-size: 12px; + font-weight: 400; } } } .trading__right__tables { - padding-bottom: 50px; - scrollbar-gutter: stable; - padding-right: 12px; - height: 465px; + padding: 10px; + padding-bottom: 20px; + height: 300px; overflow: auto; table { width: 100%; - padding: 0 15px; &.trading__apply__table { - border: 1px solid rgba(31, 143, 235, 1); - border-radius: 10px; - background: var(--blur-color); + 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; - > div:first-child { - padding-left: 15px; - padding-right: 10px; - } - - .stats__table__incoming { - padding-left: 15px; - padding-right: 10px; - border: 1px solid rgba(31, 143, 235, 1); - border-radius: 10px; - background: rgba(255, 255, 255, 0.05); + &.stats__table__incoming { + tr { + &:nth-child(even) { + background-color: var(--table-even-bg); + } + } } tr { - width: 100%; display: flex; align-items: center; - padding: 10px 0; - - &:not(:last-child) { - border-bottom: 1px solid var(--delimiter-color); - } + justify-content: space-between; + width: 100%; + padding: 4px 0; td { - width: 100%; - min-width: 0; position: relative; + min-width: 100px; - > p { - overflow: hidden; - text-overflow: ellipsis; - line-height: 1; + &:last-child { + min-width: 50px; + } - > span { - margin: 5px; - vertical-align: middle; - line-height: 1; - color: var(--font-dimmed-color); - font-size: 12px; - margin: 5px; + @media screen and (max-width: 1440px) { + min-width: 0; + + &:last-child { + min-width: 0; } } - &:first-child { - max-width: 112px; - display: flex; - flex-direction: column; - gap: 3px; - } + >p { + width: 100%; + font-size: 12px; + font-weight: 400; - &:nth-child(3) { - width: 120%; - } - - &:nth-last-child(2) { - max-width: 50px; - } - - &:last-child { - text-align: end; - max-width: 80px; + >span { + line-height: 1; + color: var(--font-dimmed-color); + font-size: 11px; + } } } } @@ -763,13 +881,17 @@ .orders__message { width: 100%; - height: 465px; + margin-top: 40px !important; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; + svg { + transform: scale(0.8); + } + &.all__orders__msg { padding-right: 30px; } @@ -788,7 +910,7 @@ } } - > h6 { + >h6 { color: var(--font-dimmed-color); } } @@ -801,7 +923,7 @@ transform: translateX(-50%); background-color: var(--trade-table-tooltip); - > .table__tooltip_arrow { + >.table__tooltip_arrow { background-color: var(--trade-table-tooltip); } } @@ -812,7 +934,7 @@ left: 2%; background-color: var(--trade-table-tooltip); - > .table__tooltip_arrow { + >.table__tooltip_arrow { left: 40px; background-color: var(--trade-table-tooltip); } @@ -824,7 +946,7 @@ left: -50%; background-color: var(--trade-table-tooltip); - > .table__tooltip_arrow { + >.table__tooltip_arrow { border-radius: 2px; left: 10%; background-color: var(--trade-table-tooltip); @@ -841,12 +963,25 @@ border-radius: 100px; background: radial-gradient(100% 246.57% at 0% 0%, #a366ff 0%, #601fff 100%); - > img { + &.icon { + min-width: 15px; + height: 15px; + border-radius: 50%; + justify-content: center; + padding: 0; + + >img { + width: 11px; + height: 11px; + } + } + + >img { height: 13px; width: auto; } - > span { + >span { font-size: 10px; font-weight: 600; } @@ -856,252 +991,4 @@ background: radial-gradient(100% 188.88% at 0% 0%, #16d1d6 0%, #274cff 100%); } } - - @media screen and (max-width: 1750px) { - .trading__info { - flex-direction: column; - - > div:last-child { - width: 100%; - min-width: 100%; - } - } - } - - @media screen and (max-width: 900px) { - .trading__orders_panel { - table { - th, - td { - &:nth-child(1) { - padding-right: 20px; - } - } - } - } - - .trading__user__orders { - > div:last-child { - table { - td, - th { - &:first-child { - display: none; - } - } - } - } - } - } - - @media screen and (max-width: 700px) { - .trading__user__orders { - table { - td > a, - td > p { - font-size: 14px; - - > span { - display: none; - } - } - - td, - th { - &:nth-last-child(4) { - display: none; - } - } - - tbody { - tr { - padding: 12px 0 !important; - } - } - } - } - - .trading__orders_panel { - table { - td > a, - td > p { - font-size: 14px; - - > span { - display: none; - } - } - - th, - td { - &:nth-last-child(3) { - display: none; - } - } - - tbody { - tr { - padding: 12px 0 !important; - } - } - } - } - } - - @media screen and (max-width: 600px) { - .trading__user__orders th br { - display: block !important; - } - } - - @media screen and (max-width: 550px) { - .trading__top__wrapper { - > div:first-child { - padding: 20px !important; - } - } - - .trading__info { - > div { - padding: 20px 0; - } - - .trading__user__orders { - width: 100%; - min-width: 100%; - padding: 20px; - padding-right: 0; - - > div:first-child { - margin-right: 20px; - } - - > div:last-child { - padding-right: 7px !important; - - > table:first-child { - padding-right: 26px !important; - } - } - - table td { - font-size: 14px; - } - - thead { - padding-left: 10px !important; - - th { - font-size: 11px; - } - } - - .trading__right__tables { - padding-right: 7px !important; - - table { - padding: 0 10px !important; - } - } - } - - .trading__orders_panel { - padding-left: 20px !important; - - thead { - padding-right: 13px !important; - } - - > div:first-child { - margin-right: 20px; - } - - > :last-child { - flex-direction: column; - } - - > input[type='number'], - > button { - padding-top: 13px !important; - padding-bottom: 13px !important; - } - - > div:first-child { - h5 { - span { - display: none; - } - } - } - - table { - padding-right: 7px !important; - - thead { - tr th br { - display: block !important; - } - } - - tbody { - padding-right: 7px !important; - } - } - } - } - - .trading__chart__wrapper { - .trading__chart__settings { - padding-top: 40px; - flex-direction: column-reverse; - align-items: flex-start !important; - gap: 40px; - - .trading__chart__dropdown { - width: 100%; - - > div:first-child { - height: 48px; - } - } - } - } - } - - @media screen and (max-width: 450px) { - .trading__info { - .trading__orders_panel { - .orders__preloader { - padding-right: 20px !important; - } - - table { - td > a, - td > p, - th { - font-size: 11 px; - } - - thead tr th { - font-size: 9px; - } - } - } - - .trading__user__orders { - table { - thead tr th { - font-size: 9px; - } - - tbody { - td > p, - td > a { - font-size: 11px; - } - } - } - } - } - } -} +} \ No newline at end of file diff --git a/src/styles/themes/light.scss b/src/styles/themes/light.scss index b8eee08..664db18 100644 --- a/src/styles/themes/light.scss +++ b/src/styles/themes/light.scss @@ -46,6 +46,10 @@ --admin-table-border-color: rgba(31, 143, 235, 0.2); --alert-btn-bg: rgba(31, 143, 235, 0.2); --alert-btn-hover: rgba(31, 143, 235, 0.3); + --table-even-bg: #f2f5f9; + --table-tr-hover-color: #dcf0ff; + --dex-tooltip-bg: #eff8ff; + --dex-tooltip-border-color: #1f8feb33; } [data-theme='dark'] { @@ -96,4 +100,8 @@ --admin-table-border-color: #596f98; --alert-btn-bg: rgba(31, 143, 235, 0.2); --alert-btn-hover: rgba(31, 143, 235, 0.3); + --table-even-bg: #0c1d4f; + --table-tr-hover-color: #172a66; + --dex-tooltip-bg: #11316b; + --dex-tooltip-border-color: #1f8feb26; } diff --git a/src/utils/methods.ts b/src/utils/methods.ts index db63ea0..f320543 100644 --- a/src/utils/methods.ts +++ b/src/utils/methods.ts @@ -327,3 +327,11 @@ export async function getZanoPrice() { ) .then((res) => res.data); } + +export async function getTrades(pairId: string) { + return axios + .post(`/api/orders/get-trades`, { + pairId, + }) + .then((res) => res.data); +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 568866d..5498468 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -132,6 +132,17 @@ export function isPositiveFloatStr(input: string) { return regExp.test(input); } +export function formatTime(ts: string | number) { + let num = Number(ts); + + if (num < 1e12) num *= 1000; + const date = new Date(num); + + if (Number.isNaN(date.getTime())) return '-'; + + return date.toLocaleTimeString('ru-RU', { hour12: false }); +} + export function classes(...items: (string | boolean | undefined)[]): string { // boolean for constructions like [predicate] && [className] return items.filter((className) => className).join(' '); From dbdfbd1ed6bd813fad3c6c1c52c2dbf123721947 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Fri, 8 Aug 2025 16:40:10 +0500 Subject: [PATCH 02/27] refactoring: dex/trading --- .../ContentPreloader.module.scss | 6 +- .../UI/ContentPreloader/ContentPreloader.tsx | 11 +- src/components/UI/ContentPreloader/types.ts | 6 + src/components/UI/EmptyMessage/index.tsx | 16 + .../UI/EmptyMessage/styles.module.scss | 17 + src/components/UI/EmptyMessage/types.ts | 6 + .../UI/RangeInput/RangeInput.module.scss | 16 +- .../default/Header/Header.module.scss | 11 +- src/constants/index.ts | 36 + src/hook/useAlert.ts | 22 + src/hook/useMouseLeave.ts | 21 + src/hook/useScroll.ts | 20 + src/interfaces/common/ContextValue.ts | 11 + src/interfaces/common/MatrixAddress.ts | 6 + src/interfaces/common/orderFormOutput.ts | 24 + .../InputPanelItem/InputPanelItemProps.ts | 10 +- .../props/pages/dex/trading/StatItemProps.ts | 1 + src/pages/_app.tsx | 1 + .../InputPanelItem/InputPanelItem.module.scss | 243 --- .../OrdersBuySellSwitch.module.scss | 31 - .../OrdersBuySellSwitch.tsx | 33 - src/pages/dex/trading/[id].tsx | 1868 ++--------------- .../trading/components/AllTrades/index.tsx | 82 + .../components/AllTrades/styles.module.scss | 116 + .../dex/trading/components/AllTrades/types.ts | 15 + .../trading/components/BadgeStatus/index.tsx | 26 + .../components/BadgeStatus/styles.module.scss | 39 + .../trading/components/BadgeStatus/types.ts | 4 + .../CandleChart/index.tsx} | 8 +- .../CandleChart/styles.module.scss} | 4 +- .../CandleChart/testCandles.json | 0 .../components/LabeledInput/index.tsx | 52 + .../LabeledInput/styles.module.scss | 62 + .../InputPanelItem/index.tsx} | 129 +- .../InputPanelItem/styles.module.scss | 94 + .../MatrixConnectionBadge/index.tsx | 41 + .../MatrixConnectionBadge/styles.module.scss | 28 + .../components/MatrixConnectionBadge/types.ts | 7 + .../components/OrderRowTooltipCell/index.tsx | 52 + .../OrderRowTooltipCell/styles.module.scss | 28 + .../components/OrderRowTooltipCell/types.ts | 9 + .../OrdersPool/components/OrdersRow/index.tsx | 54 + .../components/OrdersRow/styles.module.scss | 48 + .../OrdersPool/components/OrdersRow/types.ts | 14 + .../trading/components/OrdersPool/index.tsx | 198 ++ .../components/OrdersPool/styles.module.scss | 151 ++ .../trading/components/OrdersPool/types.ts | 21 + .../dex/trading/components/StatItem/index.tsx | 32 + .../components/StatItem/styles.module.scss | 44 + .../TimeLeft/index.tsx} | 0 .../components/AssetRow/index.tsx | 23 + .../components/AssetRow/styles.module.scss | 19 + .../components/AssetRow/types.ts | 6 + .../components/CurrencyIcon/index.tsx | 8 + .../components/CurrencyIcon/types.ts | 4 + .../components/TradingHeader/index.tsx | 127 ++ .../TradingHeader/styles.module.scss | 78 + .../trading/components/TradingHeader/types.ts | 12 + .../components/MyOrdersApplyRow/index.tsx | 231 ++ .../components/MyOrdersApplyRow/types.ts | 16 + .../components/MyOrdersRow/index.tsx | 140 ++ .../components/MyOrdersRow/types.ts | 13 + .../trading/components/UserOrders/index.tsx | 133 ++ .../components/UserOrders/styles.module.scss | 227 ++ .../trading/components/UserOrders/types.ts | 22 + src/pages/dex/trading/find-pair/index.tsx | 44 - .../dex/trading/helpers/handleInputChange.ts | 89 + .../dex/trading/helpers/takeOrderClick.ts | 96 + .../dex/trading/hooks/useFilteredData.ts | 47 + .../dex/trading/hooks/useMatrixAddresses.ts | 25 + src/pages/dex/trading/hooks/useOrdereForm.ts | 104 + .../dex/trading/hooks/useSocketListeners.ts | 85 + src/pages/dex/trading/hooks/useTradeInit.ts | 70 + src/pages/dex/trading/hooks/useTradingData.ts | 144 ++ src/store/store-reducer.tsx | 9 + src/styles/Trading.module.scss | 961 +-------- src/styles/themes/dark.scss | 53 + src/styles/themes/light.scss | 54 - src/utils/methods.ts | 12 + src/utils/utils.ts | 4 +- 80 files changed, 3453 insertions(+), 3177 deletions(-) create mode 100644 src/components/UI/ContentPreloader/types.ts create mode 100644 src/components/UI/EmptyMessage/index.tsx create mode 100644 src/components/UI/EmptyMessage/styles.module.scss create mode 100644 src/components/UI/EmptyMessage/types.ts create mode 100644 src/constants/index.ts create mode 100644 src/hook/useAlert.ts create mode 100644 src/hook/useMouseLeave.ts create mode 100644 src/hook/useScroll.ts create mode 100644 src/interfaces/common/MatrixAddress.ts create mode 100644 src/interfaces/common/orderFormOutput.ts delete mode 100644 src/pages/dex/trading/InputPanelItem/InputPanelItem.module.scss delete mode 100644 src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.module.scss delete mode 100644 src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.tsx create mode 100644 src/pages/dex/trading/components/AllTrades/index.tsx create mode 100644 src/pages/dex/trading/components/AllTrades/styles.module.scss create mode 100644 src/pages/dex/trading/components/AllTrades/types.ts create mode 100644 src/pages/dex/trading/components/BadgeStatus/index.tsx create mode 100644 src/pages/dex/trading/components/BadgeStatus/styles.module.scss create mode 100644 src/pages/dex/trading/components/BadgeStatus/types.ts rename src/pages/dex/trading/{CandleChart/CandleChart.tsx => components/CandleChart/index.tsx} (96%) rename src/pages/dex/trading/{CandleChart/CandleChart.module.scss => components/CandleChart/styles.module.scss} (93%) rename src/pages/dex/trading/{ => components}/CandleChart/testCandles.json (100%) create mode 100644 src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/index.tsx create mode 100644 src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/styles.module.scss rename src/pages/dex/trading/{InputPanelItem/InputPanelItem.tsx => components/InputPanelItem/index.tsx} (65%) create mode 100644 src/pages/dex/trading/components/InputPanelItem/styles.module.scss create mode 100644 src/pages/dex/trading/components/MatrixConnectionBadge/index.tsx create mode 100644 src/pages/dex/trading/components/MatrixConnectionBadge/styles.module.scss create mode 100644 src/pages/dex/trading/components/MatrixConnectionBadge/types.ts create mode 100644 src/pages/dex/trading/components/OrderRowTooltipCell/index.tsx create mode 100644 src/pages/dex/trading/components/OrderRowTooltipCell/styles.module.scss create mode 100644 src/pages/dex/trading/components/OrderRowTooltipCell/types.ts create mode 100644 src/pages/dex/trading/components/OrdersPool/components/OrdersRow/index.tsx create mode 100644 src/pages/dex/trading/components/OrdersPool/components/OrdersRow/styles.module.scss create mode 100644 src/pages/dex/trading/components/OrdersPool/components/OrdersRow/types.ts create mode 100644 src/pages/dex/trading/components/OrdersPool/index.tsx create mode 100644 src/pages/dex/trading/components/OrdersPool/styles.module.scss create mode 100644 src/pages/dex/trading/components/OrdersPool/types.ts create mode 100644 src/pages/dex/trading/components/StatItem/index.tsx create mode 100644 src/pages/dex/trading/components/StatItem/styles.module.scss rename src/pages/dex/trading/{TimeLeft/TimeLeft.tsx => components/TimeLeft/index.tsx} (100%) create mode 100644 src/pages/dex/trading/components/TradingHeader/components/AssetRow/index.tsx create mode 100644 src/pages/dex/trading/components/TradingHeader/components/AssetRow/styles.module.scss create mode 100644 src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts create mode 100644 src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx create mode 100644 src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts create mode 100644 src/pages/dex/trading/components/TradingHeader/index.tsx create mode 100644 src/pages/dex/trading/components/TradingHeader/styles.module.scss create mode 100644 src/pages/dex/trading/components/TradingHeader/types.ts create mode 100644 src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/index.tsx create mode 100644 src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/types.ts create mode 100644 src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/index.tsx create mode 100644 src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/types.ts create mode 100644 src/pages/dex/trading/components/UserOrders/index.tsx create mode 100644 src/pages/dex/trading/components/UserOrders/styles.module.scss create mode 100644 src/pages/dex/trading/components/UserOrders/types.ts delete mode 100644 src/pages/dex/trading/find-pair/index.tsx create mode 100644 src/pages/dex/trading/helpers/handleInputChange.ts create mode 100644 src/pages/dex/trading/helpers/takeOrderClick.ts create mode 100644 src/pages/dex/trading/hooks/useFilteredData.ts create mode 100644 src/pages/dex/trading/hooks/useMatrixAddresses.ts create mode 100644 src/pages/dex/trading/hooks/useOrdereForm.ts create mode 100644 src/pages/dex/trading/hooks/useSocketListeners.ts create mode 100644 src/pages/dex/trading/hooks/useTradeInit.ts create mode 100644 src/pages/dex/trading/hooks/useTradingData.ts create mode 100644 src/styles/themes/dark.scss diff --git a/src/components/UI/ContentPreloader/ContentPreloader.module.scss b/src/components/UI/ContentPreloader/ContentPreloader.module.scss index 453ed49..3076015 100644 --- a/src/components/UI/ContentPreloader/ContentPreloader.module.scss +++ b/src/components/UI/ContentPreloader/ContentPreloader.module.scss @@ -1,17 +1,17 @@ -.content__preloader__wrapper { +.loader { width: 100%; display: flex; justify-content: center; align-items: center; - > div { + &__content { display: flex; flex-direction: column; align-items: center; gap: 20px; } - p { + &__text { color: var(--font-dimmed-color); } } diff --git a/src/components/UI/ContentPreloader/ContentPreloader.tsx b/src/components/UI/ContentPreloader/ContentPreloader.tsx index 95d129e..2fd0a1e 100644 --- a/src/components/UI/ContentPreloader/ContentPreloader.tsx +++ b/src/components/UI/ContentPreloader/ContentPreloader.tsx @@ -1,12 +1,15 @@ import Preloader from '@/components/UI/Preloader/Preloader'; +import { classes } from '@/utils/utils'; import styles from './ContentPreloader.module.scss'; +import { ContentPreloaderProps } from './types'; -function ContentPreloader(props: { className?: string }) { +function ContentPreloader({ className, style }: ContentPreloaderProps) { return ( -
-
+
+
-

Loading...

+ +

Loading...

); diff --git a/src/components/UI/ContentPreloader/types.ts b/src/components/UI/ContentPreloader/types.ts new file mode 100644 index 0000000..8b3523d --- /dev/null +++ b/src/components/UI/ContentPreloader/types.ts @@ -0,0 +1,6 @@ +import { CSSProperties } from 'react'; + +export interface ContentPreloaderProps { + className?: string; + style?: CSSProperties; +} diff --git a/src/components/UI/EmptyMessage/index.tsx b/src/components/UI/EmptyMessage/index.tsx new file mode 100644 index 0000000..e046819 --- /dev/null +++ b/src/components/UI/EmptyMessage/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { ReactComponent as NoOffersIcon } from '@/assets/images/UI/no_offers.svg'; +import { classes } from '@/utils/utils'; +import styles from './styles.module.scss'; +import { EmptyMessageProps } from './types'; + +const EmptyMessage = ({ text, customIcon }: EmptyMessageProps) => { + return ( +
+ {!customIcon ? : customIcon} +
{text}
+
+ ); +}; + +export default EmptyMessage; diff --git a/src/components/UI/EmptyMessage/styles.module.scss b/src/components/UI/EmptyMessage/styles.module.scss new file mode 100644 index 0000000..8533ba5 --- /dev/null +++ b/src/components/UI/EmptyMessage/styles.module.scss @@ -0,0 +1,17 @@ +.empty { + width: 100%; + margin-top: 40px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + + &__text { + color: var(--font-dimmed-color); + } + + &__icon { + transform: scale(0.8); + } +} diff --git a/src/components/UI/EmptyMessage/types.ts b/src/components/UI/EmptyMessage/types.ts new file mode 100644 index 0000000..17d350a --- /dev/null +++ b/src/components/UI/EmptyMessage/types.ts @@ -0,0 +1,6 @@ +import { ReactNode } from 'react'; + +export interface EmptyMessageProps { + text: string; + customIcon?: ReactNode; +} diff --git a/src/components/UI/RangeInput/RangeInput.module.scss b/src/components/UI/RangeInput/RangeInput.module.scss index 75bd42c..b68e15e 100644 --- a/src/components/UI/RangeInput/RangeInput.module.scss +++ b/src/components/UI/RangeInput/RangeInput.module.scss @@ -9,7 +9,7 @@ background: none; outline: none; border: none; - z-index: 5; + z-index: 2; cursor: pointer; } @@ -81,18 +81,4 @@ border: 2px solid var(--window-bg-color); transition: background 0.3s ease-in-out; } - - // .input__range::-webkit-slider-runnable-track::-webkit-slider-thumb { - // background-color: #fff; - // border: 2px solid #555; - // } - - // .input__range::-webkit-slider-runnable-track { - // -webkit-appearance: none; - // height: 20px; - // background-color: #1F8FEB; - // border: 1px solid #ffffff; - // box-shadow: none; - // background: transparent; - // } } diff --git a/src/components/default/Header/Header.module.scss b/src/components/default/Header/Header.module.scss index 8c4d5ec..2560849 100644 --- a/src/components/default/Header/Header.module.scss +++ b/src/components/default/Header/Header.module.scss @@ -11,11 +11,14 @@ position: relative; &.lg { - padding: 0 60px; + padding-inline: 60px; - @media screen and (max-width: 1060px) { - padding-right: 20px; - padding-left: 20px; + @media screen and (max-width: 1600px) { + padding-inline: 40px; + } + + @media screen and (max-width: 1200px) { + padding-inline: 20px; } } diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..89d2759 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,36 @@ +import PeriodState from '@/interfaces/states/pages/dex/trading/InputPanelItem/PeriodState'; +import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; + +export const periods: PeriodState[] = [ + { + name: '1H', + code: '1h', + }, + { + name: '1D', + code: '1d', + }, + { + name: '1W', + code: '1w', + }, + { + name: '1M', + code: '1m', + }, +]; + +export const buySellValues: SelectValue[] = [ + { + name: 'All', + code: 'all', + }, + { + name: 'Buy', + code: 'buy', + }, + { + name: 'Sell', + code: 'sell', + }, +]; diff --git a/src/hook/useAlert.ts b/src/hook/useAlert.ts new file mode 100644 index 0000000..dd282cc --- /dev/null +++ b/src/hook/useAlert.ts @@ -0,0 +1,22 @@ +import AlertType from '@/interfaces/common/AlertType'; +import { Store } from '@/store/store-reducer'; +import { useContext } from 'react'; + +export const useAlert = () => { + const { state, dispatch } = useContext(Store); + + const setAlertState = (state: AlertType) => { + dispatch({ type: 'ALERT_STATE_UPDATED', payload: state }); + }; + + const setAlertSubtitle = (subtitle: string) => { + dispatch({ type: 'ALERT_SUBTITLE_UPDATED', payload: subtitle }); + }; + + return { + alertState: state.alertState, + alertSubtitle: state.alertSubtitle, + setAlertState, + setAlertSubtitle, + }; +}; diff --git a/src/hook/useMouseLeave.ts b/src/hook/useMouseLeave.ts new file mode 100644 index 0000000..dc284ec --- /dev/null +++ b/src/hook/useMouseLeave.ts @@ -0,0 +1,21 @@ +import { ForwardedRef, useEffect } from 'react'; + +const useMouseLeave = (ref: ForwardedRef, callbackFn: () => void) => { + useEffect(() => { + const targetEl = (event: MouseEvent) => { + if (ref && typeof ref !== 'function' && ref.current) { + if (ref?.current && !ref?.current.contains(event.target as Node)) { + callbackFn(); + } + } + }; + + window.addEventListener('mousemove', targetEl); + + return () => { + window.removeEventListener('mousemove', targetEl); + }; + }, []); +}; + +export default useMouseLeave; diff --git a/src/hook/useScroll.ts b/src/hook/useScroll.ts new file mode 100644 index 0000000..baf2f2c --- /dev/null +++ b/src/hook/useScroll.ts @@ -0,0 +1,20 @@ +import { useCallback, useRef } from 'react'; + +function useScroll() { + const elementRef = useRef(null); + + const scrollToElement = useCallback((options?: ScrollIntoViewOptions) => { + if (elementRef.current) { + elementRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'nearest', + ...options, + }); + } + }, []); + + return { elementRef, scrollToElement }; +} + +export default useScroll; diff --git a/src/interfaces/common/ContextValue.ts b/src/interfaces/common/ContextValue.ts index 43ed69e..4ca5496 100644 --- a/src/interfaces/common/ContextValue.ts +++ b/src/interfaces/common/ContextValue.ts @@ -1,6 +1,7 @@ import { Dispatch } from 'react'; import { GetUserResData } from '../responses/user/GetUserRes'; import { GetConfigResData } from '../responses/config/GetConfigRes'; +import AlertType from './AlertType'; export interface Asset { name: string; @@ -50,6 +51,8 @@ interface ContextState { offers: number; }; closed_notifications: number[]; + alertState: AlertType; + alertSubtitle: string; } type ContextAction = @@ -75,6 +78,14 @@ type ContextAction = | { type: 'CLOSED_NOTIFICATIONS_UPDATED'; payload: number[]; + } + | { + type: 'ALERT_STATE_UPDATED'; + payload: AlertType; + } + | { + type: 'ALERT_SUBTITLE_UPDATED'; + payload: string; }; interface ContextValue { diff --git a/src/interfaces/common/MatrixAddress.ts b/src/interfaces/common/MatrixAddress.ts new file mode 100644 index 0000000..23f6451 --- /dev/null +++ b/src/interfaces/common/MatrixAddress.ts @@ -0,0 +1,6 @@ +interface MatrixAddress { + address: string; + registered: boolean; +} + +export default MatrixAddress; diff --git a/src/interfaces/common/orderFormOutput.ts b/src/interfaces/common/orderFormOutput.ts new file mode 100644 index 0000000..e31124a --- /dev/null +++ b/src/interfaces/common/orderFormOutput.ts @@ -0,0 +1,24 @@ +import { Dispatch, SetStateAction } from 'react'; + +interface OrderFormOutput { + price: string; + amount: string; + total: string; + priceValid: boolean; + amountValid: boolean; + totalValid: boolean; + totalUsd: string | undefined; + rangeInputValue: string; + setRangeInputValue: Dispatch>; + onPriceChange: (_inputValue: string) => void; + onAmountChange: (_inputValue: string) => void; + resetForm: () => void; + setTotal: Dispatch>; + setPrice: Dispatch>; + setAmount: Dispatch>; + setPriceValid: Dispatch>; + setAmountValid: Dispatch>; + setTotalValid: Dispatch>; +} + +export default OrderFormOutput; diff --git a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts index 89e795b..d3897ea 100644 --- a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts +++ b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts @@ -1,22 +1,20 @@ -import AlertType from '@/interfaces/common/AlertType'; import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; import { Dispatch, SetStateAction } from 'react'; interface InputPanelItemProps { + currencyNames: { + firstCurrencyName: string; + secondCurrencyName: string; + }; priceState: string; amountState: string; totalState: string; buySellValues: SelectValue[]; buySellState: SelectValue; - // setBuySellState: Dispatch>; setPriceFunction: (_value: string) => void; setAmountFunction: (_value: string) => void; - setAlertState: Dispatch>; - setAlertSubtitle: Dispatch>; setRangeInputValue: Dispatch>; rangeInputValue: string; - firstCurrencyName: string; - secondCurrencyName: string; balance: number | undefined; amountValid: boolean; priceValid: boolean; diff --git a/src/interfaces/props/pages/dex/trading/StatItemProps.ts b/src/interfaces/props/pages/dex/trading/StatItemProps.ts index cf99aeb..20f0ec3 100644 --- a/src/interfaces/props/pages/dex/trading/StatItemProps.ts +++ b/src/interfaces/props/pages/dex/trading/StatItemProps.ts @@ -5,6 +5,7 @@ interface StatItemProps { title: string; value: string; coefficient?: number; + className?: string; } export default StatItemProps; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index bce9305..dc7bb30 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,5 +1,6 @@ import '@/styles/globals.scss'; import '@/styles/themes/light.scss'; +import '@/styles/themes/dark.scss'; import Head from 'next/head'; import { StoreProvider } from '@/store/store-reducer'; import NextApp, { AppContext, AppProps } from 'next/app'; diff --git a/src/pages/dex/trading/InputPanelItem/InputPanelItem.module.scss b/src/pages/dex/trading/InputPanelItem/InputPanelItem.module.scss deleted file mode 100644 index 5ad992f..0000000 --- a/src/pages/dex/trading/InputPanelItem/InputPanelItem.module.scss +++ /dev/null @@ -1,243 +0,0 @@ -.input_panel__item { - width: 100%; - - &_header { - display: flex; - justify-content: space-between; - align-items: center; - padding-bottom: 10px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 10px; - - h5 { - font-size: 16px; - font-weight: 600; - } - - .input_panel__fees { - display: flex; - justify-content: space-between; - font-weight: 400; - font-size: 12px; - - p { - color: var(--table-th-color); - font-weight: 400; - font-size: 12px; - } - - span { - font-weight: 400; - font-size: 12px; - } - } - } - - &_body { - display: flex; - flex-direction: column; - gap: 10px; - - button { - margin-top: 10px; - } - - > .buy_btn { - background-color: #16d1d6; - - &:hover { - background-color: #45dade; - } - } - - > .sell_btn { - background-color: #ff6767; - - &:hover { - background-color: #ff8585; - } - } - - .input_panel__range { - margin-top: 10px; - } - - .input_panel__expiration { - display: flex; - justify-content: space-between; - gap: 20px; - - h6 { - white-space: nowrap; - } - - .expiration__dropdown { - width: 100%; - } - - > div:first-child { - display: flex; - align-items: center; - gap: 6px; - } - - @media screen and (max-width: 1500px) { - flex-wrap: wrap; - } - - @media screen and (max-width: 1000px) { - flex-wrap: nowrap; - } - - @media screen and (max-width: 530px) { - flex-wrap: wrap; - } - } - - .labeled_input { - display: flex; - flex-direction: column; - gap: 8px; - - h6 { - font-size: 11px; - font-family: 700; - color: var(--table-th-color); - } - - > div { - width: 100%; - position: relative; - background-color: var(--bordered-input-bg); - border: 1px solid var(--window-border-color); - border-radius: 8px; - display: flex; - overflow: hidden; - - input { - width: 100%; - padding: 13px 15px; - background-color: transparent; - border: none; - font-size: 16px; - font-weight: 400; - } - - .labeled_input__value { - padding-right: 10px; - display: flex; - align-items: center; - - > p { - color: var(--table-th-color); - font-size: 12px; - font-weight: 400; - } - } - - .labeled_input__currency { - min-width: 82px; - max-width: 150px; - padding: 0 15px; - background-color: var(--dex-input-currency); - display: flex; - align-items: center; - justify-content: center; - - > p { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - &.labeled_input__invalid > div { - border-color: #ff6767; - } - - @media screen and (max-width: 430px) { - > div { - input, - .labeled_input__value > p, - .labeled_input__currency > p { - font-size: 13px; - } - - input { - padding: 19px 15px; - } - - .labeled_input__currency { - min-width: 70px; - } - } - } - } - } -} - -.buy-sell-switch { - padding: 3px; - height: 30px; - display: flex; - align-items: center; - border-radius: 100px; - border: 1px solid var(--dex-buy-sell-border); - - .buy-sell-switch__item { - width: 50px; - height: 100%; - background-color: transparent; - cursor: pointer; - font-size: 14px; - font-weight: 600; - border-radius: 100px; - - &.item_selected-buy { - background-color: #16d1d6; - color: #ffffff; - } - - &.item_selected-sell { - background-color: #ff6767; - color: #ffffff; - } - } -} - -.apply__alert { - display: flex; - gap: 20px; - align-items: center; - - &__content { - display: flex; - flex-direction: column; - gap: 10px; - } - - &__button { - max-width: 125px; - background-color: var(--alert-btn-bg); - color: #1f8feb; - padding: 7px 32px; - font-size: 12px; - font-weight: 500; - - &:hover { - background-color: var(--alert-btn-hover); - } - } - - h2 { - font-size: 16px; - font-weight: 600; - } - - p { - font-size: 14px; - opacity: 0.7; - margin-bottom: 5px; - } -} diff --git a/src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.module.scss b/src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.module.scss deleted file mode 100644 index aac0fd1..0000000 --- a/src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -.orders-buy-sell-switch { - display: flex; - align-items: center; - gap: 8px; - background-color: transparent !important; - cursor: pointer; - - &:hover { - opacity: 0.7; - } - - > p { - font-size: 16px; - font-weight: 600; - color: #16d1d6; - } - - > svg > * { - fill: transparent; - } - - &.orders-buy-sell-switch_sell { - > p { - color: #ff6767; - } - - > svg > * { - stroke: #ff6767; - } - } -} diff --git a/src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.tsx b/src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.tsx deleted file mode 100644 index fbe8d93..0000000 --- a/src/pages/dex/trading/OrdersBuySellSwitch/OrdersBuySellSwitch.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import HorizontalSelectProps from '@/interfaces/props/components/UI/HorizontalSelect/HorizontalSelectProps'; -import { ReactComponent as ArrowIcon } from '@/assets/images/UI/trade_arrow.svg'; -import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; -import { classes } from '@/utils/utils'; -import styles from './OrdersBuySellSwitch.module.scss'; - -export default function OrdersBuySellSwitch({ - body, - value, - setValue, - className, -}: HorizontalSelectProps) { - const defaultValue = body[0]; - - const buyValue = body.find((e) => e.code === 'buy'); - const sellValue = body.find((e) => e.code === 'sell'); - - const isBuy = value.code === 'buy'; - - return ( - - ); -} diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 5cfb914..7ef62e0 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -1,45 +1,12 @@ import styles from '@/styles/Trading.module.scss'; import Footer from '@/components/default/Footer/Footer'; import Header from '@/components/default/Header/Header'; -import { ReactComponent as ClockIcon } from '@/assets/images/UI/clock_icon.svg'; -import { ReactComponent as UpIcon } from '@/assets/images/UI/up_icon.svg'; -import { ReactComponent as DownIcon } from '@/assets/images/UI/down_icon.svg'; -import { ReactComponent as VolumeIcon } from '@/assets/images/UI/volume_icon.svg'; -import { ReactComponent as NoOffersIcon } from '@/assets/images/UI/no_offers.svg'; import Dropdown from '@/components/UI/Dropdown/Dropdown'; import HorizontalSelect from '@/components/UI/HorizontalSelect/HorizontalSelect'; -import { useContext, useEffect, useRef, useState } from 'react'; -import { Store } from '@/store/store-reducer'; -import { useRouter } from 'next/router'; -import { - applyOrder, - cancelOrder, - getOrdersPage, - confirmTransaction, - getPair, - getPairStats, - getUserOrdersPage, - getCandles, - getTrades, -} from '@/utils/methods'; +import { useCallback, useState } from 'react'; +import { cancelOrder } from '@/utils/methods'; import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; -import Link from 'next/link'; -import { nanoid } from 'nanoid'; -import { - classes, - cutAddress, - formatDollarValue, - formatTime, - isPositiveFloatStr, - notationToString, - roundTo, - shortenAddress, - tradingKnownCurrencies, -} from '@/utils/utils'; import Alert from '@/components/UI/Alert/Alert'; -import socket from '@/utils/socket'; -import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; -import AlertType from '@/interfaces/common/AlertType'; import PeriodState from '@/interfaces/states/pages/dex/trading/InputPanelItem/PeriodState'; import OrderRow from '@/interfaces/common/OrderRow'; import ApplyTip from '@/interfaces/common/ApplyTip'; @@ -47,1183 +14,112 @@ import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes'; import PairData from '@/interfaces/common/PairData'; import CandleRow from '@/interfaces/common/CandleRow'; -import StatItemProps from '@/interfaces/props/pages/dex/trading/StatItemProps'; -import { confirmIonicSwap, ionicSwap } from '@/utils/wallet'; -import Decimal from 'decimal.js'; -import Tooltip from '@/components/UI/Tooltip/Tooltip'; -import { updateAutoClosedNotification } from '@/store/actions'; -import useUpdateUser from '@/hook/useUpdateUser'; -import LightningImg from '@/assets/images/UI/lightning.png'; -import RocketImg from '@/assets/images/UI/rocket.png'; -import { ReactComponent as ConnectionIcon } from '@/assets/images/UI/connection.svg'; -import Image from 'next/image'; -import BackButton from '@/components/default/BackButton/BackButton'; import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; -import CandleChart from './CandleChart/CandleChart'; -import InputPanelItem from './InputPanelItem/InputPanelItem'; -import { validateTokensInput } from '../../../../shared/utils'; +import { periods, buySellValues } from '@/constants'; +import { useAlert } from '@/hook/useAlert'; +import useScroll from '@/hook/useScroll'; +import CandleChart from './components/CandleChart'; +import InputPanelItem from './components/InputPanelItem'; +import TradingHeader from './components/TradingHeader'; +import UserOrders from './components/UserOrders'; +import AllTrades from './components/AllTrades'; +import OrdersPool from './components/OrdersPool'; +import { useSocketListeners } from './hooks/useSocketListeners'; +import { useTradingData } from './hooks/useTradingData'; +import takeOrderClick from './helpers/takeOrderClick'; +import useFilteredData from './hooks/useFilteredData'; +import useTradeInit from './hooks/useTradeInit'; +import useMatrixAddresses from './hooks/useMatrixAddresses'; -function BadgeStatus({ type = 'instant', icon }: { type?: 'instant' | 'high'; icon?: boolean }) { - return ( -
- - {!icon && {type === 'instant' ? 'instant' : 'high volume'}} -
- ); -} +const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }]; +const DEFAULT_CHART = CHART_OPTIONS[0]; function Trading() { - const router = useRouter(); - const fetchUser = useUpdateUser(); + const { alertState, alertSubtitle, setAlertState } = useAlert(); + const { elementRef: orderListRef, scrollToElement: scrollToOrdersList } = + useScroll(); + const { elementRef: orderFormRef, scrollToElement: scrollToOrderForm } = + useScroll(); const [ordersHistory, setOrdersHistory] = useState([]); - - const orderFormRef = useRef(null); - const orderListRef = useRef(null); - - const pairId = typeof router.query.id === 'string' ? router.query.id : ''; - - const periods: PeriodState[] = [ - { - name: '1H', - code: '1h', - }, - { - name: '1D', - code: '1d', - }, - { - name: '1W', - code: '1w', - }, - { - name: '1M', - code: '1m', - }, - ]; - - const buySellValues: SelectValue[] = [ - { - name: 'All', - code: 'all', - }, - { - name: 'Buy', - code: 'buy', - }, - { - name: 'Sell', - code: 'sell', - }, - ]; - - const { state, dispatch } = useContext(Store); - const [userOrders, setUserOrders] = useState([]); - const [periodsState, setPeriodsState] = useState(periods[0]); - const [pairData, setPairData] = useState(null); - const [candles, setCandles] = useState([]); - - const [candlesLoaded, setCandlesLoaded] = useState(false); - - const [ordersLoading, setOrdersLoading] = useState(true); - const [trades, setTrades] = useState([]); - const [tradesLoading, setTradesLoading] = useState(true); - 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 [pairStats, setPairStats] = useState(null); - const [applyTips, setApplyTips] = useState([]); + const matrixAddresses = useMatrixAddresses(ordersHistory); - const [alertState, setAlertState] = useState(null); + const { + buyForm, + sellForm, + currencyNames, + firstAssetLink, + secondAssetLink, + secondAssetUsdPrice, + balance, + loggedIn, + pairRateUsd, + } = useTradeInit({ pairData, pairStats }); - const [alertSubtitle, setAlertSubtitle] = useState(''); + const { + fetchTrades, + updateOrders, + updateUserOrders, + candlesLoaded, + ordersLoading, + tradesLoading, + } = useTradingData({ + periodsState, + setApplyTips, + setCandles, + setMyOrdersLoading, + setOrdersHistory, + setPairData, + setPairStats, + setTrades, + setUserOrders, + }); - const [ordersInfoTooltip, setOrdersInfoTooltip] = useState(null); - const ordersInfoRef = useRef(null); - const [infoTooltipPos, setInfoTooltipPos] = useState({ x: 0, y: 0 }); + useSocketListeners({ + setUserOrders, + ordersHistory, + setApplyTips, + setOrdersHistory, + setPairStats, + updateOrders, + }); - async function fetchTrades() { - setTradesLoading(true); - const result = await getTrades(pairId); - - if (result.success) { - setTrades(result.data); - } - - setTradesLoading(false); - } - - useEffect(() => { - (async () => { - await fetchTrades(); - })(); - }, [pairId]); - - const filteredTrades = - tradesType === 'my' - ? trades.filter( - (trade) => - trade.buyer.address === state.wallet?.address || - trade.seller.address === state.wallet?.address, - ) - : trades; - - useEffect(() => { - const targetEl = (event: MouseEvent) => { - if (ordersInfoRef.current && !ordersInfoRef.current.contains(event.target as Node)) { - setOrdersInfoTooltip(null); - } - }; - - window.addEventListener('mousemove', targetEl); - - return () => { - window.removeEventListener('mousemove', targetEl); - }; - }, []); - - const moveInfoTooltip = (event: React.MouseEvent) => { - setInfoTooltipPos({ x: event.clientX, y: event.clientY }); - }; - - const [matrixAddresses, setMatrixAddresses] = useState([]); - - async function updateOrders() { - setOrdersLoading(true); - const result = await getOrdersPage(pairId); - if (!result.success) return; - setOrdersHistory(result?.data || []); - setOrdersLoading(false); - } - - async function socketUpdateOrders() { - const result = await getUserOrdersPage(pairId); - - if (result.success) { - setUserOrders(result?.data?.orders || []); - setApplyTips(result?.data?.applyTips || []); - } - } - - useEffect(() => { - socket.emit('in-trading', { id: router.query.id }); - - return () => { - socket.emit('out-trading', { id: router.query.id }); - }; - }, []); - - useEffect(() => { - socket.on('new-order', async (data) => { - setOrdersHistory([data.orderData, ...ordersHistory]); - await socketUpdateOrders(); - }); - - socket.on('delete-order', async () => { - await updateOrders(); - await socketUpdateOrders(); - }); - - return () => { - socket.off('new-order'); - socket.off('delete-order'); - }; - }, [ordersHistory]); - - useEffect(() => { - function onUpdateStats({ pairStats }: { pairStats: PairStats }) { - setPairStats(pairStats); - } - - socket.on('update-pair-stats', onUpdateStats); - - return () => { - socket.off('update-pair-stats', onUpdateStats); - }; - }, []); - - useEffect(() => { - socket.on('update-orders', async () => { - await socketUpdateOrders(); - }); - - return () => { - socket.off('update-orders'); - }; - }, []); - - // Detect registered addresses - const hasConnection = (address: string) => - matrixAddresses.some( - (item: { address: string; registered: boolean }) => - item.address === address && item.registered, - ); - - const filteredOrdersHistory = ordersHistory - ?.filter((e) => (ordersBuySell.code === 'all' ? e : e.type === ordersBuySell.code)) - ?.filter((e) => e.user.address !== state.wallet?.address) - ?.sort((a, b) => { - if (ordersBuySell.code === 'buy') { - return parseFloat(b.price.toString()) - parseFloat(a.price.toString()); - } - return parseFloat(a.price.toString()) - parseFloat(b.price.toString()); - }); - - // Get registered addresses from matrix - useEffect(() => { - const fetchConnections = async () => { - const filteredAddresses = ordersHistory?.map((e) => e?.user?.address); - if (!filteredAddresses.length) return; - const response = await fetch('https://messenger.zano.org/api/get-addresses', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - addresses: filteredAddresses, - }), + // Take order from trades + const onHandleTakeOrder = useCallback( + ( + event: + | React.MouseEvent + | React.MouseEvent, + e: PageOrderData, + ) => { + takeOrderClick({ + event, + PageOrderData: e, + balance, + buyForm, + pairData, + scrollToOrderForm, + sellForm, }); + }, + [balance, buyForm, pairData, scrollToOrderForm, sellForm], + ); - const data = await response.json(); - setMatrixAddresses(data.addresses); - }; + // Cancel all user orders + const handleCancelAllOrders = useCallback(async () => { + if (!userOrders.length) return; - fetchConnections(); - }, [ordersHistory]); - - const firstCurrencyName = pairData?.first_currency?.name || ''; - const secondCurrencyName = pairData?.second_currency?.name || ''; - - const firstAssetId = pairData ? pairData.first_currency?.asset_id : undefined; - const secondAssetId = pairData ? pairData.second_currency?.asset_id : undefined; - const firstAssetLink = firstAssetId - ? `https://explorer.zano.org/assets?asset_id=${encodeURIComponent(firstAssetId)}` - : undefined; - const secondAssetLink = secondAssetId - ? `https://explorer.zano.org/assets?asset_id=${encodeURIComponent(secondAssetId)}` - : undefined; - - const secondAssetUsdPrice = state.assetsRates.get(secondAssetId || ''); - const secondCurrencyDP = pairData?.second_currency.asset_info?.decimal_point || 12; - - useEffect(() => { - async function fetchPairStats() { - const result = await getPairStats(pairId); - if (!result.success) return; - setPairStats(result.data); - } - - fetchPairStats(); - }, []); - - useEffect(() => { - async function fetchCandles() { - setCandlesLoaded(false); - setCandles([]); - const result = await getCandles(pairId, periodsState.code); - if (result.success) { - setCandles(result.data); - } else { - setCandles([]); - } - setCandlesLoaded(true); - } - - fetchCandles(); - }, [periodsState]); - - useEffect(() => { - async function getPairData() { - const result = await getPair(pairId); - if (!result.success) { - router.push('/404'); - return; - } - setPairData(result.data); - } - - getPairData(); - }, []); - - async function updateUserOrders() { - setMyOrdersLoading(true); - const result = await getUserOrdersPage(pairId); - console.log('result getuserorderspage', result); - await fetchUser(); - - if (!result.success) return; - setUserOrders(result?.data?.orders || []); - setApplyTips(result?.data?.applyTips || []); - setMyOrdersLoading(false); - } - - const loggedIn = !!state.wallet?.connected; - - useEffect(() => { - if (!loggedIn) return; - setUserOrders([]); - updateUserOrders(); - }, [state.wallet?.connected && state.wallet?.address]); - - useEffect(() => { - updateOrders(); - }, []); - - // useEffect(() => { - // socket.on - // }, []); - - const [priceState, setPriceState] = useState(''); - const [amountState, setAmountState] = useState(''); - const [totalState, setTotalState] = useState(''); - - const [priceSellState, setPriceSellState] = useState(''); - const [amountSellState, setAmountSellState] = useState(''); - const [totalSellState, setTotalSellState] = useState(''); - - const [totalUsd, setTotalUsd] = useState(undefined); - const [totalSellUsd, setTotalSellUsd] = useState(undefined); - - const [priceValid, setPriceValid] = useState(false); - const [amountValid, setAmountValid] = useState(false); - const [totalValid, setTotalValid] = useState(false); - const [priceSellValid, setPriceSellValid] = useState(false); - const [amountSellValid, setAmountSellValid] = useState(false); - const [totalSellValid, setTotalSellValid] = useState(false); - - const [buySellState, setBuySellState] = useState(buySellValues[0]); - - const [rangeInputValue, setRangeInputValue] = useState('50'); - const [rangeInputSellValue, setRangeInputSellValue] = useState('50'); - - useEffect(() => { - let totalDecimal: Decimal | undefined; - - try { - totalDecimal = new Decimal(totalState); - } catch (err) { - console.log(err); - } - - const zanoPrice = state.assetsRates.get(pairData?.second_currency?.asset_id || ''); - - setTotalUsd(zanoPrice && totalDecimal ? totalDecimal.mul(zanoPrice).toFixed(2) : undefined); - }, [totalState, state.assetsRates, pairData?.second_currency?.asset_id]); - - useEffect(() => { - let totalSellDecimal: Decimal | undefined; - - try { - totalSellDecimal = new Decimal(totalSellState); - } catch (error) { - console.log(error); - } - - const zanoPrice = state.assetsRates.get(pairData?.second_currency?.asset_id || ''); - - setTotalSellUsd( - zanoPrice && totalSellDecimal ? totalSellDecimal.mul(zanoPrice).toFixed(2) : undefined, - ); - }, [totalSellState, state.assetsRates, pairData?.second_currency?.asset_id]); - - function setPriceFunction(inputValue: string) { - if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { - return; - } - - try { - const value = new Decimal(inputValue || NaN); - - if (value.toString().replace('.', '').length > 18) { - console.log('TOO MANY DECIMALS'); - return; - } - } catch (error) { - console.log(error); - } - - setPriceState(inputValue); - - if (!inputValue) { - setTotalState(''); - setTotalValid(false); - setPriceValid(false); - return; - } - - const valueDecimal = new Decimal(inputValue || NaN); - const amountDecimal = new Decimal(amountState || NaN); - - const validationResult = validateTokensInput(inputValue, secondCurrencyDP); - - if (!validationResult.valid) { - setTotalState(''); - setTotalValid(false); - setPriceValid(false); - return; - } - - setPriceValid(true); - - if (!valueDecimal.isNaN() && !amountDecimal.isNaN() && amountState !== '') { - const totalDecimal = valueDecimal.mul(amountDecimal); - setTotalState(totalDecimal.toString()); - const total = totalDecimal.toFixed(secondCurrencyDP); - - const totalValidationResult = validateTokensInput(total, secondCurrencyDP); - - setTotalValid(totalValidationResult.valid); - } else { - setTotalState(''); - setTotalValid(false); - } - } - - function setPriceSellFunction(inputValue: string) { - if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { - return; - } - - try { - const value = new Decimal(inputValue || NaN); - - if (value.toString().replace('.', '').length > 18) { - console.log('TOO MANY DECIMALS'); - return; - } - } catch (error) { - console.log(error); - } - - setPriceSellState(inputValue); - - if (!inputValue) { - setTotalSellState(''); - setTotalSellValid(false); - setPriceSellValid(false); - return; - } - - const valueDecimal = new Decimal(inputValue || NaN); - const amountDecimal = new Decimal(amountSellState || NaN); - - const secondCurrencyDP = pairData?.second_currency.asset_info?.decimal_point || 12; - - const validationResult = validateTokensInput(inputValue, secondCurrencyDP); - - if (!validationResult.valid) { - setTotalSellState(''); - setTotalSellValid(false); - setPriceSellValid(false); - return; - } - - setPriceSellValid(true); - - if (!valueDecimal.isNaN() && !amountDecimal.isNaN() && amountSellState !== '') { - const total = valueDecimal.mul(amountDecimal).toFixed(); - setTotalSellState(total); - - const totalValidationResult = validateTokensInput(total, secondCurrencyDP); - - setTotalSellValid(totalValidationResult.valid); - } else { - setTotalSellState(''); - setTotalSellValid(false); - } - } - - const assets = state.wallet?.connected ? state.wallet?.assets || [] : []; - - const balance = assets.find((e) => e.ticker === firstCurrencyName)?.balance; - - function setAmountFunction(inputValue: string) { - if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { - return; - } - - try { - const value = new Decimal(inputValue || NaN); - - if (value.toString().replace('.', '').length > 18) { - console.log('TOO MANY DECIMALS'); - return; - } - } catch (error) { - console.log(error); - } - - setAmountState(inputValue); - - if (!inputValue) { - setTotalState(''); - setTotalValid(false); - setAmountValid(false); - return; - } - - const value = new Decimal(inputValue || NaN); - const price = new Decimal(priceState || NaN); - - const validationResult = validateTokensInput( - inputValue, - pairData?.first_currency.asset_info?.decimal_point || 12, - ); - console.log(validationResult); - - if (!validationResult.valid) { - setTotalState(''); - setTotalValid(false); - setAmountValid(false); - return; - } - - setAmountValid(true); - - if (balance) setRangeInputValue(value.div(balance).mul(100).toFixed()); - - if (!price.isNaN() && !value.isNaN() && priceState !== '') { - const totalDecimal = value.mul(price); - setTotalState(totalDecimal.toString()); - const total = totalDecimal.toFixed(secondCurrencyDP); - const totalValidationResult = validateTokensInput(total, secondCurrencyDP); - - setTotalValid(totalValidationResult.valid); - } else { - setTotalState(''); - setTotalValid(false); - } - } - - function setAmountSellFunction(inputValue: string) { - if (inputValue !== '' && !isPositiveFloatStr(inputValue)) { - return; - } - - try { - const value = new Decimal(inputValue || NaN); - - if (value.toString().replace('.', '').length > 18) { - console.log('TOO MANY DECIMALS'); - return; - } - } catch (error) { - console.log(error); - } - - setAmountSellState(inputValue); - - if (!inputValue) { - setTotalSellState(''); - setTotalSellValid(false); - setAmountSellValid(false); - return; - } - - const value = new Decimal(inputValue || NaN); - const price = new Decimal(priceSellState || NaN); - - const validationResult = validateTokensInput( - inputValue, - pairData?.first_currency.asset_info?.decimal_point || 12, - ); - console.log(validationResult); - - if (!validationResult.valid) { - setTotalSellState(''); - setTotalSellValid(false); - setAmountSellValid(false); - return; - } - - setAmountSellValid(true); - - if (balance) setRangeInputSellValue(value.div(balance).mul(100).toFixed()); - - if (!price.isNaN() && !value.isNaN() && priceSellState !== '') { - const total = value.mul(price).toFixed(); - setTotalSellState(total); - - const totalValidationResult = validateTokensInput( - total, - pairData?.second_currency.asset_info?.decimal_point || 12, - ); - - setTotalSellValid(totalValidationResult.valid); - } else { - setTotalSellState(''); - setTotalSellValid(false); - } - } - - function setCorrespondingOrder(price: number, amount: number) { - const priceDecimal = new Decimal(price || 0); - const amountDecimal = new Decimal(amount || 0); - const totalDecimal = priceDecimal.mul(amountDecimal); - - setPriceFunction(notationToString(priceDecimal.toString()) || ''); - setAmountFunction(notationToString(amountDecimal.toString()) || ''); - setTotalState(notationToString(totalDecimal.toString()) || ''); - - if (balance) { - const balanceDecimal = new Decimal(balance); - - const percentageDecimal = amountDecimal.div(balanceDecimal).mul(100); - setRangeInputValue(percentageDecimal.toFixed() || ''); - } - - const total = priceDecimal.mul(amountDecimal).toFixed(secondCurrencyDP); - - const totalValidationResult = validateTokensInput( - total, - pairData?.second_currency.asset_info?.decimal_point || 12, - ); - - setTotalValid(totalValidationResult.valid); - } - - function setCorrespondingSellOrder(price: number, amount: number) { - const priceDecimal = new Decimal(price || 0); - const amountDecimal = new Decimal(amount || 0); - const totalDecimal = priceDecimal.mul(amountDecimal); - - setPriceSellFunction(notationToString(priceDecimal.toString()) || ''); - setAmountSellFunction(notationToString(amountDecimal.toString()) || ''); - setTotalSellState(notationToString(totalDecimal.toString()) || ''); - - if (balance) { - const balanceDecimal = new Decimal(balance); - - const percentageDecimal = amountDecimal.div(balanceDecimal).mul(100); - setRangeInputSellValue(percentageDecimal.toFixed() || ''); - } - - const total = priceDecimal.mul(amountDecimal).toFixed(); - - const totalValidationResult = validateTokensInput( - total, - pairData?.second_currency.asset_info?.decimal_point || 12, - ); - - setTotalSellValid(totalValidationResult.valid); - } - - function StatItem(props: StatItemProps) { - const { Img } = props; - - return ( -
-
- -

{props.title}

-
-
-

{props.value}

- {props.coefficient !== undefined && ( -

= 0 - ? styles.coefficient__green - : styles.coefficient__red, - )} - > - {props.coefficient >= 0 ? '+' : ''} - {props.coefficient?.toFixed(2)}% -

- )} -
-
- ); - } - - function takeOrderClick( - event: - | React.MouseEvent - | React.MouseEvent, - e: PageOrderData, - ) { - event.preventDefault(); - - if (e.type === 'buy') { - setCorrespondingOrder(e.price, e.amount); - setBuySellState(buySellValues[1]); - } else { - setCorrespondingSellOrder(e.price, e.amount); - setBuySellState(buySellValues[2]); - } - - if (!orderFormRef.current) return; - - orderFormRef.current.scrollIntoView({ behavior: 'smooth' }); - } - - //* * FOR USAGE IN THIS PAGE TABLES ONLY */ - function OrderRowTooltipCell({ - style, - children, - sideText, - sideTextColor, - noTooltip, - }: { - style?: React.CSSProperties; - children: string | React.ReactNode; - sideText?: string; - sideTextColor?: string; - noTooltip?: boolean; - }) { - const [showTooltip, setShowTooltip] = useState(false); - - const tooltipText = `${children}${sideText ? ` ~${sideText}` : ''}`; - - const isLongContent = tooltipText.length > 14; - - return ( -
- ); - } - - function MatrixConnectionBadge({ - userAdress, - userAlias, - }: { - userAdress?: string; - userAlias?: string; - }) { - const [connectionTooltip, setConnectionTooltip] = useState(false); - return userAdress && hasConnection(userAdress) ? ( - <> - setConnectionTooltip(true)} - onMouseLeave={() => setConnectionTooltip(false)} - style={{ marginTop: '4px', cursor: 'pointer', position: 'relative' }} - > - - - - Matrix connection - - - ) : ( - <> - ); - } - - function OrdersRow(props: { orderData: PageOrderData; percentage: number }) { - const e = props?.orderData || {}; - const percentage = props?.percentage; - - const totalDecimal = new Decimal(e.left).mul(new Decimal(e.price)); - - return ( - setOrdersInfoTooltip(e)} - onClick={(event) => takeOrderClick(event, e)} - className={e.type === 'sell' ? styles.sell_section : ''} - style={{ '--line-width': `${percentage}%` } as React.CSSProperties} - key={nanoid(16)} - > - - {notationToString(e.price)} - - {notationToString(e.amount)} - {/* {notationToString(e.left)} */} - - {notationToString(totalDecimal.toString())} - - - ); - } - - function MyOrdersRow(props: { orderData: OrderRow }) { - const e = props?.orderData || {}; - 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(); - - setCancellingState(true); - const result = await cancelOrder(e.id); - - setCancellingState(false); - - if (!result.success) { - setAlertState('error'); - setAlertSubtitle('Error while cancelling order'); - setTimeout(() => { - setAlertState(null); - setAlertSubtitle(''); - }, 3000); - return; - } - - await updateOrders(); - await updateUserOrders(); - await fetchUser(); - } - - return ( - - - - {notationToString(e.price)} - - {notationToString(e.amount)} - - - {notationToString(totalDecimal.toString())}{' '} - ~ ${totalValue && formatDollarValue(totalValue)} - - - {/* */} - - - - - ); - } - - function MyOrdersApplyRow(props: { orderData: ApplyTip }) { - const e = props?.orderData || {}; - - 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 ( - - - - - {notationToString(e.price)} - - {notationToString(e.left)} - - - {notationToString(totalDecimal.toString())}{' '} - ~ ${totalValue && formatDollarValue(totalValue)} - - - - - - ); - } - - const imgCode = - pairData && tradingKnownCurrencies.includes(pairData.first_currency?.code) - ? pairData.first_currency?.code - : 'tsds'; - - const imgCode2 = - pairData && tradingKnownCurrencies.includes(pairData.second_currency?.code) - ? pairData.second_currency?.code - : 'tsds'; - - const coefficient = pairStats?.coefficient || 0; - const coefficientOutput = - parseFloat(coefficient?.toFixed(2) || '0') === -100 - ? -99.99 - : parseFloat(coefficient?.toFixed(2) || '0'); - - const pairRateUsd = - pairStats?.rate !== undefined && secondAssetUsdPrice !== undefined - ? new Decimal(pairStats.rate) - .mul(secondAssetUsdPrice) - .toFixed(pairStats.rate < 0.1 ? 6 : 2) - : undefined; - - const scrollToOrderList = () => { - if (!orderListRef.current) return; - - orderListRef.current.scrollIntoView({ behavior: 'smooth' }); - }; - - const handleCancelAllOrders = async () => { setMyOrdersLoading(true); try { @@ -1234,295 +130,43 @@ function Trading() { } finally { setMyOrdersLoading(false); } - }; + }, [userOrders, updateUserOrders]); + + const { filteredOrdersHistory, filteredTrades } = useFilteredData({ + ordersBuySell, + ordersHistory, + trades, + tradesType, + }); return ( <>
-
-
-
-
-
- -
-
-

- {!( - pairData && - pairData.first_currency?.name && - pairData.second_currency?.name - ) ? ( - '...' - ) : ( - <> - {firstCurrencyName} - /{secondCurrencyName} - - )} -

-
-

- {notationToString(pairStats?.rate || 0)}{' '} - {secondCurrencyName} -

- {pairRateUsd && ( -

- ~ ${pairRateUsd} -

- )} -
-
-
-
-
- {pairData && firstAssetLink && secondAssetLink && ( -
-
-

- {' '} - {firstCurrencyName}: -

- - {shortenAddress(firstAssetId || '')} - -
-
-

- {' '} - {secondCurrencyName}: -

- - {shortenAddress(firstAssetId || '')} - -
-
- )} - - - - -
+
+ - -
+
+ -
-
-
-
- Orders pool -
- -
- - - - - -
-
- -
-
-

setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - > - {children} - {sideText && ( - - {sideText} - - )} -

- {isLongContent && !noTooltip && ( - - {tooltipText} - - )} -
-

setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - > - @ - {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} - - )} - {/* High volume */} - {/* */} -
{localeTimeLeft(now, parseInt(e.expiration_timestamp, 10))} -

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

-
- - {cancellingState ? 'Process' : 'Cancel'} - -
-

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

- -
- )} -

- - {(e.isInstant || e.transaction) && } - - {e.user?.alias.length > 12 && ( - - {e.user?.alias} - - )} -
- - {applyingState ? 'Process' : 'Apply'} - -
- - - - - - - - - {!ordersLoading && !!filteredOrdersHistory.length && ( - setOrdersInfoTooltip(null)} - className="orders-scroll" - > - {filteredOrdersHistory?.map((e) => { - const maxValue = Math.max( - ...filteredOrdersHistory.map((order) => - parseFloat(String(order.left)), - ), - ); - const percentage = ( - (parseFloat(String(e.left)) / maxValue) * - 100 - ).toFixed(2); - - return ( - - ); - })} - - )} -
Price ({secondCurrencyName})Amount ({firstCurrencyName})Total ({secondCurrencyName})
- - {!filteredOrdersHistory.length && !ordersLoading && ( -
- -
No orders
-
- )} - {ordersLoading && ( - - )} - - {ordersInfoTooltip && - (() => { - const totalDecimal = new Decimal(ordersInfoTooltip?.left).mul( - new Decimal(ordersInfoTooltip?.price), - ); - const totalValue = secondAssetUsdPrice - ? totalDecimal.mul(secondAssetUsdPrice).toFixed(2) - : undefined; - - return ( - -
-
Alias
-

- @ - {cutAddress( - ordersInfoTooltip?.user?.alias || - 'no alias', - 12, - )}{' '} - {ordersInfoTooltip?.isInstant && ( - - )} -

- -
Price ({secondCurrencyName})
-

- {notationToString(ordersInfoTooltip?.price)} -

- - ~ - {secondAssetUsdPrice && - ordersInfoTooltip?.price !== undefined - ? (() => { - const total = - secondAssetUsdPrice * - ordersInfoTooltip.price; - const formatted = - ordersInfoTooltip.price < 0.9 - ? `$${total.toFixed(5)}` - : `$${total.toFixed(2)}`; - return formatted; - })() - : 'undefined'} - - -
Amount ({firstCurrencyName})
-

{notationToString(ordersInfoTooltip?.amount)}

- -
Total ({secondCurrencyName})
-

{notationToString(totalDecimal.toString())}

- - ~{' '} - {totalValue - ? `$${formatDollarValue(totalValue)}` - : 'undefined'} - -
-
- ); - })()} -
-
- -
-
+
+
undefined} />
@@ -1544,241 +185,64 @@ function Trading() { {candlesLoaded ? ( ) : ( - + )}
-
-
- - - -
- -
- - - - - - - - - - {!tradesLoading && !!filteredTrades.length && ( - - {filteredTrades.map((trade) => ( - - - - - - ))} - - )} -
Price ({secondCurrencyName})Amount ({firstCurrencyName})Time
-

- {trade.price} -

-
-

{trade.amount}

-
-

{formatTime(trade.timestamp)}

-
- - {!filteredTrades.length && !tradesLoading && ( -
- -
No trades
-
- )} - - {tradesLoading && ( - - )} -
-
+
-
-
-
- - - -
- -
- -
-
- -
- - - - - - - - - - - -
Alias - Price
({secondCurrencyName}) -
- Amount
({firstCurrencyName}) -
- Total
({secondCurrencyName}) -
Offers
- - {!myOrdersLoading && loggedIn && !!userOrders.length && ( -
- - - {userOrders.map((e) => ( - - ))} - -
- {!!applyTips.length && ( - - - {applyTips.map((e) => ( - - ))} - -
- )} -
- )} - - {myOrdersLoading && loggedIn && ( - - )} - - {!loggedIn && ( -
- -
Connect wallet to see your orders
-
- )} - - {loggedIn && !userOrders.length && !myOrdersLoading && ( -
- -
No orders
-
- )} -
-
+
-
- -
+ {['buy', 'sell'].map((type) => { + const isBuy = type === 'buy'; + const form = isBuy ? buyForm : sellForm; -
- -
+ return ( + + ); + })}
diff --git a/src/pages/dex/trading/components/AllTrades/index.tsx b/src/pages/dex/trading/components/AllTrades/index.tsx new file mode 100644 index 0000000..8e9a3cb --- /dev/null +++ b/src/pages/dex/trading/components/AllTrades/index.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { classes, formatTime } from '@/utils/utils'; +import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; +import EmptyMessage from '@/components/UI/EmptyMessage'; +import styles from './styles.module.scss'; +import { AllTradesProps } from './types'; + +const AllTrades = ({ + setTradesType, + tradesType, + filteredTrades, + tradesLoading, + currencyNames, +}: AllTradesProps) => { + return ( +
+
+ + + +
+ +
+ + + + + + + + + + {!tradesLoading && !!filteredTrades.length && ( + + {filteredTrades.map((trade) => ( + + + + + + ))} + + )} +
Price ({currencyNames.secondCurrencyName})Amount ({currencyNames.firstCurrencyName})Time
+

+ {trade.price} +

+
+

{trade.amount}

+
+

{formatTime(trade.timestamp)}

+
+ + {!filteredTrades.length && !tradesLoading && } + + {tradesLoading && } +
+
+ ); +}; + +export default AllTrades; diff --git a/src/pages/dex/trading/components/AllTrades/styles.module.scss b/src/pages/dex/trading/components/AllTrades/styles.module.scss new file mode 100644 index 0000000..aae3f21 --- /dev/null +++ b/src/pages/dex/trading/components/AllTrades/styles.module.scss @@ -0,0 +1,116 @@ +.allTrades { + max-width: 415px; + width: 100%; + padding: 5px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + + @media screen and (max-width: 1480px) { + max-width: 340px; + } + + &__header { + border-bottom: 1px solid var(--delimiter-color); + display: flex; + align-items: center; + gap: 22px; + padding: 10px; + padding-bottom: 0; + + &_btn { + padding-bottom: 7px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + font-size: 16px; + border-bottom: 2px solid transparent; + font-weight: 600; + background-color: transparent !important; + cursor: pointer; + + &.active { + border-color: #1f8feb; + } + + &:hover { + color: #1f8feb; + } + } + } + + &__content { + display: flex; + flex-direction: column; + padding-top: 10px; + + table { + width: 100%; + + thead { + display: flex; + width: 100%; + padding-inline: 10px; + margin-bottom: 9px; + + tr { + width: 100%; + display: flex; + justify-content: space-between; + + th { + min-width: 80px; + font-size: 11px; + font-weight: 700; + text-align: start; + color: var(--table-th-color); + + &:last-child { + text-align: right; + } + } + } + } + + tbody { + height: 29dvh; + min-height: 265px; + max-height: 380px; + display: flex; + flex-direction: column; + overflow: auto; + padding: 10px; + padding-bottom: 20px; + + tr { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 4px 0; + + &:nth-child(even) { + background-color: var(--table-even-bg); + } + + td { + position: relative; + + &:last-child { + > p { + text-align: right; + } + } + + > p { + min-width: 80px; + width: 100%; + font-size: 12px; + font-weight: 400; + } + } + } + } + } + } +} diff --git a/src/pages/dex/trading/components/AllTrades/types.ts b/src/pages/dex/trading/components/AllTrades/types.ts new file mode 100644 index 0000000..1fb3096 --- /dev/null +++ b/src/pages/dex/trading/components/AllTrades/types.ts @@ -0,0 +1,15 @@ +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; +import { Dispatch, SetStateAction } from 'react'; + +type tradeType = 'all' | 'my'; + +export interface AllTradesProps { + setTradesType: Dispatch>; + tradesType: tradeType; + filteredTrades: Trade[]; + tradesLoading: boolean; + currencyNames: { + firstCurrencyName: string; + secondCurrencyName: string; + }; +} diff --git a/src/pages/dex/trading/components/BadgeStatus/index.tsx b/src/pages/dex/trading/components/BadgeStatus/index.tsx new file mode 100644 index 0000000..d2690e0 --- /dev/null +++ b/src/pages/dex/trading/components/BadgeStatus/index.tsx @@ -0,0 +1,26 @@ +import { classes } from '@/utils/utils'; +import React from 'react'; +import Image from 'next/image'; +import LightningImg from '@/assets/images/UI/lightning.png'; +import RocketImg from '@/assets/images/UI/rocket.png'; +import styles from './styles.module.scss'; +import { BadgeStatusProps } from './types'; + +function BadgeStatus({ type, icon }: BadgeStatusProps) { + return ( +
+ badge image + {!icon && ( + + {type === 'instant' ? 'instant' : 'high volume'} + + )} +
+ ); +} + +export default BadgeStatus; diff --git a/src/pages/dex/trading/components/BadgeStatus/styles.module.scss b/src/pages/dex/trading/components/BadgeStatus/styles.module.scss new file mode 100644 index 0000000..03d821e --- /dev/null +++ b/src/pages/dex/trading/components/BadgeStatus/styles.module.scss @@ -0,0 +1,39 @@ +.badge { + width: fit-content; + padding: 1px 4px; + padding-right: 8px; + display: flex; + align-items: center; + gap: 2px; + border-radius: 100px; + background: radial-gradient(100% 246.57% at 0% 0%, #a366ff 0%, #601fff 100%); + + &.high { + padding: 2px 4px; + background: radial-gradient(100% 188.88% at 0% 0%, #16d1d6 0%, #274cff 100%); + } + + &.icon { + min-width: 15px; + height: 15px; + border-radius: 50%; + justify-content: center; + padding: 0; + + .badge__img { + width: 11px; + height: 11px; + } + } + + &__img { + height: 13px; + width: auto; + } + + &__text { + font-size: 10px; + font-weight: 600; + color: #fff; + } +} diff --git a/src/pages/dex/trading/components/BadgeStatus/types.ts b/src/pages/dex/trading/components/BadgeStatus/types.ts new file mode 100644 index 0000000..a375313 --- /dev/null +++ b/src/pages/dex/trading/components/BadgeStatus/types.ts @@ -0,0 +1,4 @@ +export interface BadgeStatusProps { + type?: 'instant' | 'high'; + icon?: boolean; +} diff --git a/src/pages/dex/trading/CandleChart/CandleChart.tsx b/src/pages/dex/trading/components/CandleChart/index.tsx similarity index 96% rename from src/pages/dex/trading/CandleChart/CandleChart.tsx rename to src/pages/dex/trading/components/CandleChart/index.tsx index c8dccc0..d9f19ef 100644 --- a/src/pages/dex/trading/CandleChart/CandleChart.tsx +++ b/src/pages/dex/trading/components/CandleChart/index.tsx @@ -6,7 +6,7 @@ import Decimal from 'decimal.js'; import * as echarts from 'echarts'; import CandleRow from '@/interfaces/common/CandleRow'; import testCandles from './testCandles.json'; -import styles from './CandleChart.module.scss'; +import styles from './styles.module.scss'; const TESTING_MODE = false; @@ -214,7 +214,7 @@ function CandleChart(props: CandleChartProps) { console.log('option', option); return ( -
+
- {!candles?.length && isLoaded &&

[ Low volume ]

} + {!candles?.length && isLoaded && ( +

[ Low volume ]

+ )}
); } diff --git a/src/pages/dex/trading/CandleChart/CandleChart.module.scss b/src/pages/dex/trading/components/CandleChart/styles.module.scss similarity index 93% rename from src/pages/dex/trading/CandleChart/CandleChart.module.scss rename to src/pages/dex/trading/components/CandleChart/styles.module.scss index 447d07f..b2e1950 100644 --- a/src/pages/dex/trading/CandleChart/CandleChart.module.scss +++ b/src/pages/dex/trading/components/CandleChart/styles.module.scss @@ -1,4 +1,4 @@ -.candle__chart__wrapper { +.chart { position: relative; width: auto; height: auto; @@ -12,7 +12,7 @@ cursor: crosshair; } - h1 { + &__lowVolume { font-size: 72px; color: var(--font-dimmed-color); white-space: nowrap; diff --git a/src/pages/dex/trading/CandleChart/testCandles.json b/src/pages/dex/trading/components/CandleChart/testCandles.json similarity index 100% rename from src/pages/dex/trading/CandleChart/testCandles.json rename to src/pages/dex/trading/components/CandleChart/testCandles.json diff --git a/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/index.tsx b/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/index.tsx new file mode 100644 index 0000000..5c0cb27 --- /dev/null +++ b/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/index.tsx @@ -0,0 +1,52 @@ +import LabeledInputProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps'; +import { classes, formatDollarValue } from '@/utils/utils'; +import { useRef } from 'react'; +import Input from '@/components/UI/Input/Input'; +import styles from './styles.module.scss'; + +function LabeledInput(props: LabeledInputProps) { + const labelRef = useRef(null); + const { + label = '', + placeholder = '', + currency = '', + value, + readonly, + usd, + setValue, + invalid, + } = props; + + const handleInput = (e: React.FormEvent) => { + if (!readonly && setValue) { + setValue(e.currentTarget.value); + } + }; + + return ( +
+
{label}
+
+ + + {usd && ( +
+

~${formatDollarValue(usd)}

+
+ )} + +
+

{currency}

+
+
+
+ ); +} + +export default LabeledInput; diff --git a/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/styles.module.scss b/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/styles.module.scss new file mode 100644 index 0000000..80c2834 --- /dev/null +++ b/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/styles.module.scss @@ -0,0 +1,62 @@ +.labeledInput { + display: flex; + flex-direction: column; + gap: 8px; + + &__label { + font-size: 11px; + font-family: 700; + color: var(--table-th-color); + } + + &__wrapper { + width: 100%; + position: relative; + background-color: var(--bordered-input-bg); + border: 1px solid var(--window-border-color); + border-radius: 8px; + display: flex; + overflow: hidden; + + &.invalid { + border-color: #ff6767; + } + + input { + width: 100%; + padding: 13px 15px; + background-color: transparent; + border: none; + font-size: 16px; + font-weight: 400; + } + } + + &__value { + padding-right: 10px; + display: flex; + align-items: center; + + > p { + color: var(--table-th-color); + font-size: 12px; + font-weight: 400; + } + } + + &__currency { + min-width: 82px; + max-width: 150px; + padding: 0 15px; + background-color: var(--dex-input-currency); + display: flex; + align-items: center; + justify-content: center; + + > p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } +} diff --git a/src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx b/src/pages/dex/trading/components/InputPanelItem/index.tsx similarity index 65% rename from src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx rename to src/pages/dex/trading/components/InputPanelItem/index.tsx index 44d2eb2..1176bc4 100644 --- a/src/pages/dex/trading/InputPanelItem/InputPanelItem.tsx +++ b/src/pages/dex/trading/components/InputPanelItem/index.tsx @@ -1,26 +1,22 @@ import { Store } from '@/store/store-reducer'; import { createOrder } from '@/utils/methods'; -import { ChangeEvent, useContext, useRef, useState } from 'react'; -import Input from '@/components/UI/Input/Input'; +import { ChangeEvent, useContext, useState } from 'react'; import RangeInput from '@/components/UI/RangeInput/RangeInput'; import ConnectButton from '@/components/UI/ConnectButton/ConnectButton'; import Button from '@/components/UI/Button/Button'; import { useRouter } from 'next/router'; -import { classes, formatDollarValue } from '@/utils/utils'; +import { classes } from '@/utils/utils'; import InputPanelItemProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps'; -import LabeledInputProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps'; import CreateOrderData from '@/interfaces/fetch-data/create-order/CreateOrderData'; import Decimal from 'decimal.js'; import Alert from '@/components/UI/Alert/Alert'; import infoIcon from '@/assets/images/UI/info_alert_icon.svg'; import Image from 'next/image'; -import styles from './InputPanelItem.module.scss'; +import { useAlert } from '@/hook/useAlert'; +import styles from './styles.module.scss'; +import LabeledInput from './components/LabeledInput'; function InputPanelItem(props: InputPanelItemProps) { - const { state } = useContext(Store); - - const router = useRouter(); - const { priceState = '', amountState = '', @@ -29,71 +25,32 @@ function InputPanelItem(props: InputPanelItemProps) { buySellState = buySellValues[0], setPriceFunction, setAmountFunction, - setAlertState, - setAlertSubtitle, setRangeInputValue, rangeInputValue = '50', - firstCurrencyName = '', - secondCurrencyName = '', balance = 0, amountValid, priceValid, totalValid, totalUsd, scrollToOrderList, + currencyNames, } = props; + const { state } = useContext(Store); + const router = useRouter(); + const { setAlertState, setAlertSubtitle } = useAlert(); const [creatingState, setCreatingState] = useState(false); + const { firstCurrencyName, secondCurrencyName } = currencyNames; const [hasImmediateMatch, setHasImmediateMatch] = useState(false); - - function LabeledInput(props: LabeledInputProps) { - const labelRef = useRef(null); - const { - label = '', - placeholder = '', - currency = '', - value, - readonly, - usd, - setValue, - invalid, - } = props; - - const handleInput = (e: React.FormEvent) => { - if (!readonly && setValue) { - setValue(e.currentTarget.value); - } - }; - - return ( -
-
{label}
-
- - {usd && ( -
-

~${formatDollarValue(usd)}

-
- )} -
-

{currency}

-
-
-
- ); - } - const isBuy = buySellState?.code === 'buy'; + function resetForm() { + setPriceFunction(''); + setAmountFunction(''); + setRangeInputValue('50'); + } + async function postOrder() { const price = new Decimal(priceState); const amount = new Decimal(amountState); @@ -122,6 +79,8 @@ function InputPanelItem(props: InputPanelItemProps) { if (result.data?.immediateMatch) { setHasImmediateMatch(true); } + + resetForm(); } else { setAlertState('error'); if (result.data === 'Same order') { @@ -139,35 +98,30 @@ function InputPanelItem(props: InputPanelItemProps) { function onRangeInput(e: ChangeEvent) { setRangeInputValue(e.target.value); - if (balance) { + if (balance > 0) { const rangeValue = new Decimal(e.target.value || '0'); - const balanceDecimal = new Decimal(balance || '0'); + const balanceDecimal = new Decimal(balance); const calculatedAmount = balanceDecimal.mul(rangeValue.div(100)).toString(); setAmountFunction(calculatedAmount || ''); } } - let buttonText; - - if (creatingState) { - buttonText = 'Creating...'; - } else { - buttonText = 'Create Order'; - } + const buttonText = creatingState ? 'Creating...' : 'Create Order'; + const isButtonDisabled = !priceValid || !amountValid || !totalValid || creatingState; return ( -
+
{hasImmediateMatch && ( +
success -
+

Apply the order

You have to apply the order

) : ( - + )}
diff --git a/src/pages/dex/trading/components/InputPanelItem/styles.module.scss b/src/pages/dex/trading/components/InputPanelItem/styles.module.scss new file mode 100644 index 0000000..bb00386 --- /dev/null +++ b/src/pages/dex/trading/components/InputPanelItem/styles.module.scss @@ -0,0 +1,94 @@ +.inputPanel { + width: 100%; + padding: 15px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + margin-bottom: 10px; + + .title { + font-size: 16px; + font-weight: 600; + } + } + + &__fees { + color: var(--table-th-color); + font-weight: 400; + font-size: 12px; + + span { + font-weight: 400; + font-size: 12px; + } + } + + &__body { + display: flex; + flex-direction: column; + gap: 10px; + + &_btn { + margin-top: 10px; + + &.buy { + background-color: #16d1d6; + + &:hover { + background-color: #45dade; + } + } + + &.sell { + background-color: #ff6767; + + &:hover { + background-color: #ff8585; + } + } + } + } +} + +.applyAlert { + display: flex; + gap: 20px; + align-items: center; + + &__content { + display: flex; + flex-direction: column; + gap: 10px; + } + + &__button { + max-width: 125px; + background-color: var(--alert-btn-bg); + color: #1f8feb; + padding: 7px 32px; + font-size: 12px; + font-weight: 500; + + &:hover { + background-color: var(--alert-btn-hover); + } + } + + h2 { + font-size: 16px; + font-weight: 600; + } + + p { + font-size: 14px; + opacity: 0.7; + margin-bottom: 5px; + } +} diff --git a/src/pages/dex/trading/components/MatrixConnectionBadge/index.tsx b/src/pages/dex/trading/components/MatrixConnectionBadge/index.tsx new file mode 100644 index 0000000..4839242 --- /dev/null +++ b/src/pages/dex/trading/components/MatrixConnectionBadge/index.tsx @@ -0,0 +1,41 @@ +import Tooltip from '@/components/UI/Tooltip/Tooltip'; +import { useState } from 'react'; +import { ReactComponent as ConnectionIcon } from '@/assets/images/UI/connection.svg'; +import styles from './styles.module.scss'; +import { MatrixConnectionBadgeProps } from './types'; + +function MatrixConnectionBadge({ + userAdress, + userAlias, + matrixAddresses, +}: MatrixConnectionBadgeProps) { + const hasConnection = (address: string) => + matrixAddresses.some((item) => item.address === address && item.registered); + + const [connectionTooltip, setConnectionTooltip] = useState(false); + return userAdress && hasConnection(userAdress) ? ( + + ) : ( + <> + ); +} + +export default MatrixConnectionBadge; diff --git a/src/pages/dex/trading/components/MatrixConnectionBadge/styles.module.scss b/src/pages/dex/trading/components/MatrixConnectionBadge/styles.module.scss new file mode 100644 index 0000000..9d64826 --- /dev/null +++ b/src/pages/dex/trading/components/MatrixConnectionBadge/styles.module.scss @@ -0,0 +1,28 @@ +.badge { + position: relative; + + &__link { + margin-top: 4px; + cursor: pointer; + } + + &__tooltip { + position: absolute; + top: 30px; + left: 50%; + transform: translateX(-50%); + background-color: var(--trade-table-tooltip); + font-size: 12px; + z-index: 9999; + + &_text { + font-size: 12px !important; + } + + &_arrow { + border-radius: 2px; + left: 50%; + background-color: var(--trade-table-tooltip) !important; + } + } +} diff --git a/src/pages/dex/trading/components/MatrixConnectionBadge/types.ts b/src/pages/dex/trading/components/MatrixConnectionBadge/types.ts new file mode 100644 index 0000000..08f8141 --- /dev/null +++ b/src/pages/dex/trading/components/MatrixConnectionBadge/types.ts @@ -0,0 +1,7 @@ +import MatrixAddress from '@/interfaces/common/MatrixAddress'; + +export interface MatrixConnectionBadgeProps { + userAdress?: string; + userAlias?: string; + matrixAddresses: MatrixAddress[]; +} diff --git a/src/pages/dex/trading/components/OrderRowTooltipCell/index.tsx b/src/pages/dex/trading/components/OrderRowTooltipCell/index.tsx new file mode 100644 index 0000000..b584236 --- /dev/null +++ b/src/pages/dex/trading/components/OrderRowTooltipCell/index.tsx @@ -0,0 +1,52 @@ +import Tooltip from '@/components/UI/Tooltip/Tooltip'; +import { useState } from 'react'; +import styles from './styles.module.scss'; +import { OrderRowTooltipCellProps } from './types'; + +function OrderRowTooltipCell({ + style, + children, + sideText, + sideTextColor, + noTooltip, +}: OrderRowTooltipCellProps) { + const [showTooltip, setShowTooltip] = useState(false); + + const tooltipText = `${children}${sideText ? ` ~${sideText}` : ''}`; + + const isLongContent = tooltipText.length > 14; + + return ( + +

setShowTooltip(true)} + onMouseLeave={() => setShowTooltip(false)} + > + {children} + {sideText && ( + + {sideText} + + )} +

+ {isLongContent && !noTooltip && ( + + {tooltipText} + + )} + + ); +} + +export default OrderRowTooltipCell; diff --git a/src/pages/dex/trading/components/OrderRowTooltipCell/styles.module.scss b/src/pages/dex/trading/components/OrderRowTooltipCell/styles.module.scss new file mode 100644 index 0000000..099fc8b --- /dev/null +++ b/src/pages/dex/trading/components/OrderRowTooltipCell/styles.module.scss @@ -0,0 +1,28 @@ +.row { + position: relative; + + &:last-child { + > p { + text-align: right; + } + } + + > p { + min-width: 80px; + width: 100%; + font-size: 12px; + font-weight: 400; + } +} + +.tooltip { + position: absolute; + top: 30px; + left: 20%; + transform: translateX(-50%); + background-color: var(--trade-table-tooltip); + + &__arrow { + background-color: var(--trade-table-tooltip); + } +} diff --git a/src/pages/dex/trading/components/OrderRowTooltipCell/types.ts b/src/pages/dex/trading/components/OrderRowTooltipCell/types.ts new file mode 100644 index 0000000..965bc02 --- /dev/null +++ b/src/pages/dex/trading/components/OrderRowTooltipCell/types.ts @@ -0,0 +1,9 @@ +import { ReactNode } from 'react'; + +export interface OrderRowTooltipCellProps { + style?: React.CSSProperties; + children: string | ReactNode; + sideText?: string; + sideTextColor?: string; + noTooltip?: boolean; +} diff --git a/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/index.tsx b/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/index.tsx new file mode 100644 index 0000000..a4c3eb7 --- /dev/null +++ b/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/index.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { classes, notationToString } from '@/utils/utils'; +import { nanoid } from 'nanoid'; +import Decimal from 'decimal.js'; +import styles from './styles.module.scss'; +import { OrdersRowProps } from './types'; +import OrderRowTooltipCell from '../../../OrderRowTooltipCell'; + +function OrdersRow({ + orderData, + percentage, + takeOrderClick, + setOrdersInfoTooltip, +}: OrdersRowProps) { + const e = orderData || {}; + + const totalDecimal = new Decimal(e.left).mul(new Decimal(e.price)); + + return ( + setOrdersInfoTooltip(e)} + onClick={(event) => takeOrderClick(event, e)} + className={classes(styles.row, e.type === 'sell' && styles.sell_section)} + style={{ '--line-width': `${percentage}%` } as React.CSSProperties} + key={nanoid(16)} + > + + {notationToString(e.price)} + + {notationToString(e.amount)} + + {notationToString(totalDecimal.toString())} + + + ); +} + +export default OrdersRow; diff --git a/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/styles.module.scss b/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/styles.module.scss new file mode 100644 index 0000000..34a925f --- /dev/null +++ b/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/styles.module.scss @@ -0,0 +1,48 @@ +.row { + cursor: pointer; + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 4px 0; + + &:nth-child(even) { + background-color: var(--table-even-bg); + } + + &::after { + content: ''; + pointer-events: none; + position: absolute; + z-index: 1; + right: 0; + top: 0; + width: var(--line-width, 0%); + height: 100%; + background: #16d1d61a; + } + + &.sell_section { + &::after { + background: #ff67671a; + } + } + + td { + position: relative; + + &:last-child { + > p { + text-align: right; + } + } + + > p { + min-width: 80px; + width: 100%; + font-size: 12px; + font-weight: 400; + } + } +} diff --git a/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/types.ts b/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/types.ts new file mode 100644 index 0000000..0f7ea0a --- /dev/null +++ b/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/types.ts @@ -0,0 +1,14 @@ +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { Dispatch, SetStateAction } from 'react'; + +export interface OrdersRowProps { + orderData: PageOrderData; + percentage: number; + takeOrderClick: ( + _event: + | React.MouseEvent + | React.MouseEvent, + _e: PageOrderData, + ) => void; + setOrdersInfoTooltip: Dispatch>; +} diff --git a/src/pages/dex/trading/components/OrdersPool/index.tsx b/src/pages/dex/trading/components/OrdersPool/index.tsx new file mode 100644 index 0000000..194094b --- /dev/null +++ b/src/pages/dex/trading/components/OrdersPool/index.tsx @@ -0,0 +1,198 @@ +import React, { useRef, useState } from 'react'; +import { classes, cutAddress, formatDollarValue, notationToString } from '@/utils/utils'; +import { nanoid } from 'nanoid'; +import Decimal from 'decimal.js'; +import Tooltip from '@/components/UI/Tooltip/Tooltip'; +import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; +import { buySellValues } from '@/constants'; +import EmptyMessage from '@/components/UI/EmptyMessage'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import useMouseLeave from '@/hook/useMouseLeave'; +import OrdersRow from './components/OrdersRow'; +import styles from './styles.module.scss'; +import BadgeStatus from '../BadgeStatus'; +import { OrdersPoolProps } from './types'; + +const OrdersPool = (props: OrdersPoolProps) => { + const { + ordersBuySell, + setOrdersBuySell, + currencyNames, + ordersLoading, + filteredOrdersHistory, + secondAssetUsdPrice, + takeOrderClick, + } = props; + const ordersInfoRef = useRef(null); + const { firstCurrencyName, secondCurrencyName } = currencyNames; + const [infoTooltipPos, setInfoTooltipPos] = useState({ x: 0, y: 0 }); + const [ordersInfoTooltip, setOrdersInfoTooltip] = useState(null); + + const moveInfoTooltip = (event: React.MouseEvent) => { + setInfoTooltipPos({ x: event.clientX, y: event.clientY }); + }; + + useMouseLeave(ordersInfoRef, () => setOrdersInfoTooltip(null)); + return ( + <> +
+
+
Orders pool
+ +
+ + + + + +
+
+ +
+ + + + + + + + + + {!ordersLoading && !!filteredOrdersHistory.length && ( + setOrdersInfoTooltip(null)} + className="orders-scroll" + > + {filteredOrdersHistory?.map((e) => { + const maxValue = Math.max( + ...filteredOrdersHistory.map((order) => + parseFloat(String(order.left)), + ), + ); + const percentage = ( + (parseFloat(String(e.left)) / maxValue) * + 100 + ).toFixed(2); + + return ( + + ); + })} + + )} +
Price ({secondCurrencyName})Amount ({firstCurrencyName})Total ({secondCurrencyName})
+ + {!filteredOrdersHistory.length && !ordersLoading && ( + + )} + {ordersLoading && } +
+
+ + {/* Order tooltip */} + {ordersInfoTooltip && + (() => { + const totalDecimal = new Decimal(ordersInfoTooltip?.left).mul( + new Decimal(ordersInfoTooltip?.price), + ); + const totalValue = secondAssetUsdPrice + ? totalDecimal.mul(secondAssetUsdPrice).toFixed(2) + : undefined; + + return ( + +
+
Alias
+

+ @{cutAddress(ordersInfoTooltip?.user?.alias || 'no alias', 12)}{' '} + {ordersInfoTooltip?.isInstant && ( + + )} +

+ +
Price ({secondCurrencyName})
+

+ {notationToString(ordersInfoTooltip?.price)} +

+ + ~ + {secondAssetUsdPrice && ordersInfoTooltip?.price !== undefined + ? (() => { + const total = + secondAssetUsdPrice * ordersInfoTooltip.price; + const formatted = + ordersInfoTooltip.price < 0.9 + ? `$${total.toFixed(5)}` + : `$${total.toFixed(2)}`; + return formatted; + })() + : 'undefined'} + + +
Amount ({firstCurrencyName})
+

{notationToString(ordersInfoTooltip?.amount)}

+ +
Total ({secondCurrencyName})
+

{notationToString(totalDecimal.toString())}

+ + ~{' '} + {totalValue ? `$${formatDollarValue(totalValue)}` : 'undefined'} + +
+
+ ); + })()} + + ); +}; + +export default OrdersPool; diff --git a/src/pages/dex/trading/components/OrdersPool/styles.module.scss b/src/pages/dex/trading/components/OrdersPool/styles.module.scss new file mode 100644 index 0000000..d16c375 --- /dev/null +++ b/src/pages/dex/trading/components/OrdersPool/styles.module.scss @@ -0,0 +1,151 @@ +.ordersPool { + max-width: 415px; + width: 100%; + padding: 5px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + + @media screen and (max-width: 1480px) { + max-width: 340px; + } + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--delimiter-color); + + &_title { + font-size: 18px; + font-weight: 600; + } + + &_type { + display: flex; + align-items: center; + gap: 8px; + + .btn { + cursor: pointer; + width: 20px; + height: 20px; + border-radius: 4px; + font-size: 12px; + font-weight: 700; + transition: 0.3s opacity ease; + color: #ffffff; + + &.selected, + &:hover { + opacity: 80%; + } + + &.all { + background: linear-gradient(to left, #ff6767 50%, #16d1d6 50%); + } + + &.buy { + background-color: #16d1d6; + } + + &.sell { + background-color: #ff6767; + } + } + } + } + + &__content { + display: flex; + flex-direction: column; + padding-top: 10px; + + table { + width: 100%; + + thead { + display: flex; + width: 100%; + padding-inline: 10px; + margin-bottom: 9px; + + tr { + width: 100%; + display: flex; + justify-content: space-between; + + th { + font-size: 11px; + font-weight: 700; + text-align: start; + color: var(--table-th-color); + min-width: 80px; + + &:last-child { + text-align: right; + } + } + } + } + + tbody { + height: 29dvh; + min-height: 265px; + max-height: 380px; + display: flex; + flex-direction: column; + overflow: auto; + padding-bottom: 20px; + padding: 10px; + } + } + } +} + +.tooltip { + pointer-events: none; + position: fixed; + border: 1px solid var(--dex-tooltip-border-color); + min-width: 140px; + max-width: 180px; + padding: 10px; + transform: translateX(-50%); + background-color: var(--dex-tooltip-bg); + + &__arrow { + border-top: 1px solid var(--dex-tooltip-border-color); + background-color: var(--dex-tooltip-bg) !important; + } + + h6 { + color: var(--table-th-color); + margin-top: 12px; + font-size: 11px; + font-weight: 700; + + &:first-child { + margin-top: 0; + } + } + + p { + font-size: 12px; + font-weight: 400; + display: flex; + align-items: center; + gap: 5px; + margin-top: 6px; + } + + span { + margin-top: 5px; + display: block; + color: #8d95ae; + font-size: 11px; + font-weight: 400; + } +} diff --git a/src/pages/dex/trading/components/OrdersPool/types.ts b/src/pages/dex/trading/components/OrdersPool/types.ts new file mode 100644 index 0000000..c5c5a8b --- /dev/null +++ b/src/pages/dex/trading/components/OrdersPool/types.ts @@ -0,0 +1,21 @@ +import { Dispatch, SetStateAction } from 'react'; +import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; + +export interface OrdersPoolProps { + ordersBuySell: SelectValue; + setOrdersBuySell: Dispatch>; + currencyNames: { + firstCurrencyName: string; + secondCurrencyName: string; + }; + ordersLoading: boolean; + filteredOrdersHistory: PageOrderData[]; + secondAssetUsdPrice: number | undefined; + takeOrderClick: ( + _event: + | React.MouseEvent + | React.MouseEvent, + _e: PageOrderData, + ) => void; +} diff --git a/src/pages/dex/trading/components/StatItem/index.tsx b/src/pages/dex/trading/components/StatItem/index.tsx new file mode 100644 index 0000000..c14c13c --- /dev/null +++ b/src/pages/dex/trading/components/StatItem/index.tsx @@ -0,0 +1,32 @@ +import StatItemProps from '@/interfaces/props/pages/dex/trading/StatItemProps'; +import { classes } from '@/utils/utils'; +import styles from './styles.module.scss'; + +function StatItem({ Img, title, value, className, coefficient }: StatItemProps) { + return ( +
+
+ +

{title}

+
+ +
+

{value}

+ + {coefficient !== undefined && ( +

= 0 ? styles.green : styles.red, + )} + > + {coefficient >= 0 ? '+' : ''} + {coefficient?.toFixed(2)}% +

+ )} +
+
+ ); +} + +export default StatItem; diff --git a/src/pages/dex/trading/components/StatItem/styles.module.scss b/src/pages/dex/trading/components/StatItem/styles.module.scss new file mode 100644 index 0000000..2173148 --- /dev/null +++ b/src/pages/dex/trading/components/StatItem/styles.module.scss @@ -0,0 +1,44 @@ +.statItem { + display: flex; + flex-direction: column; + gap: 6px; + + &__nav { + display: flex; + align-items: center; + gap: 5px; + + &_title { + color: var(--footer-selected-link); + white-space: nowrap; + font-size: 14px; + font-weight: 700; + } + } + + &__content { + display: flex; + align-items: center; + gap: 5px; + + &_val { + white-space: nowrap; + font-size: 14px; + font-weight: 400; + } + + &_coefficient { + white-space: nowrap; + font-size: 14px; + font-weight: 400; + + &.green { + color: #16d1d6; + } + + &.red { + color: #ff6767; + } + } + } +} diff --git a/src/pages/dex/trading/TimeLeft/TimeLeft.tsx b/src/pages/dex/trading/components/TimeLeft/index.tsx similarity index 100% rename from src/pages/dex/trading/TimeLeft/TimeLeft.tsx rename to src/pages/dex/trading/components/TimeLeft/index.tsx diff --git a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/index.tsx b/src/pages/dex/trading/components/TradingHeader/components/AssetRow/index.tsx new file mode 100644 index 0000000..dd978c7 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/components/AssetRow/index.tsx @@ -0,0 +1,23 @@ +import { shortenAddress } from '@/utils/utils'; +import Link from 'next/link'; +import CurrencyIcon from '../CurrencyIcon'; +import styles from './styles.module.scss'; +import { AssetRowProps } from './types'; + +const AssetRow = ({ name, link, id, code }: AssetRowProps) => ( +
+

+ {name}: +

+ + {shortenAddress(id)} + +
+); + +export default AssetRow; diff --git a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/styles.module.scss b/src/pages/dex/trading/components/TradingHeader/components/AssetRow/styles.module.scss new file mode 100644 index 0000000..15e8a85 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/components/AssetRow/styles.module.scss @@ -0,0 +1,19 @@ +.asset { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + + &__name { + display: flex; + align-items: center; + gap: 5px; + font-size: 14px; + font-weight: 400; + } + + &__address { + font-size: 14px; + font-weight: 400; + } +} diff --git a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts b/src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts new file mode 100644 index 0000000..354ce55 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts @@ -0,0 +1,6 @@ +export interface AssetRowProps { + name: string; + link: string; + id: string; + code: string | undefined; +} diff --git a/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx b/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx new file mode 100644 index 0000000..95e2d80 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx @@ -0,0 +1,8 @@ +import Image from 'next/image'; +import { CurrencyIconProps } from './types'; + +const CurrencyIcon = ({ code, size = 50 }: CurrencyIconProps) => ( + currency +); + +export default CurrencyIcon; diff --git a/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts b/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts new file mode 100644 index 0000000..75dc2f0 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts @@ -0,0 +1,4 @@ +export interface CurrencyIconProps { + code: string | undefined; + size?: number; +} diff --git a/src/pages/dex/trading/components/TradingHeader/index.tsx b/src/pages/dex/trading/components/TradingHeader/index.tsx new file mode 100644 index 0000000..32d1f47 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/index.tsx @@ -0,0 +1,127 @@ +import { ReactComponent as ClockIcon } from '@/assets/images/UI/clock_icon.svg'; +import { ReactComponent as UpIcon } from '@/assets/images/UI/up_icon.svg'; +import { ReactComponent as DownIcon } from '@/assets/images/UI/down_icon.svg'; +import { ReactComponent as VolumeIcon } from '@/assets/images/UI/volume_icon.svg'; +import BackButton from '@/components/default/BackButton/BackButton'; +import { tradingKnownCurrencies, roundTo, notationToString } from '@/utils/utils'; +import styles from './styles.module.scss'; +import StatItem from '../StatItem'; +import { TradingHeaderProps } from './types'; +import CurrencyIcon from './components/CurrencyIcon'; +import AssetRow from './components/AssetRow'; + +const getCurrencyCode = (code?: string) => + tradingKnownCurrencies.includes(code || '') ? code : 'tsds'; + +const TradingHeader = ({ + pairStats, + pairRateUsd, + firstAssetLink, + secondAssetLink, + firstAssetId, + secondAssetId, + pairData, +}: TradingHeaderProps) => { + const currencyNames = { + firstCurrencyName: pairData?.first_currency?.name || '', + secondCurrencyName: pairData?.second_currency?.name || '', + }; + + const { firstCurrencyName, secondCurrencyName } = currencyNames; + const imgCode = getCurrencyCode(pairData?.first_currency?.code || ''); + const imgCode2 = getCurrencyCode(pairData?.second_currency?.code || ''); + + const coefficient = pairStats?.coefficient || 0; + const coefficientOutput = + parseFloat(coefficient?.toFixed(2) || '0') === -100 + ? -99.99 + : parseFloat(coefficient?.toFixed(2) || '0'); + + const stats = [ + { + Img: ClockIcon, + title: '24h change', + value: `${roundTo(notationToString(pairStats?.rate || 0), 8)} ${secondCurrencyName}`, + coefficient: coefficientOutput, + }, + { + Img: UpIcon, + title: '24h high', + value: `${roundTo(notationToString(pairStats?.high || 0), 8)} ${secondCurrencyName}`, + }, + { + Img: DownIcon, + title: '24h low', + value: `${roundTo(notationToString(pairStats?.low || 0), 8)} ${secondCurrencyName}`, + }, + { + Img: VolumeIcon, + title: '24h volume', + value: `${roundTo(notationToString(pairStats?.volume || 0), 8)} ${secondCurrencyName}`, + }, + ]; + + return ( +
+
+
+ +
+ +
+

+ {!pairData ? ( + '...' + ) : ( + <> + {firstCurrencyName} + /{secondCurrencyName} + + )} +

+ +
+

+ {notationToString(pairStats?.rate || 0)} {secondCurrencyName} +

+ {pairRateUsd &&

~ ${pairRateUsd}

} +
+
+
+ +
+ {pairData && firstAssetLink && secondAssetLink && ( +
+ + +
+ )} + + {stats.map(({ Img, title, value, coefficient }) => ( + + ))} +
+ + +
+ ); +}; + +export default TradingHeader; diff --git a/src/pages/dex/trading/components/TradingHeader/styles.module.scss b/src/pages/dex/trading/components/TradingHeader/styles.module.scss new file mode 100644 index 0000000..6a87c1d --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/styles.module.scss @@ -0,0 +1,78 @@ +.header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 25px; + position: relative; + border: none; + + &__currency { + display: flex; + align-items: center; + gap: 12px; + + &_icon { + min-width: 48px; + min-height: 48px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--icon-bg-color); + border-radius: 50%; + + > img { + width: 26px; + height: auto; + } + } + + &_item { + display: flex; + flex-direction: column; + justify-content: space-between; + + .currencyName { + font-size: 18px; + font-weight: 600; + + span { + color: var(--footer-selected-link); + } + } + + .price { + display: flex; + align-items: center; + gap: 5px; + + &__secondCurrency { + font-weight: 400; + font-size: 14px; + } + + &__usd { + color: var(--footer-selected-link); + font-size: 12px; + font-weight: 400; + } + } + } + } + + &__stats { + display: flex; + flex-wrap: nowrap; + gap: 20px; + + &_assets { + display: flex; + flex-direction: column; + gap: 7px; + } + + &_item { + padding-left: 20px; + border-left: 1px solid var(--delimiter-color); + } + } +} diff --git a/src/pages/dex/trading/components/TradingHeader/types.ts b/src/pages/dex/trading/components/TradingHeader/types.ts new file mode 100644 index 0000000..9371ec0 --- /dev/null +++ b/src/pages/dex/trading/components/TradingHeader/types.ts @@ -0,0 +1,12 @@ +import PairData from '@/interfaces/common/PairData'; +import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes'; + +export interface TradingHeaderProps { + pairStats: PairStats | null; + pairRateUsd: string | undefined; + firstAssetLink?: string; + secondAssetLink?: string; + firstAssetId?: string | null; + secondAssetId?: string | null; + pairData: PairData | null; +} diff --git a/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/index.tsx b/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/index.tsx new file mode 100644 index 0000000..d1b447d --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/index.tsx @@ -0,0 +1,231 @@ +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/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/types.ts b/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/types.ts new file mode 100644 index 0000000..bec6f27 --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/types.ts @@ -0,0 +1,16 @@ +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/pages/dex/trading/components/UserOrders/components/MyOrdersRow/index.tsx b/src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/index.tsx new file mode 100644 index 0000000..f697441 --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/index.tsx @@ -0,0 +1,140 @@ +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/pages/dex/trading/components/UserOrders/components/MyOrdersRow/types.ts b/src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/types.ts new file mode 100644 index 0000000..9bd8026 --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/types.ts @@ -0,0 +1,13 @@ +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/pages/dex/trading/components/UserOrders/index.tsx b/src/pages/dex/trading/components/UserOrders/index.tsx new file mode 100644 index 0000000..e580296 --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/index.tsx @@ -0,0 +1,133 @@ +import { classes } from '@/utils/utils'; +import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; +import useUpdateUser from '@/hook/useUpdateUser'; +import EmptyMessage from '@/components/UI/EmptyMessage'; +import styles from './styles.module.scss'; +import { UserOrdersProps } from './types'; +import MyOrdersRow from './components/MyOrdersRow'; +import MyOrdersApplyRow from './components/MyOrdersApplyRow'; + +const UserOrders = ({ + userOrders, + applyTips, + myOrdersLoading, + loggedIn, + ordersType, + setOrdersType, + handleCancelAllOrders, + orderListRef, + matrixAddresses, + secondAssetUsdPrice, + updateOrders, + updateUserOrders, + fetchTrades, + pairData, +}: UserOrdersProps) => { + const fetchUser = useUpdateUser(); + const firstCurrencyName = pairData?.first_currency?.name || ''; + const secondCurrencyName = pairData?.second_currency?.name || ''; + + return ( +
+
+
+ + + +
+ +
+ +
+
+ +
+ + + + + + + + + + + +
AliasPrice ({secondCurrencyName})Amount ({firstCurrencyName})Total ({secondCurrencyName})Offers
+ + {!myOrdersLoading && loggedIn && !!userOrders.length && ( +
+ + + {userOrders.map((e) => ( + + ))} + +
+ + {!!applyTips.length && ( + + + {applyTips.map((e) => ( + + ))} + +
+ )} +
+ )} + + {myOrdersLoading && loggedIn && } + + {!loggedIn && } + + {loggedIn && !userOrders.length && !myOrdersLoading && ( + + )} +
+
+ ); +}; + +export default UserOrders; diff --git a/src/pages/dex/trading/components/UserOrders/styles.module.scss b/src/pages/dex/trading/components/UserOrders/styles.module.scss new file mode 100644 index 0000000..cb2a2c5 --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/styles.module.scss @@ -0,0 +1,227 @@ +.userOrders { + width: 100%; + padding: 5px; + background: var(--window-bg-color); + border: 1px solid var(--delimiter-color); + border-radius: 10px; + + @media screen and (max-width: 1440px) { + width: 520px; + } + + &__header { + border-bottom: 1px solid var(--delimiter-color); + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + padding: 10px; + padding-bottom: 0; + + &_nav { + display: flex; + align-items: center; + gap: 22px; + + .navItem { + padding-bottom: 7px; + position: relative; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + font-size: 16px; + 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%; + } + + &.active { + border-color: #1f8feb; + } + + &:hover { + color: #1f8feb; + } + } + } + + &_btn { + 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; + } + } + } + + table { + width: 100%; + + thead { + display: flex; + width: 100%; + padding-inline: 10px; + padding-bottom: 5px; + margin-top: 10px; + + 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; + } + } + } + } + + tbody { + a { + display: block; + text-align: right; + font-size: 12px; + font-weight: 400; + } + } + } + + &__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; + width: 100%; + padding: 4px 0; + + td { + position: relative; + min-width: 100px; + + &:last-child { + min-width: 50px; + } + + @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; + } + } + } + } + } + } + } +} + +.alias { + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + + &__text { + color: var(--font-main-color) !important; + } + + path { + fill: none; + } +} + +.tooltip { + position: absolute; + top: 30px; + left: 5%; + background-color: var(--trade-table-tooltip); + + z-index: 9999; + + &__text { + font-size: 12px !important; + } + + &__arrow { + border-radius: 2px; + left: 30% !important; + background-color: var(--trade-table-tooltip) !important; + } +} diff --git a/src/pages/dex/trading/components/UserOrders/types.ts b/src/pages/dex/trading/components/UserOrders/types.ts new file mode 100644 index 0000000..b67eb69 --- /dev/null +++ b/src/pages/dex/trading/components/UserOrders/types.ts @@ -0,0 +1,22 @@ +import ApplyTip from '@/interfaces/common/ApplyTip'; +import MatrixAddress from '@/interfaces/common/MatrixAddress'; +import OrderRow from '@/interfaces/common/OrderRow'; +import PairData from '@/interfaces/common/PairData'; +import { Dispatch, ForwardedRef, SetStateAction } from 'react'; + +export interface UserOrdersProps { + orderListRef: ForwardedRef; + userOrders: OrderRow[]; + applyTips: ApplyTip[]; + myOrdersLoading: boolean; + loggedIn: boolean; + ordersType: 'opened' | 'history'; + setOrdersType: Dispatch>; + handleCancelAllOrders: () => void; + secondAssetUsdPrice: number | undefined; + updateOrders: () => Promise; + updateUserOrders: () => Promise; + fetchTrades: () => Promise; + matrixAddresses: MatrixAddress[]; + pairData: PairData | null; +} diff --git a/src/pages/dex/trading/find-pair/index.tsx b/src/pages/dex/trading/find-pair/index.tsx deleted file mode 100644 index 0a7149d..0000000 --- a/src/pages/dex/trading/find-pair/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { findPairID } from '@/utils/methods'; -import { GetServerSideProps } from 'next'; - -const getServerSideProps: GetServerSideProps = async (context) => { - const { first, second } = context.query; - - if (!first || !second) { - return { - notFound: true, // Show a 404 page if parameters are missing - }; - } - - try { - const idFound = await findPairID( - first as string, - second as string, - context.req.headers.host as string, - ); - - console.log('ID found:', idFound); - - if (typeof idFound === 'number') { - return { - redirect: { - destination: `/dex/trading/${idFound}`, - permanent: false, - }, - }; - } - - return { - notFound: true, - }; - } catch (error) { - console.error('Error fetching pair ID:', error); - return { - props: { - error: 'Failed to resolve the pair.', - }, - }; - } -}; - -export default getServerSideProps; diff --git a/src/pages/dex/trading/helpers/handleInputChange.ts b/src/pages/dex/trading/helpers/handleInputChange.ts new file mode 100644 index 0000000..ee32ed3 --- /dev/null +++ b/src/pages/dex/trading/helpers/handleInputChange.ts @@ -0,0 +1,89 @@ +import { Dispatch, SetStateAction } from 'react'; +import Decimal from 'decimal.js'; +import { isPositiveFloatStr } from '@/utils/utils'; +import { validateTokensInput } from 'shared/utils'; + +interface HandleInputChangeParams { + inputValue: string; + priceOrAmount: 'price' | 'amount'; + otherValue: string; + thisDP: number; + totalDP: number; + setThisState: Dispatch>; + setTotalState: Dispatch>; + setThisValid: Dispatch>; + setTotalValid: Dispatch>; + balance?: string | undefined; + setRangeInputValue?: Dispatch>; +} + +export function handleInputChange({ + inputValue, + priceOrAmount, + otherValue, + thisDP, + totalDP, + setThisState, + setTotalState, + setThisValid, + setTotalValid, + balance, + setRangeInputValue, +}: HandleInputChangeParams) { + if (inputValue !== '' && !isPositiveFloatStr(inputValue)) return; + + const digitsOnly = inputValue.replace('.', '').replace(/^0+/, ''); + if (digitsOnly.length > 18) return; + + let thisDecimal: Decimal; + let otherDecimal: Decimal; + + try { + thisDecimal = new Decimal(inputValue || '0'); + otherDecimal = new Decimal(otherValue || '0'); + } catch (err) { + console.log(err); + setThisValid(false); + setTotalValid(false); + return; + } + + setThisState(inputValue); + + if (!inputValue) { + setTotalState(''); + setTotalValid(false); + setThisValid(false); + return; + } + + const isValid = validateTokensInput(inputValue, thisDP); + if (!isValid.valid) { + setTotalState(''); + setTotalValid(false); + setThisValid(false); + return; + } + + setThisValid(true); + + if (!thisDecimal.isNaN() && !otherDecimal.isNaN() && otherValue !== '') { + const total = + priceOrAmount === 'price' + ? thisDecimal.mul(otherDecimal) + : otherDecimal.mul(thisDecimal); + + setTotalState(total.toString()); + + const totalValid = validateTokensInput(total.toFixed(totalDP), totalDP); + setTotalValid(totalValid.valid); + + if (priceOrAmount === 'amount' && balance && setRangeInputValue) { + const percent = thisDecimal.div(balance).mul(100); + setRangeInputValue(percent.toFixed()); + } + } else { + setTotalState(''); + setTotalValid(false); + } +} diff --git a/src/pages/dex/trading/helpers/takeOrderClick.ts b/src/pages/dex/trading/helpers/takeOrderClick.ts new file mode 100644 index 0000000..d80fdfe --- /dev/null +++ b/src/pages/dex/trading/helpers/takeOrderClick.ts @@ -0,0 +1,96 @@ +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { notationToString } from '@/utils/utils'; +import Decimal from 'decimal.js'; +import React from 'react'; +import PairDataType from '@/interfaces/common/PairData'; +import OrderFormOutput from '@/interfaces/common/orderFormOutput'; +import { handleInputChange } from './handleInputChange'; + +interface takeOrderClickParams { + event: + | React.MouseEvent + | React.MouseEvent; + PageOrderData: PageOrderData; + pairData: PairDataType | null; + buyForm: OrderFormOutput; + sellForm: OrderFormOutput; + balance: string | undefined; + scrollToOrderForm: () => void; +} + +function takeOrderClick({ + event, + PageOrderData, + pairData, + buyForm, + sellForm, + balance, + scrollToOrderForm, +}: takeOrderClickParams) { + event.preventDefault(); + const e = PageOrderData; + + const priceStr = notationToString(new Decimal(e.price).toString()) || ''; + const amountStr = notationToString(new Decimal(e.amount).toString()) || ''; + + const secondCurrencyDP = pairData?.second_currency?.asset_info?.decimal_point || 12; + const firstCurrencyDP = pairData?.first_currency?.asset_info?.decimal_point || 12; + + if (e.type === 'sell') { + handleInputChange({ + inputValue: priceStr, + priceOrAmount: 'price', + otherValue: amountStr, + thisDP: secondCurrencyDP, + totalDP: secondCurrencyDP, + setThisState: buyForm.setPrice, + setTotalState: buyForm.setTotal, + setThisValid: buyForm.setPriceValid, + setTotalValid: buyForm.setTotalValid, + }); + + handleInputChange({ + inputValue: amountStr, + priceOrAmount: 'amount', + otherValue: priceStr, + thisDP: firstCurrencyDP, + totalDP: secondCurrencyDP, + setThisState: buyForm.setAmount, + setTotalState: buyForm.setTotal, + setThisValid: buyForm.setAmountValid, + setTotalValid: buyForm.setTotalValid, + balance, + setRangeInputValue: buyForm.setRangeInputValue, + }); + } else { + handleInputChange({ + inputValue: priceStr, + priceOrAmount: 'price', + otherValue: amountStr, + thisDP: secondCurrencyDP, + totalDP: secondCurrencyDP, + setThisState: sellForm.setPrice, + setTotalState: sellForm.setTotal, + setThisValid: sellForm.setPriceValid, + setTotalValid: sellForm.setTotalValid, + }); + + handleInputChange({ + inputValue: amountStr, + priceOrAmount: 'amount', + otherValue: priceStr, + thisDP: firstCurrencyDP, + totalDP: secondCurrencyDP, + setThisState: sellForm.setAmount, + setTotalState: sellForm.setTotal, + setThisValid: sellForm.setAmountValid, + setTotalValid: sellForm.setTotalValid, + balance, + setRangeInputValue: sellForm.setRangeInputValue, + }); + } + + scrollToOrderForm(); +} + +export default takeOrderClick; diff --git a/src/pages/dex/trading/hooks/useFilteredData.ts b/src/pages/dex/trading/hooks/useFilteredData.ts new file mode 100644 index 0000000..43b3122 --- /dev/null +++ b/src/pages/dex/trading/hooks/useFilteredData.ts @@ -0,0 +1,47 @@ +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; +import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; +import { Store } from '@/store/store-reducer'; +import { useContext } from 'react'; + +interface useFilteredDataParams { + trades: Trade[]; + ordersHistory: PageOrderData[]; + ordersBuySell: SelectValue; + tradesType: 'all' | 'my'; +} + +const useFilteredData = ({ + ordersHistory, + trades, + ordersBuySell, + tradesType, +}: useFilteredDataParams) => { + const { state } = useContext(Store); + + const filteredTrades = + tradesType === 'my' + ? trades.filter( + (trade) => + trade.buyer.address === state.wallet?.address || + trade.seller.address === state.wallet?.address, + ) + : trades; + + const filteredOrdersHistory = ordersHistory + ?.filter((e) => (ordersBuySell.code === 'all' ? e : e.type === ordersBuySell.code)) + ?.filter((e) => e.user.address !== state.wallet?.address) + ?.sort((a, b) => { + if (ordersBuySell.code === 'buy') { + return parseFloat(b.price.toString()) - parseFloat(a.price.toString()); + } + return parseFloat(a.price.toString()) - parseFloat(b.price.toString()); + }); + + return { + filteredOrdersHistory, + filteredTrades, + }; +}; + +export default useFilteredData; diff --git a/src/pages/dex/trading/hooks/useMatrixAddresses.ts b/src/pages/dex/trading/hooks/useMatrixAddresses.ts new file mode 100644 index 0000000..eb03242 --- /dev/null +++ b/src/pages/dex/trading/hooks/useMatrixAddresses.ts @@ -0,0 +1,25 @@ +import MatrixAddress from '@/interfaces/common/MatrixAddress'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { getMatrixAddresses } from '@/utils/methods'; +import { useEffect, useState } from 'react'; + +const useMatrixAddresses = (ordersHistory: PageOrderData[]) => { + const [matrixAddresses, setMatrixAddresses] = useState([]); + + useEffect(() => { + const fetchConnections = async () => { + const filteredAddresses = ordersHistory?.map((e) => e?.user?.address); + if (!filteredAddresses.length) return; + + const data = await getMatrixAddresses(filteredAddresses); + + setMatrixAddresses(data.addresses); + }; + + fetchConnections(); + }, [ordersHistory]); + + return matrixAddresses; +}; + +export default useMatrixAddresses; diff --git a/src/pages/dex/trading/hooks/useOrdereForm.ts b/src/pages/dex/trading/hooks/useOrdereForm.ts new file mode 100644 index 0000000..902b6d9 --- /dev/null +++ b/src/pages/dex/trading/hooks/useOrdereForm.ts @@ -0,0 +1,104 @@ +import { useState, useEffect } from 'react'; +import Decimal from 'decimal.js'; +import PairData from '@/interfaces/common/PairData'; +import OrderFormOutput from '@/interfaces/common/orderFormOutput'; +import { handleInputChange } from '../helpers/handleInputChange'; + +interface UseOrderFormParams { + type: 'buy' | 'sell'; + pairData: PairData | null; + balance: string | undefined; + assetsRates: Map; +} + +export function useOrderForm({ + type, + pairData, + balance, + assetsRates, +}: UseOrderFormParams): OrderFormOutput { + const [price, setPrice] = useState(''); + const [amount, setAmount] = useState(''); + const [total, setTotal] = useState(''); + + const [priceValid, setPriceValid] = useState(false); + const [amountValid, setAmountValid] = useState(false); + const [totalValid, setTotalValid] = useState(false); + + const [totalUsd, setTotalUsd] = useState(undefined); + const [rangeInputValue, setRangeInputValue] = useState('50'); + + const priceDP = pairData?.second_currency?.asset_info?.decimal_point || 12; + const amountDP = pairData?.first_currency?.asset_info?.decimal_point || 12; + + useEffect(() => { + try { + const totalDecimal = new Decimal(total); + const zanoPrice = assetsRates.get(pairData?.second_currency?.asset_id || ''); + setTotalUsd(zanoPrice ? totalDecimal.mul(zanoPrice).toFixed(2) : undefined); + } catch (err) { + setTotalUsd(undefined); + } + }, [total, assetsRates, pairData?.second_currency?.asset_id]); + + function onPriceChange(inputValue: string) { + handleInputChange({ + inputValue, + priceOrAmount: 'price', + otherValue: amount, + thisDP: priceDP, + totalDP: priceDP, + setThisState: setPrice, + setTotalState: setTotal, + setThisValid: setPriceValid, + setTotalValid, + }); + } + + function onAmountChange(inputValue: string) { + handleInputChange({ + inputValue, + priceOrAmount: 'amount', + otherValue: price, + thisDP: amountDP, + totalDP: priceDP, + setThisState: setAmount, + setTotalState: setTotal, + setThisValid: setAmountValid, + setTotalValid, + balance, + setRangeInputValue, + }); + } + + function resetForm() { + setPrice(''); + setAmount(''); + setTotal(''); + setPriceValid(false); + setAmountValid(false); + setTotalValid(false); + setRangeInputValue('50'); + } + + return { + price, + amount, + total, + priceValid, + amountValid, + totalValid, + totalUsd, + rangeInputValue, + setRangeInputValue, + onPriceChange, + onAmountChange, + resetForm, + setTotal, + setPrice, + setAmount, + setPriceValid, + setAmountValid, + setTotalValid, + }; +} diff --git a/src/pages/dex/trading/hooks/useSocketListeners.ts b/src/pages/dex/trading/hooks/useSocketListeners.ts new file mode 100644 index 0000000..8ee0514 --- /dev/null +++ b/src/pages/dex/trading/hooks/useSocketListeners.ts @@ -0,0 +1,85 @@ +import ApplyTip from '@/interfaces/common/ApplyTip'; +import OrderRow from '@/interfaces/common/OrderRow'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes'; +import { getUserOrdersPage } from '@/utils/methods'; +import socket from '@/utils/socket'; +import { useRouter } from 'next/router'; +import { Dispatch, SetStateAction, useEffect } from 'react'; + +interface useSocketListenersParams { + setUserOrders: Dispatch>; + setApplyTips: Dispatch>; + setPairStats: Dispatch>; + setOrdersHistory: Dispatch>; + ordersHistory: PageOrderData[]; + updateOrders: () => Promise; +} + +export const useSocketListeners = ({ + setUserOrders, + setApplyTips, + setPairStats, + setOrdersHistory, + ordersHistory, + updateOrders, +}: useSocketListenersParams) => { + const router = useRouter(); + const pairId = typeof router.query.id === 'string' ? router.query.id : ''; + + async function socketUpdateOrders() { + const result = await getUserOrdersPage(pairId); + + if (result.success) { + setUserOrders(result?.data?.orders || []); + setApplyTips(result?.data?.applyTips || []); + } + } + + useEffect(() => { + socket.emit('in-trading', { id: router.query.id }); + + return () => { + socket.emit('out-trading', { id: router.query.id }); + }; + }, []); + + useEffect(() => { + socket.on('new-order', async (data) => { + setOrdersHistory([data.orderData, ...ordersHistory]); + await socketUpdateOrders(); + }); + + socket.on('delete-order', async () => { + await updateOrders(); + await socketUpdateOrders(); + }); + + return () => { + socket.off('new-order'); + socket.off('delete-order'); + }; + }, [ordersHistory]); + + useEffect(() => { + function onUpdateStats({ pairStats }: { pairStats: PairStats }) { + setPairStats(pairStats); + } + + socket.on('update-pair-stats', onUpdateStats); + + return () => { + socket.off('update-pair-stats', onUpdateStats); + }; + }, []); + + useEffect(() => { + socket.on('update-orders', async () => { + await socketUpdateOrders(); + }); + + return () => { + socket.off('update-orders'); + }; + }, []); +}; diff --git a/src/pages/dex/trading/hooks/useTradeInit.ts b/src/pages/dex/trading/hooks/useTradeInit.ts new file mode 100644 index 0000000..4b5dc9b --- /dev/null +++ b/src/pages/dex/trading/hooks/useTradeInit.ts @@ -0,0 +1,70 @@ +import PairData from '@/interfaces/common/PairData'; +import { Store } from '@/store/store-reducer'; +import { useContext } from 'react'; +import Decimal from 'decimal.js'; +import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes'; +import { useOrderForm } from './useOrdereForm'; + +interface useTradeInitParams { + pairData: PairData | null; + pairStats: PairStats | null; +} + +const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => { + const { state } = useContext(Store); + + const currencyNames = { + 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; + + const firstAssetId = pairData ? pairData.first_currency?.asset_id : undefined; + const secondAssetId = pairData ? pairData.second_currency?.asset_id : undefined; + const firstAssetLink = firstAssetId + ? `https://explorer.zano.org/assets?asset_id=${encodeURIComponent(firstAssetId)}` + : undefined; + const secondAssetLink = secondAssetId + ? `https://explorer.zano.org/assets?asset_id=${encodeURIComponent(secondAssetId)}` + : undefined; + + const secondAssetUsdPrice = state.assetsRates.get(secondAssetId || ''); + + const pairRateUsd = + pairStats?.rate !== undefined && secondAssetUsdPrice !== undefined + ? new Decimal(pairStats.rate) + .mul(secondAssetUsdPrice) + .toFixed(pairStats.rate < 0.1 ? 6 : 2) + : undefined; + + const buyForm = useOrderForm({ + type: 'buy', + pairData, + balance, + assetsRates: state.assetsRates, + }); + + const sellForm = useOrderForm({ + type: 'sell', + pairData, + balance, + assetsRates: state.assetsRates, + }); + + return { + currencyNames, + firstAssetLink, + secondAssetLink, + secondAssetUsdPrice, + loggedIn, + balance, + buyForm, + sellForm, + pairRateUsd, + }; +}; + +export default useTradeInit; diff --git a/src/pages/dex/trading/hooks/useTradingData.ts b/src/pages/dex/trading/hooks/useTradingData.ts new file mode 100644 index 0000000..fafa4cb --- /dev/null +++ b/src/pages/dex/trading/hooks/useTradingData.ts @@ -0,0 +1,144 @@ +import { + getCandles, + getOrdersPage, + getPair, + getPairStats, + getUserOrdersPage, + getTrades, +} from '@/utils/methods'; +import useUpdateUser from '@/hook/useUpdateUser'; +import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react'; +import CandleRow from '@/interfaces/common/CandleRow'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; +import PairData from '@/interfaces/common/PairData'; +import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes'; +import OrderRow from '@/interfaces/common/OrderRow'; +import ApplyTip from '@/interfaces/common/ApplyTip'; +import { useRouter } from 'next/router'; +import PeriodState from '@/interfaces/states/pages/dex/trading/InputPanelItem/PeriodState'; +import { Store } from '@/store/store-reducer'; + +interface UseTradingDataParams { + periodsState: PeriodState; + setCandles: Dispatch>; + setOrdersHistory: Dispatch>; + setTrades: Dispatch>; + setPairData: Dispatch>; + setPairStats: Dispatch>; + setUserOrders: Dispatch>; + setApplyTips: Dispatch>; + setMyOrdersLoading: Dispatch>; +} + +export function useTradingData({ + periodsState, + setCandles, + setOrdersHistory, + setTrades, + setPairData, + setPairStats, + setUserOrders, + setApplyTips, + setMyOrdersLoading, +}: UseTradingDataParams) { + const { state } = useContext(Store); + const fetchUser = useUpdateUser(); + const router = useRouter(); + const [candlesLoaded, setCandlesLoaded] = useState(false); + const [ordersLoading, setOrdersLoading] = useState(true); + const [tradesLoading, setTradesLoading] = useState(true); + const pairId = typeof router.query.id === 'string' ? router.query.id : ''; + const loggedIn = !!state.wallet?.connected; + + async function fetchCandles() { + setCandlesLoaded(false); + setCandles([]); + const result = await getCandles(pairId, periodsState.code); + if (result.success) { + setCandles(result.data); + } else { + setCandles([]); + } + setCandlesLoaded(true); + } + + async function updateOrders() { + setOrdersLoading(true); + const result = await getOrdersPage(pairId); + if (!result.success) return; + setOrdersHistory(result?.data || []); + setOrdersLoading(false); + } + + async function updateUserOrders() { + setMyOrdersLoading(true); + const result = await getUserOrdersPage(pairId); + await fetchUser(); + + if (!result.success) return; + setUserOrders(result?.data?.orders || []); + setApplyTips(result?.data?.applyTips || []); + setMyOrdersLoading(false); + } + + async function fetchTrades() { + setTradesLoading(true); + const result = await getTrades(pairId); + + if (result.success) { + setTrades(result.data); + } + + setTradesLoading(false); + } + + async function fetchPairStats() { + const result = await getPairStats(pairId); + if (!result.success) return; + setPairStats(result.data); + } + + async function getPairData() { + const result = await getPair(pairId); + if (!result.success) { + router.push('/404'); + return; + } + setPairData(result.data); + } + + useEffect(() => { + fetchPairStats(); + getPairData(); + updateOrders(); + }, []); + + useEffect(() => { + fetchCandles(); + }, [periodsState]); + + useEffect(() => { + (async () => { + await fetchTrades(); + })(); + }, [pairId]); + + useEffect(() => { + if (!loggedIn) return; + setUserOrders([]); + updateUserOrders(); + }, [state.wallet?.connected && state.wallet?.address]); + + return { + fetchCandles, + updateOrders, + updateUserOrders, + fetchTrades, + fetchPairStats, + getPairData, + candlesLoaded, + ordersLoading, + tradesLoading, + }; +} diff --git a/src/store/store-reducer.tsx b/src/store/store-reducer.tsx index ee4edff..848ff98 100644 --- a/src/store/store-reducer.tsx +++ b/src/store/store-reducer.tsx @@ -10,6 +10,8 @@ const initialState: ContextState = { offers: 0, }, closed_notifications: [], + alertState: null, + alertSubtitle: '', }; const reducer = (state: ContextState, action: ContextAction): ContextState => { @@ -31,6 +33,12 @@ const reducer = (state: ContextState, action: ContextAction): ContextState => { case 'CLOSED_NOTIFICATIONS_UPDATED': { return { ...state, closed_notifications: action.payload }; } + case 'ALERT_STATE_UPDATED': { + return { ...state, alertState: action.payload }; + } + case 'ALERT_SUBTITLE_UPDATED': { + return { ...state, alertSubtitle: action.payload }; + } default: return { ...state }; } @@ -40,6 +48,7 @@ export const Store = createContext({ state: initialState, dispatch: () => undefined, }); + export const StoreProvider = (props: { children: ReactNode }) => { const [state, dispatch] = useReducer(reducer, initialState); return {props.children}; diff --git a/src/styles/Trading.module.scss b/src/styles/Trading.module.scss index d2cbdab..aeb178e 100644 --- a/src/styles/Trading.module.scss +++ b/src/styles/Trading.module.scss @@ -1,238 +1,18 @@ -.main { - padding: 0 60px; +.trading { + padding-inline: 60px; padding-top: 20px; display: flex; flex-direction: column; - @media screen and (max-width: 1060px) { - padding-right: 20px; - padding-left: 20px; + @media screen and (max-width: 1600px) { + padding-inline: 40px; } - .orders__preloader { - margin-top: 40px; + @media screen and (max-width: 1200px) { + padding-inline: 20px; } - - table { - .alias { - display: flex; - align-items: center; - gap: 4px; - - p { - font-size: 14px; - } - - path { - fill: none; - } - - &__tooltip { - position: absolute; - top: 30px; - left: 5%; - background-color: var(--trade-table-tooltip); - font-size: 12px; - - &_arrow { - border-radius: 2px; - left: 50%; - background-color: var(--trade-table-tooltip); - } - } - } - } - - .trading__title__wrapper { - display: flex; - align-items: center; - justify-content: space-between; - gap: 25px; - position: relative; - border: none; - - .currency__stats__wrapper { - display: flex; - flex-wrap: nowrap; - gap: 20px; - - &_assets { - display: flex; - flex-direction: column; - gap: 7px; - - .asset { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - - p { - display: flex; - align-items: center; - gap: 5px; - font-size: 14px; - font-weight: 400; - } - - a { - font-size: 14px; - font-weight: 400; - } - } - } - - > :nth-child(2), - :nth-child(4), - :nth-child(3) { - padding-left: 20px; - border-left: 1px solid var(--delimiter-color); - } - - .trading__stat__item { - display: flex; - flex-direction: column; - gap: 6px; - - &_nav { - display: flex; - align-items: center; - gap: 5px; - - p { - color: var(--footer-selected-link); - white-space: nowrap; - font-size: 14px; - font-weight: 700; - } - } - - &_content { - display: flex; - align-items: center; - gap: 5px; - - .val { - white-space: nowrap; - font-size: 14px; - font-weight: 400; - } - - .coefficient { - white-space: nowrap; - font-size: 14px; - font-weight: 400; - - &__green { - color: #16d1d6; - } - - &__red { - color: #ff6767; - } - } - } - } - - @media screen and (max-width: 600px) { - gap: 0; - flex-wrap: nowrap; - flex-direction: column; - - >div { - width: 100% !important; - padding-left: 0 !important; - border-left: none !important; - - padding: 20px 0 !important; - border-bottom: 1px solid var(--delimiter-color); - } - } - } - - .trading__currency__wrapper { - display: flex; - flex-direction: column; - gap: 15px; - - .trading__currency__wrapper_top { - display: flex; - align-items: center; - gap: 12px; - - .coin__icon { - min-width: 48px; - min-height: 48px; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--icon-bg-color); - border-radius: 50%; - - img { - width: 25px; - height: auto; - } - } - - .coin__currency { - display: flex; - flex-direction: column; - justify-content: space-between; - - >p:first-child { - font-size: 18px; - font-weight: 600; - - span { - color: var(--footer-selected-link); - } - } - - .trading__currency__rate { - display: flex; - align-items: center; - gap: 5px; - - .trading__currency__rate_secondCurrency { - font-weight: 400; - font-size: 14px; - } - - .trading__currency__rate_usd { - color: var(--footer-selected-link); - font-size: 12px; - font-weight: 400; - } - } - } - - @media screen and (max-width: 400px) { - >div:first-child { - width: 48px; - height: 48px; - - >img { - scale: 0.7; - } - } - - >div:last-child { - >p:first-child { - font-size: 24px; - - >span { - font-size: 24px; - } - } - } - } - } - } - } - - .trading__top__wrapper { + &__top { margin-top: 20px; display: flex; gap: 20px; @@ -241,412 +21,19 @@ min-height: 380px; max-height: 500px; - .trading__orders_panel { - max-width: 415px; - width: 100%; - padding: 5px; - background: var(--window-bg-color); - border: 1px solid var(--delimiter-color); - border-radius: 10px; - - @media screen and (max-width: 1480px) { - max-width: 340px; - } - - .tooltip__arrow { - border-top: 1px solid var(--dex-tooltip-border-color); - background-color: var(--dex-tooltip-bg); - } - - .tooltip { - pointer-events: none; - position: fixed; - border: 1px solid var(--dex-tooltip-border-color); - width: 140px; - padding: 10px; - transform: translateX(-50%); - background-color: var(--dex-tooltip-bg); - - h6 { - color: var(--table-th-color); - margin-top: 12px; - font-size: 11px; - font-weight: 700; - - &:first-child { - margin-top: 0; - } - } - - p { - font-size: 12px; - font-weight: 400; - display: flex; - align-items: center; - gap: 5px; - margin-top: 6px; - } - - span { - margin-top: 5px; - display: block; - color: #8d95ae; - font-size: 11px; - font-weight: 400; - } - } - - &__header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 10px; - padding: 10px; - padding-bottom: 10px; - border-bottom: 1px solid var(--delimiter-color); - - &_title { - font-size: 18px; - font-weight: 600; - } - - &_type { - display: flex; - align-items: center; - gap: 8px; - - button { - cursor: pointer; - width: 20px; - height: 20px; - border-radius: 4px; - font-size: 12px; - font-weight: 700; - transition: 0.3s opacity ease; - color: #ffffff; - - &.selected, - &:hover { - opacity: 80%; - } - - &.all { - background: linear-gradient(to left, #ff6767 50%, #16d1d6 50%); - } - - &.buy { - background-color: #16d1d6; - } - - &.sell { - background-color: #ff6767; - } - } - } - } - - .orders__panel_content { - display: flex; - flex-direction: column; - padding-top: 10px; - } - - table { - width: 100%; - - thead { - display: flex; - width: 100%; - padding-inline: 10px; - margin-bottom: 9px; - - tr { - width: 100%; - display: flex; - justify-content: space-between; - - th { - font-size: 11px; - font-weight: 700; - text-align: start; - color: var(--table-th-color); - min-width: 80px; - - &:last-child { - text-align: right; - } - } - } - } - - tbody { - height: 29dvh; - min-height: 265px; - max-height: 380px; - display: flex; - flex-direction: column; - overflow: auto; - padding-bottom: 20px; - padding: 10px; - - tr { - cursor: pointer; - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 4px 0; - - &:nth-child(even) { - background-color: var(--table-even-bg); - } - - &::after { - content: ''; - pointer-events: none; - position: absolute; - z-index: 1; - right: 0; - top: 0; - width: var(--line-width, 0%); - height: 100%; - background: #16d1d61a; - } - - &.sell_section { - &::after { - background: #ff67671a; - } - } - - td { - position: relative; - - &:last-child { - >p { - text-align: right; - } - } - - >p { - min-width: 80px; - width: 100%; - font-size: 12px; - font-weight: 400; - } - } - } - } - } - - .orders__message { - width: 100%; - margin-top: 40px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 20px; - - svg { - transform: scale(0.8); - } - - &.all__orders__msg { - padding-right: 30px; - } - - &.user__orders__msg { - padding-right: 18px; - } - - @media screen and (max-width: 550px) { - &.all__orders__msg { - padding-right: 20px; - } - - &.user__orders__msg { - padding-right: 13px; - } - } - - >h6 { - color: var(--font-dimmed-color); - } - } - } - - .allTrades { - max-width: 415px; - width: 100%; - padding: 5px; - background: var(--window-bg-color); - border: 1px solid var(--delimiter-color); - border-radius: 10px; - - @media screen and (max-width: 1480px) { - max-width: 340px; - } - - &__header { - border-bottom: 1px solid var(--delimiter-color); - display: flex; - align-items: center; - gap: 22px; - padding: 10px; - padding-bottom: 0; - - .navItem { - padding-bottom: 7px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - font-size: 16px; - border-bottom: 2px solid transparent; - font-weight: 600; - background-color: transparent; - cursor: pointer; - - &.active { - border-color: #1f8feb; - } - - &:hover { - color: #1f8feb; - } - } - } - - .orders__panel_content { - display: flex; - flex-direction: column; - padding-top: 10px; - } - - table { - width: 100%; - - thead { - display: flex; - width: 100%; - padding-inline: 10px; - margin-bottom: 9px; - - tr { - width: 100%; - display: flex; - justify-content: space-between; - - th { - min-width: 80px; - font-size: 11px; - font-weight: 700; - text-align: start; - color: var(--table-th-color); - - &:last-child { - text-align: right; - } - } - } - } - - tbody { - height: 29dvh; - min-height: 265px; - max-height: 380px; - display: flex; - flex-direction: column; - overflow: auto; - padding: 10px; - padding-bottom: 20px; - - tr { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 4px 0; - - &:nth-child(even) { - background-color: var(--table-even-bg); - } - - td { - position: relative; - - &:last-child { - >p { - text-align: right; - } - } - - >p { - min-width: 80px; - width: 100%; - font-size: 12px; - font-weight: 400; - } - } - } - } - } - - .orders__message { - width: 100%; - margin-top: 40px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 20px; - - svg { - transform: scale(0.8); - } - - &.all__orders__msg { - padding-right: 30px; - } - - &.user__orders__msg { - padding-right: 18px; - } - - @media screen and (max-width: 550px) { - &.all__orders__msg { - padding-right: 20px; - } - - &.user__orders__msg { - padding-right: 13px; - } - } - - >h6 { - color: var(--font-dimmed-color); - } - } - } - - .trading__chart__wrapper { + &_chart { width: 100%; overflow: hidden; display: flex; flex-direction: column; - .trading__chart__preloader { - height: 100%; - } - - .trading__chart__settings { + .settings { display: flex; align-items: center; justify-content: space-between; - .trading__chart__dropdown { - width: 254px; + &__dropdown { + width: 250px; height: 48px; @media screen and (max-width: 1360px) { @@ -657,7 +44,7 @@ } } - .trading__info { + &__info { display: flex; gap: 20px; margin-bottom: 40px; @@ -668,327 +55,5 @@ gap: 20px; width: 100%; } - - &_createOrder { - width: 100%; - padding: 15px; - background: var(--window-bg-color); - border: 1px solid var(--delimiter-color); - border-radius: 10px; - } - - - - .trading__user__orders { - width: 100%; - padding: 5px; - background: var(--window-bg-color); - border: 1px solid var(--delimiter-color); - border-radius: 10px; - - @media screen and (max-width: 1440px) { - width: 520px; - } - - &__header { - border-bottom: 1px solid var(--delimiter-color); - display: flex; - align-items: center; - justify-content: space-between; - position: relative; - padding: 10px; - padding-bottom: 0; - - &_nav { - display: flex; - align-items: center; - gap: 22px; - - .navItem { - padding-bottom: 7px; - position: relative; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - font-size: 16px; - 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%; - } - - &.active { - border-color: #1f8feb; - } - - &:hover { - color: #1f8feb; - } - } - } - - &_btn { - 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; - } - } - } - - th { - br { - display: none; - } - } - - table { - width: 100%; - - thead { - display: flex; - width: 100%; - padding-inline: 10px; - padding-bottom: 5px; - margin-top: 10px; - - tr { - width: 100%; - display: flex; - justify-content: space-between; - - th { - min-width: 100px; - font-size: 11px; - font-weight: 700; - text-align: start; - color: var(--table-th-color); - - &:last-child { - text-align: right; - min-width: 50px; - } - } - } - } - - tbody { - a { - display: block; - text-align: right; - font-size: 12px; - font-weight: 400; - } - } - } - - .trading__right__tables { - padding: 10px; - padding-bottom: 20px; - height: 300px; - overflow: auto; - - table { - width: 100%; - - &.trading__apply__table { - 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; - - &.stats__table__incoming { - tr { - &:nth-child(even) { - background-color: var(--table-even-bg); - } - } - } - - tr { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 4px 0; - - td { - position: relative; - min-width: 100px; - - &:last-child { - min-width: 50px; - } - - @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; - } - } - } - } - } - } - } - } - - .orders__message { - width: 100%; - margin-top: 40px !important; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 20px; - - svg { - transform: scale(0.8); - } - - &.all__orders__msg { - padding-right: 30px; - } - - &.user__orders__msg { - padding-right: 18px; - } - - @media screen and (max-width: 550px) { - &.all__orders__msg { - padding-right: 20px; - } - - &.user__orders__msg { - padding-right: 13px; - } - } - - >h6 { - color: var(--font-dimmed-color); - } - } } - - .table__tooltip { - position: absolute; - top: 30px; - left: 20%; - transform: translateX(-50%); - background-color: var(--trade-table-tooltip); - - >.table__tooltip_arrow { - background-color: var(--trade-table-tooltip); - } - } - - .table__tooltip_right { - position: absolute; - top: 30px; - left: 2%; - background-color: var(--trade-table-tooltip); - - >.table__tooltip_arrow { - left: 40px; - background-color: var(--trade-table-tooltip); - } - } - - .table__tooltip_end { - position: absolute; - top: 30px; - left: -50%; - background-color: var(--trade-table-tooltip); - - >.table__tooltip_arrow { - border-radius: 2px; - left: 10%; - background-color: var(--trade-table-tooltip); - } - } - - .badge { - width: fit-content; - padding: 1px 4px; - padding-right: 8px; - display: flex; - align-items: center; - gap: 2px; - border-radius: 100px; - background: radial-gradient(100% 246.57% at 0% 0%, #a366ff 0%, #601fff 100%); - - &.icon { - min-width: 15px; - height: 15px; - border-radius: 50%; - justify-content: center; - padding: 0; - - >img { - width: 11px; - height: 11px; - } - } - - >img { - height: 13px; - width: auto; - } - - >span { - font-size: 10px; - font-weight: 600; - } - - &.high { - padding: 2px 4px; - background: radial-gradient(100% 188.88% at 0% 0%, #16d1d6 0%, #274cff 100%); - } - } -} \ No newline at end of file +} diff --git a/src/styles/themes/dark.scss b/src/styles/themes/dark.scss new file mode 100644 index 0000000..3f47466 --- /dev/null +++ b/src/styles/themes/dark.scss @@ -0,0 +1,53 @@ +[data-theme='dark'] { + --main-bg-color: #0c0c3a; + --table-header-font-color: rgba(213, 213, 226, 1); + --table-header-bg: rgba(36, 36, 78, 1); + --table-button-bg-hover: rgba(40, 40, 83, 1); + --font-main-color: rgba(255, 255, 255, 1); + --font-dimmed-color: rgba(141, 149, 174, 1); + --delimiter-color: rgba(255, 255, 255, 0.1); + --window-bg-color: rgba(15, 32, 85, 1); + --dropdown-bg-color: rgba(17, 49, 107, 1); + --dropdown-bg-hover: rgba(29, 59, 114, 1); + --profile-widget-avatar: rgba(17, 49, 107, 1); + --window-border-color: rgba(255, 255, 255, 0.3); + --bordered-input-bg: rgba(255, 255, 255, 0.1); + --row-header-bg: #154d91; + --button-bordered-hover: rgba(255, 255, 255, 0.1); + --alert-bg: rgba(35, 52, 103, 1); + --switch-bg-color: rgba(15, 32, 85, 1); + --switch-bg-hover: rgba(39, 54, 102, 1); + --switch-disabled-bg-color: rgba(255, 255, 255, 0.1); + --dimmed-btn-bg: rgba(255, 255, 255, 0.1); + --dimmed-btn-hover: rgba(255, 255, 255, 0.3); + --font-faded-color: rgba(255, 255, 255, 0.5); + --slider-bg-color: #1d3b72; + --blur-color: rgba(255, 255, 255, 0.05); + --icon-bg-color: #0f1f54; + --swap-btn-bg: rgba(31, 143, 235, 0.1); + --table-bg-color: rgba(16, 16, 64, 1); + --advices-bg-color: rgba(255, 255, 255, 0.1); + --messenger-top-bg: #0f2055; + --messenger-bottom-bg: #273666; + --messenger-bg: #0f2055; + --messenger-border: rgba(255, 255, 255, 0.1); + --message-bg: rgba(255, 255, 255, 0.1); + --custom-message-bg: transparent; + --trade-table-tooltip: #1d3b72; + --dex-offer-notification: #0f2055; + --dex-panel-bg: #0f2055; + --dex-panel-tooltip: #273666; + --dex-buy-sell-border: #ffffff1a; + --dex-input-currency: #39497c; + --footer-selected-link: #ffffffcc; + --table-th-color: #ffffffb2; + --table-thead-bg: #24244e; + --table-tbody-bg: #101040; + --admin-table-border-color: #596f98; + --alert-btn-bg: rgba(31, 143, 235, 0.2); + --alert-btn-hover: rgba(31, 143, 235, 0.3); + --table-even-bg: #0c1d4f; + --table-tr-hover-color: #172a66; + --dex-tooltip-bg: #11316b; + --dex-tooltip-border-color: #1f8feb26; +} diff --git a/src/styles/themes/light.scss b/src/styles/themes/light.scss index 664db18..7809ccf 100644 --- a/src/styles/themes/light.scss +++ b/src/styles/themes/light.scss @@ -51,57 +51,3 @@ --dex-tooltip-bg: #eff8ff; --dex-tooltip-border-color: #1f8feb33; } - -[data-theme='dark'] { - --main-bg-color: #0c0c3a; - --table-header-font-color: rgba(213, 213, 226, 1); - --table-header-bg: rgba(36, 36, 78, 1); - --table-button-bg-hover: rgba(40, 40, 83, 1); - --font-main-color: rgba(255, 255, 255, 1); - --font-dimmed-color: rgba(141, 149, 174, 1); - --delimiter-color: rgba(255, 255, 255, 0.1); - --window-bg-color: rgba(15, 32, 85, 1); - --dropdown-bg-color: rgba(17, 49, 107, 1); - --dropdown-bg-hover: rgba(29, 59, 114, 1); - --profile-widget-avatar: rgba(17, 49, 107, 1); - --window-border-color: rgba(255, 255, 255, 0.3); - --bordered-input-bg: rgba(255, 255, 255, 0.1); - --row-header-bg: #154d91; - --button-bordered-hover: rgba(255, 255, 255, 0.1); - --alert-bg: rgba(35, 52, 103, 1); - --switch-bg-color: rgba(15, 32, 85, 1); - --switch-bg-hover: rgba(39, 54, 102, 1); - --switch-disabled-bg-color: rgba(255, 255, 255, 0.1); - --dimmed-btn-bg: rgba(255, 255, 255, 0.1); - --dimmed-btn-hover: rgba(255, 255, 255, 0.3); - --font-faded-color: rgba(255, 255, 255, 0.5); - --slider-bg-color: #1d3b72; - --blur-color: rgba(255, 255, 255, 0.05); - --icon-bg-color: #0f1f54; - --swap-btn-bg: rgba(31, 143, 235, 0.1); - --table-bg-color: rgba(16, 16, 64, 1); - --advices-bg-color: rgba(255, 255, 255, 0.1); - --messenger-top-bg: #0f2055; - --messenger-bottom-bg: #273666; - --messenger-bg: #0f2055; - --messenger-border: rgba(255, 255, 255, 0.1); - --message-bg: rgba(255, 255, 255, 0.1); - --custom-message-bg: transparent; - --trade-table-tooltip: #1d3b72; - --dex-offer-notification: #0f2055; - --dex-panel-bg: #0f2055; - --dex-panel-tooltip: #273666; - --dex-buy-sell-border: #ffffff1a; - --dex-input-currency: #39497c; - --footer-selected-link: #ffffffcc; - --table-th-color: #ffffffb2; - --table-thead-bg: #24244e; - --table-tbody-bg: #101040; - --admin-table-border-color: #596f98; - --alert-btn-bg: rgba(31, 143, 235, 0.2); - --alert-btn-hover: rgba(31, 143, 235, 0.3); - --table-even-bg: #0c1d4f; - --table-tr-hover-color: #172a66; - --dex-tooltip-bg: #11316b; - --dex-tooltip-border-color: #1f8feb26; -} diff --git a/src/utils/methods.ts b/src/utils/methods.ts index f320543..3ea4880 100644 --- a/src/utils/methods.ts +++ b/src/utils/methods.ts @@ -335,3 +335,15 @@ export async function getTrades(pairId: string) { }) .then((res) => res.data); } + +export async function getMatrixAddresses(addresses: string[]) { + try { + const { data } = await axios.post('https://messenger.zano.org/api/get-addresses', { + addresses, + }); + + return data; + } catch (error) { + console.log(error); + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 5498468..af62af9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -143,9 +143,9 @@ export function formatTime(ts: string | number) { return date.toLocaleTimeString('ru-RU', { hour12: false }); } -export function classes(...items: (string | boolean | undefined)[]): string { +export function classes(...classes: (string | boolean | undefined)[]): string { // boolean for constructions like [predicate] && [className] - return items.filter((className) => className).join(' '); + return classes.filter((className) => className).join(' '); } export const ZANO_ASSET_ID = 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a'; From 5ca9dc4beac89f99962d1321d3c4788db65ed613 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Wed, 13 Aug 2025 14:15:53 +0500 Subject: [PATCH 03/27] fix: build --- .../dex}/AllTrades/index.tsx | 0 .../dex}/AllTrades/styles.module.scss | 0 .../dex}/AllTrades/types.ts | 0 .../dex}/BadgeStatus/index.tsx | 0 .../dex}/BadgeStatus/styles.module.scss | 0 .../dex}/BadgeStatus/types.ts | 0 .../dex}/CandleChart/index.tsx | 0 .../dex}/CandleChart/styles.module.scss | 0 .../dex}/CandleChart/testCandles.json | 0 .../components/LabeledInput/index.tsx | 0 .../LabeledInput/styles.module.scss | 0 .../dex}/InputPanelItem/index.tsx | 0 .../dex}/InputPanelItem/styles.module.scss | 0 .../dex}/MatrixConnectionBadge/index.tsx | 0 .../MatrixConnectionBadge/styles.module.scss | 0 .../dex}/MatrixConnectionBadge/types.ts | 0 .../dex}/OrderRowTooltipCell/index.tsx | 0 .../OrderRowTooltipCell/styles.module.scss | 0 .../dex}/OrderRowTooltipCell/types.ts | 0 .../OrdersPool/components/OrdersRow/index.tsx | 0 .../components/OrdersRow/styles.module.scss | 0 .../OrdersPool/components/OrdersRow/types.ts | 0 .../dex}/OrdersPool/index.tsx | 0 .../dex}/OrdersPool/styles.module.scss | 0 .../dex}/OrdersPool/types.ts | 0 .../dex}/StatItem/index.tsx | 0 .../dex}/StatItem/styles.module.scss | 0 .../dex}/TimeLeft/index.tsx | 0 .../components/AssetRow/index.tsx | 0 .../components/AssetRow/styles.module.scss | 0 .../components/AssetRow/types.ts | 2 +- .../components/CurrencyIcon/index.tsx | 3 ++- .../components/CurrencyIcon/types.ts | 2 +- .../dex}/TradingHeader/index.tsx | 13 ++++------ .../dex}/TradingHeader/styles.module.scss | 0 .../dex}/TradingHeader/types.ts | 0 .../components/MyOrdersApplyRow/index.tsx | 0 .../components/MyOrdersApplyRow/types.ts | 0 .../components/MyOrdersRow/index.tsx | 0 .../components/MyOrdersRow/types.ts | 0 .../dex}/UserOrders/index.tsx | 0 .../dex}/UserOrders/styles.module.scss | 0 .../dex}/UserOrders/types.ts | 0 .../trading/hooks => hook}/useFilteredData.ts | 0 .../hooks => hook}/useMatrixAddresses.ts | 0 .../trading/hooks => hook}/useOrdereForm.ts | 2 +- .../hooks => hook}/useSocketListeners.ts | 0 .../trading/hooks => hook}/useTradeInit.ts | 0 .../trading/hooks => hook}/useTradingData.ts | 0 src/pages/dex/trading/[id].tsx | 24 +++++++++---------- .../helpers => utils}/handleInputChange.ts | 0 .../helpers => utils}/takeOrderClick.ts | 0 52 files changed, 21 insertions(+), 25 deletions(-) rename src/{pages/dex/trading/components => components/dex}/AllTrades/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/AllTrades/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/AllTrades/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/BadgeStatus/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/BadgeStatus/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/BadgeStatus/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/CandleChart/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/CandleChart/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/CandleChart/testCandles.json (100%) rename src/{pages/dex/trading/components => components/dex}/InputPanelItem/components/LabeledInput/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/InputPanelItem/components/LabeledInput/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/InputPanelItem/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/InputPanelItem/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/MatrixConnectionBadge/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/MatrixConnectionBadge/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/MatrixConnectionBadge/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/OrderRowTooltipCell/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/OrderRowTooltipCell/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/OrderRowTooltipCell/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/OrdersPool/components/OrdersRow/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/OrdersPool/components/OrdersRow/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/OrdersPool/components/OrdersRow/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/OrdersPool/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/OrdersPool/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/OrdersPool/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/StatItem/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/StatItem/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/TimeLeft/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/components/AssetRow/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/components/AssetRow/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/components/AssetRow/types.ts (69%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/components/CurrencyIcon/index.tsx (57%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/components/CurrencyIcon/types.ts (61%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/index.tsx (88%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/TradingHeader/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/components/MyOrdersApplyRow/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/components/MyOrdersApplyRow/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/components/MyOrdersRow/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/components/MyOrdersRow/types.ts (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/index.tsx (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/styles.module.scss (100%) rename src/{pages/dex/trading/components => components/dex}/UserOrders/types.ts (100%) rename src/{pages/dex/trading/hooks => hook}/useFilteredData.ts (100%) rename src/{pages/dex/trading/hooks => hook}/useMatrixAddresses.ts (100%) rename src/{pages/dex/trading/hooks => hook}/useOrdereForm.ts (97%) rename src/{pages/dex/trading/hooks => hook}/useSocketListeners.ts (100%) rename src/{pages/dex/trading/hooks => hook}/useTradeInit.ts (100%) rename src/{pages/dex/trading/hooks => hook}/useTradingData.ts (100%) rename src/{pages/dex/trading/helpers => utils}/handleInputChange.ts (100%) rename src/{pages/dex/trading/helpers => utils}/takeOrderClick.ts (100%) diff --git a/src/pages/dex/trading/components/AllTrades/index.tsx b/src/components/dex/AllTrades/index.tsx similarity index 100% rename from src/pages/dex/trading/components/AllTrades/index.tsx rename to src/components/dex/AllTrades/index.tsx diff --git a/src/pages/dex/trading/components/AllTrades/styles.module.scss b/src/components/dex/AllTrades/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/AllTrades/styles.module.scss rename to src/components/dex/AllTrades/styles.module.scss diff --git a/src/pages/dex/trading/components/AllTrades/types.ts b/src/components/dex/AllTrades/types.ts similarity index 100% rename from src/pages/dex/trading/components/AllTrades/types.ts rename to src/components/dex/AllTrades/types.ts diff --git a/src/pages/dex/trading/components/BadgeStatus/index.tsx b/src/components/dex/BadgeStatus/index.tsx similarity index 100% rename from src/pages/dex/trading/components/BadgeStatus/index.tsx rename to src/components/dex/BadgeStatus/index.tsx diff --git a/src/pages/dex/trading/components/BadgeStatus/styles.module.scss b/src/components/dex/BadgeStatus/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/BadgeStatus/styles.module.scss rename to src/components/dex/BadgeStatus/styles.module.scss diff --git a/src/pages/dex/trading/components/BadgeStatus/types.ts b/src/components/dex/BadgeStatus/types.ts similarity index 100% rename from src/pages/dex/trading/components/BadgeStatus/types.ts rename to src/components/dex/BadgeStatus/types.ts diff --git a/src/pages/dex/trading/components/CandleChart/index.tsx b/src/components/dex/CandleChart/index.tsx similarity index 100% rename from src/pages/dex/trading/components/CandleChart/index.tsx rename to src/components/dex/CandleChart/index.tsx diff --git a/src/pages/dex/trading/components/CandleChart/styles.module.scss b/src/components/dex/CandleChart/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/CandleChart/styles.module.scss rename to src/components/dex/CandleChart/styles.module.scss diff --git a/src/pages/dex/trading/components/CandleChart/testCandles.json b/src/components/dex/CandleChart/testCandles.json similarity index 100% rename from src/pages/dex/trading/components/CandleChart/testCandles.json rename to src/components/dex/CandleChart/testCandles.json diff --git a/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/index.tsx b/src/components/dex/InputPanelItem/components/LabeledInput/index.tsx similarity index 100% rename from src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/index.tsx rename to src/components/dex/InputPanelItem/components/LabeledInput/index.tsx diff --git a/src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/styles.module.scss b/src/components/dex/InputPanelItem/components/LabeledInput/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/InputPanelItem/components/LabeledInput/styles.module.scss rename to src/components/dex/InputPanelItem/components/LabeledInput/styles.module.scss diff --git a/src/pages/dex/trading/components/InputPanelItem/index.tsx b/src/components/dex/InputPanelItem/index.tsx similarity index 100% rename from src/pages/dex/trading/components/InputPanelItem/index.tsx rename to src/components/dex/InputPanelItem/index.tsx diff --git a/src/pages/dex/trading/components/InputPanelItem/styles.module.scss b/src/components/dex/InputPanelItem/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/InputPanelItem/styles.module.scss rename to src/components/dex/InputPanelItem/styles.module.scss diff --git a/src/pages/dex/trading/components/MatrixConnectionBadge/index.tsx b/src/components/dex/MatrixConnectionBadge/index.tsx similarity index 100% rename from src/pages/dex/trading/components/MatrixConnectionBadge/index.tsx rename to src/components/dex/MatrixConnectionBadge/index.tsx diff --git a/src/pages/dex/trading/components/MatrixConnectionBadge/styles.module.scss b/src/components/dex/MatrixConnectionBadge/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/MatrixConnectionBadge/styles.module.scss rename to src/components/dex/MatrixConnectionBadge/styles.module.scss diff --git a/src/pages/dex/trading/components/MatrixConnectionBadge/types.ts b/src/components/dex/MatrixConnectionBadge/types.ts similarity index 100% rename from src/pages/dex/trading/components/MatrixConnectionBadge/types.ts rename to src/components/dex/MatrixConnectionBadge/types.ts diff --git a/src/pages/dex/trading/components/OrderRowTooltipCell/index.tsx b/src/components/dex/OrderRowTooltipCell/index.tsx similarity index 100% rename from src/pages/dex/trading/components/OrderRowTooltipCell/index.tsx rename to src/components/dex/OrderRowTooltipCell/index.tsx diff --git a/src/pages/dex/trading/components/OrderRowTooltipCell/styles.module.scss b/src/components/dex/OrderRowTooltipCell/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/OrderRowTooltipCell/styles.module.scss rename to src/components/dex/OrderRowTooltipCell/styles.module.scss diff --git a/src/pages/dex/trading/components/OrderRowTooltipCell/types.ts b/src/components/dex/OrderRowTooltipCell/types.ts similarity index 100% rename from src/pages/dex/trading/components/OrderRowTooltipCell/types.ts rename to src/components/dex/OrderRowTooltipCell/types.ts diff --git a/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/index.tsx b/src/components/dex/OrdersPool/components/OrdersRow/index.tsx similarity index 100% rename from src/pages/dex/trading/components/OrdersPool/components/OrdersRow/index.tsx rename to src/components/dex/OrdersPool/components/OrdersRow/index.tsx diff --git a/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/styles.module.scss b/src/components/dex/OrdersPool/components/OrdersRow/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/OrdersPool/components/OrdersRow/styles.module.scss rename to src/components/dex/OrdersPool/components/OrdersRow/styles.module.scss diff --git a/src/pages/dex/trading/components/OrdersPool/components/OrdersRow/types.ts b/src/components/dex/OrdersPool/components/OrdersRow/types.ts similarity index 100% rename from src/pages/dex/trading/components/OrdersPool/components/OrdersRow/types.ts rename to src/components/dex/OrdersPool/components/OrdersRow/types.ts diff --git a/src/pages/dex/trading/components/OrdersPool/index.tsx b/src/components/dex/OrdersPool/index.tsx similarity index 100% rename from src/pages/dex/trading/components/OrdersPool/index.tsx rename to src/components/dex/OrdersPool/index.tsx diff --git a/src/pages/dex/trading/components/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/OrdersPool/styles.module.scss rename to src/components/dex/OrdersPool/styles.module.scss diff --git a/src/pages/dex/trading/components/OrdersPool/types.ts b/src/components/dex/OrdersPool/types.ts similarity index 100% rename from src/pages/dex/trading/components/OrdersPool/types.ts rename to src/components/dex/OrdersPool/types.ts diff --git a/src/pages/dex/trading/components/StatItem/index.tsx b/src/components/dex/StatItem/index.tsx similarity index 100% rename from src/pages/dex/trading/components/StatItem/index.tsx rename to src/components/dex/StatItem/index.tsx diff --git a/src/pages/dex/trading/components/StatItem/styles.module.scss b/src/components/dex/StatItem/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/StatItem/styles.module.scss rename to src/components/dex/StatItem/styles.module.scss diff --git a/src/pages/dex/trading/components/TimeLeft/index.tsx b/src/components/dex/TimeLeft/index.tsx similarity index 100% rename from src/pages/dex/trading/components/TimeLeft/index.tsx rename to src/components/dex/TimeLeft/index.tsx diff --git a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/index.tsx b/src/components/dex/TradingHeader/components/AssetRow/index.tsx similarity index 100% rename from src/pages/dex/trading/components/TradingHeader/components/AssetRow/index.tsx rename to src/components/dex/TradingHeader/components/AssetRow/index.tsx diff --git a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/styles.module.scss b/src/components/dex/TradingHeader/components/AssetRow/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/TradingHeader/components/AssetRow/styles.module.scss rename to src/components/dex/TradingHeader/components/AssetRow/styles.module.scss diff --git a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts b/src/components/dex/TradingHeader/components/AssetRow/types.ts similarity index 69% rename from src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts rename to src/components/dex/TradingHeader/components/AssetRow/types.ts index 354ce55..bb27d6b 100644 --- a/src/pages/dex/trading/components/TradingHeader/components/AssetRow/types.ts +++ b/src/components/dex/TradingHeader/components/AssetRow/types.ts @@ -2,5 +2,5 @@ export interface AssetRowProps { name: string; link: string; id: string; - code: string | undefined; + code: string | undefined | null; } diff --git a/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx b/src/components/dex/TradingHeader/components/CurrencyIcon/index.tsx similarity index 57% rename from src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx rename to src/components/dex/TradingHeader/components/CurrencyIcon/index.tsx index 95e2d80..927e6bb 100644 --- a/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/index.tsx +++ b/src/components/dex/TradingHeader/components/CurrencyIcon/index.tsx @@ -1,8 +1,9 @@ import Image from 'next/image'; +import { getAssetIcon } from '@/utils/utils'; import { CurrencyIconProps } from './types'; const CurrencyIcon = ({ code, size = 50 }: CurrencyIconProps) => ( - currency + currency ); export default CurrencyIcon; diff --git a/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts b/src/components/dex/TradingHeader/components/CurrencyIcon/types.ts similarity index 61% rename from src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts rename to src/components/dex/TradingHeader/components/CurrencyIcon/types.ts index 75dc2f0..f181779 100644 --- a/src/pages/dex/trading/components/TradingHeader/components/CurrencyIcon/types.ts +++ b/src/components/dex/TradingHeader/components/CurrencyIcon/types.ts @@ -1,4 +1,4 @@ export interface CurrencyIconProps { - code: string | undefined; + code: string | undefined | null; size?: number; } diff --git a/src/pages/dex/trading/components/TradingHeader/index.tsx b/src/components/dex/TradingHeader/index.tsx similarity index 88% rename from src/pages/dex/trading/components/TradingHeader/index.tsx rename to src/components/dex/TradingHeader/index.tsx index 32d1f47..9658c1b 100644 --- a/src/pages/dex/trading/components/TradingHeader/index.tsx +++ b/src/components/dex/TradingHeader/index.tsx @@ -3,16 +3,13 @@ import { ReactComponent as UpIcon } from '@/assets/images/UI/up_icon.svg'; import { ReactComponent as DownIcon } from '@/assets/images/UI/down_icon.svg'; import { ReactComponent as VolumeIcon } from '@/assets/images/UI/volume_icon.svg'; import BackButton from '@/components/default/BackButton/BackButton'; -import { tradingKnownCurrencies, roundTo, notationToString } from '@/utils/utils'; +import { roundTo, notationToString } from '@/utils/utils'; import styles from './styles.module.scss'; import StatItem from '../StatItem'; import { TradingHeaderProps } from './types'; import CurrencyIcon from './components/CurrencyIcon'; import AssetRow from './components/AssetRow'; -const getCurrencyCode = (code?: string) => - tradingKnownCurrencies.includes(code || '') ? code : 'tsds'; - const TradingHeader = ({ pairStats, pairRateUsd, @@ -28,8 +25,6 @@ const TradingHeader = ({ }; const { firstCurrencyName, secondCurrencyName } = currencyNames; - const imgCode = getCurrencyCode(pairData?.first_currency?.code || ''); - const imgCode2 = getCurrencyCode(pairData?.second_currency?.code || ''); const coefficient = pairStats?.coefficient || 0; const coefficientOutput = @@ -65,7 +60,7 @@ const TradingHeader = ({
- +
@@ -96,13 +91,13 @@ const TradingHeader = ({ name={firstCurrencyName} link={firstAssetLink} id={firstAssetId || ''} - code={imgCode} + code={firstAssetId} />
)} diff --git a/src/pages/dex/trading/components/TradingHeader/styles.module.scss b/src/components/dex/TradingHeader/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/TradingHeader/styles.module.scss rename to src/components/dex/TradingHeader/styles.module.scss diff --git a/src/pages/dex/trading/components/TradingHeader/types.ts b/src/components/dex/TradingHeader/types.ts similarity index 100% rename from src/pages/dex/trading/components/TradingHeader/types.ts rename to src/components/dex/TradingHeader/types.ts diff --git a/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/index.tsx b/src/components/dex/UserOrders/components/MyOrdersApplyRow/index.tsx similarity index 100% rename from src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/index.tsx rename to src/components/dex/UserOrders/components/MyOrdersApplyRow/index.tsx diff --git a/src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/types.ts b/src/components/dex/UserOrders/components/MyOrdersApplyRow/types.ts similarity index 100% rename from src/pages/dex/trading/components/UserOrders/components/MyOrdersApplyRow/types.ts rename to src/components/dex/UserOrders/components/MyOrdersApplyRow/types.ts diff --git a/src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/index.tsx b/src/components/dex/UserOrders/components/MyOrdersRow/index.tsx similarity index 100% rename from src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/index.tsx rename to src/components/dex/UserOrders/components/MyOrdersRow/index.tsx diff --git a/src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/types.ts b/src/components/dex/UserOrders/components/MyOrdersRow/types.ts similarity index 100% rename from src/pages/dex/trading/components/UserOrders/components/MyOrdersRow/types.ts rename to src/components/dex/UserOrders/components/MyOrdersRow/types.ts diff --git a/src/pages/dex/trading/components/UserOrders/index.tsx b/src/components/dex/UserOrders/index.tsx similarity index 100% rename from src/pages/dex/trading/components/UserOrders/index.tsx rename to src/components/dex/UserOrders/index.tsx diff --git a/src/pages/dex/trading/components/UserOrders/styles.module.scss b/src/components/dex/UserOrders/styles.module.scss similarity index 100% rename from src/pages/dex/trading/components/UserOrders/styles.module.scss rename to src/components/dex/UserOrders/styles.module.scss diff --git a/src/pages/dex/trading/components/UserOrders/types.ts b/src/components/dex/UserOrders/types.ts similarity index 100% rename from src/pages/dex/trading/components/UserOrders/types.ts rename to src/components/dex/UserOrders/types.ts diff --git a/src/pages/dex/trading/hooks/useFilteredData.ts b/src/hook/useFilteredData.ts similarity index 100% rename from src/pages/dex/trading/hooks/useFilteredData.ts rename to src/hook/useFilteredData.ts diff --git a/src/pages/dex/trading/hooks/useMatrixAddresses.ts b/src/hook/useMatrixAddresses.ts similarity index 100% rename from src/pages/dex/trading/hooks/useMatrixAddresses.ts rename to src/hook/useMatrixAddresses.ts diff --git a/src/pages/dex/trading/hooks/useOrdereForm.ts b/src/hook/useOrdereForm.ts similarity index 97% rename from src/pages/dex/trading/hooks/useOrdereForm.ts rename to src/hook/useOrdereForm.ts index 902b6d9..b4adc3a 100644 --- a/src/pages/dex/trading/hooks/useOrdereForm.ts +++ b/src/hook/useOrdereForm.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import Decimal from 'decimal.js'; import PairData from '@/interfaces/common/PairData'; import OrderFormOutput from '@/interfaces/common/orderFormOutput'; -import { handleInputChange } from '../helpers/handleInputChange'; +import { handleInputChange } from '@/utils/handleInputChange'; interface UseOrderFormParams { type: 'buy' | 'sell'; diff --git a/src/pages/dex/trading/hooks/useSocketListeners.ts b/src/hook/useSocketListeners.ts similarity index 100% rename from src/pages/dex/trading/hooks/useSocketListeners.ts rename to src/hook/useSocketListeners.ts diff --git a/src/pages/dex/trading/hooks/useTradeInit.ts b/src/hook/useTradeInit.ts similarity index 100% rename from src/pages/dex/trading/hooks/useTradeInit.ts rename to src/hook/useTradeInit.ts diff --git a/src/pages/dex/trading/hooks/useTradingData.ts b/src/hook/useTradingData.ts similarity index 100% rename from src/pages/dex/trading/hooks/useTradingData.ts rename to src/hook/useTradingData.ts diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 7ef62e0..fa1f7c3 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -18,18 +18,18 @@ import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; import { periods, buySellValues } from '@/constants'; import { useAlert } from '@/hook/useAlert'; import useScroll from '@/hook/useScroll'; -import CandleChart from './components/CandleChart'; -import InputPanelItem from './components/InputPanelItem'; -import TradingHeader from './components/TradingHeader'; -import UserOrders from './components/UserOrders'; -import AllTrades from './components/AllTrades'; -import OrdersPool from './components/OrdersPool'; -import { useSocketListeners } from './hooks/useSocketListeners'; -import { useTradingData } from './hooks/useTradingData'; -import takeOrderClick from './helpers/takeOrderClick'; -import useFilteredData from './hooks/useFilteredData'; -import useTradeInit from './hooks/useTradeInit'; -import useMatrixAddresses from './hooks/useMatrixAddresses'; +import InputPanelItem from '@/components/dex/InputPanelItem'; +import TradingHeader from '@/components/dex/TradingHeader'; +import UserOrders from '@/components/dex/UserOrders'; +import OrdersPool from '@/components/dex/OrdersPool'; +import CandleChart from '@/components/dex/CandleChart'; +import AllTrades from '@/components/dex/AllTrades'; +import { useSocketListeners } from '@/hook/useSocketListeners'; +import { useTradingData } from '@/hook/useTradingData'; +import useFilteredData from '@/hook/useFilteredData'; +import useTradeInit from '@/hook/useTradeInit'; +import useMatrixAddresses from '@/hook/useMatrixAddresses'; +import takeOrderClick from '@/utils/takeOrderClick'; const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }]; const DEFAULT_CHART = CHART_OPTIONS[0]; diff --git a/src/pages/dex/trading/helpers/handleInputChange.ts b/src/utils/handleInputChange.ts similarity index 100% rename from src/pages/dex/trading/helpers/handleInputChange.ts rename to src/utils/handleInputChange.ts diff --git a/src/pages/dex/trading/helpers/takeOrderClick.ts b/src/utils/takeOrderClick.ts similarity index 100% rename from src/pages/dex/trading/helpers/takeOrderClick.ts rename to src/utils/takeOrderClick.ts From 1e1f7e4bc81274352b30b89590f2090cd5ee38ec Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Thu, 14 Aug 2025 21:53:09 +0500 Subject: [PATCH 04/27] change: zano price decimals to 4 --- src/components/default/PairsTable/PairsTable.tsx | 4 ++-- src/components/dex/TradingHeader/index.tsx | 11 ++++++----- src/hook/useTradeInit.ts | 4 +--- src/pages/dex/pairs/PairsCard/PairsCard.tsx | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/default/PairsTable/PairsTable.tsx b/src/components/default/PairsTable/PairsTable.tsx index 0891919..94611b7 100644 --- a/src/components/default/PairsTable/PairsTable.tsx +++ b/src/components/default/PairsTable/PairsTable.tsx @@ -106,7 +106,7 @@ function PairsTable({ data }: IProps) { cell: ({ row }) => (
- {roundTo(notationToString(row.original.price), 2)} + {roundTo(notationToString(row.original.price), 4)}
{row.original.priceUSD}
@@ -149,7 +149,7 @@ function PairsTable({ data }: IProps) { cell: ({ row }) => (
- {roundTo(notationToString(row.original?.volume ?? 0), 2)} + {roundTo(notationToString(row.original?.volume ?? 0), 4)}
{row.original.volumeUSD}
diff --git a/src/components/dex/TradingHeader/index.tsx b/src/components/dex/TradingHeader/index.tsx index 9658c1b..1e48e06 100644 --- a/src/components/dex/TradingHeader/index.tsx +++ b/src/components/dex/TradingHeader/index.tsx @@ -36,23 +36,23 @@ const TradingHeader = ({ { Img: ClockIcon, title: '24h change', - value: `${roundTo(notationToString(pairStats?.rate || 0), 8)} ${secondCurrencyName}`, + value: `${roundTo(notationToString(pairStats?.rate || 0), 4)} ${secondCurrencyName}`, coefficient: coefficientOutput, }, { Img: UpIcon, title: '24h high', - value: `${roundTo(notationToString(pairStats?.high || 0), 8)} ${secondCurrencyName}`, + value: `${roundTo(notationToString(pairStats?.high || 0), 4)} ${secondCurrencyName}`, }, { Img: DownIcon, title: '24h low', - value: `${roundTo(notationToString(pairStats?.low || 0), 8)} ${secondCurrencyName}`, + value: `${roundTo(notationToString(pairStats?.low || 0), 4)} ${secondCurrencyName}`, }, { Img: VolumeIcon, title: '24h volume', - value: `${roundTo(notationToString(pairStats?.volume || 0), 8)} ${secondCurrencyName}`, + value: `${roundTo(notationToString(pairStats?.volume || 0), 4)} ${secondCurrencyName}`, }, ]; @@ -77,7 +77,8 @@ const TradingHeader = ({

- {notationToString(pairStats?.rate || 0)} {secondCurrencyName} + {roundTo(notationToString(pairStats?.rate || 0), 4)}{' '} + {secondCurrencyName}

{pairRateUsd &&

~ ${pairRateUsd}

}
diff --git a/src/hook/useTradeInit.ts b/src/hook/useTradeInit.ts index 4b5dc9b..f3e2732 100644 --- a/src/hook/useTradeInit.ts +++ b/src/hook/useTradeInit.ts @@ -35,9 +35,7 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => { const pairRateUsd = pairStats?.rate !== undefined && secondAssetUsdPrice !== undefined - ? new Decimal(pairStats.rate) - .mul(secondAssetUsdPrice) - .toFixed(pairStats.rate < 0.1 ? 6 : 2) + ? new Decimal(pairStats.rate).mul(secondAssetUsdPrice).toFixed(2) : undefined; const buyForm = useOrderForm({ diff --git a/src/pages/dex/pairs/PairsCard/PairsCard.tsx b/src/pages/dex/pairs/PairsCard/PairsCard.tsx index c0b5c2d..9e92456 100644 --- a/src/pages/dex/pairs/PairsCard/PairsCard.tsx +++ b/src/pages/dex/pairs/PairsCard/PairsCard.tsx @@ -22,7 +22,7 @@ export default function PairsCard({ pair }: IProps) { const secondAssetUsdPrice = state.assetsRates.get(secondCurrency.asset_id || '') ?? 0; - const price = Number(roundTo(notationToString(pair.rate ?? 0), 2)); + const price = Number(roundTo(notationToString(pair.rate ?? 0), 4)); const currentPriceUSD = secondAssetUsdPrice ? price : 0; const priceUSD = currentPriceUSD ? String(`$${(secondAssetUsdPrice * price).toFixed(2)}`) @@ -34,7 +34,7 @@ export default function PairsCard({ pair }: IProps) { ? -99.99 : parseFloat(coefficient?.toFixed(2) || '0'); - const volume = Number(roundTo(notationToString(pair.volume ?? 0), 2)); + const volume = Number(roundTo(notationToString(pair.volume ?? 0), 4)); const currentVolumeUSD = secondAssetUsdPrice ? volume : 0; const volumeUSD = currentVolumeUSD ? String(`$${(secondAssetUsdPrice * volume).toFixed(2)}`) From d4a703f5a8062be01188066dfef69b5f4e61cfaf Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Fri, 22 Aug 2025 14:46:35 +0500 Subject: [PATCH 05/27] feat: redesign and grouping orders table --- src/components/UI/ActionBtn/index.tsx | 14 + .../UI/ActionBtn/styles.module.scss | 34 ++ src/components/UI/ActionBtn/types.ts | 5 + src/components/default/GenericTable/index.tsx | 95 +++++ src/components/default/GenericTable/types.ts | 21 + .../dex/MatrixConnectionBadge/index.tsx | 71 +++- .../MatrixConnectionBadge/styles.module.scss | 1 + .../dex/UserOrders/columns/index.tsx | 386 ++++++++++++++++++ .../dex/UserOrders/columns/types.ts | 34 ++ .../UserOrders/components/AliasCell/index.tsx | 47 +++ .../UserOrders/components/AliasCell/types.ts | 9 + .../components/CancelActionCell/index.tsx | 41 ++ .../components/CancelActionCell/types.ts | 5 + .../components/MyOrdersApplyRow/index.tsx | 231 ----------- .../components/MyOrdersApplyRow/types.ts | 16 - .../components/MyOrdersRow/index.tsx | 140 ------- .../components/MyOrdersRow/types.ts | 13 - .../components/RequestActionCell/index.tsx | 124 ++++++ .../components/RequestActionCell/types.ts | 12 + .../components/TotalUsdCell/index.tsx | 18 + .../components/TotalUsdCell/types.ts | 5 + src/components/dex/UserOrders/index.tsx | 373 ++++++++++++----- .../dex/UserOrders/styles.module.scss | 183 +++------ src/components/dex/UserOrders/types.ts | 13 +- src/hook/useTradeInit.ts | 2 - src/interfaces/common/UserPendingType.ts | 12 + src/pages/dex/trading/[id].tsx | 60 ++- src/styles/Trading.module.scss | 1 - src/styles/globals.scss | 3 +- src/utils/methods.ts | 24 +- src/utils/utils.ts | 15 + 31 files changed, 1319 insertions(+), 689 deletions(-) create mode 100644 src/components/UI/ActionBtn/index.tsx create mode 100644 src/components/UI/ActionBtn/styles.module.scss create mode 100644 src/components/UI/ActionBtn/types.ts create mode 100644 src/components/default/GenericTable/index.tsx create mode 100644 src/components/default/GenericTable/types.ts create mode 100644 src/components/dex/UserOrders/columns/index.tsx create mode 100644 src/components/dex/UserOrders/columns/types.ts create mode 100644 src/components/dex/UserOrders/components/AliasCell/index.tsx create mode 100644 src/components/dex/UserOrders/components/AliasCell/types.ts create mode 100644 src/components/dex/UserOrders/components/CancelActionCell/index.tsx create mode 100644 src/components/dex/UserOrders/components/CancelActionCell/types.ts delete mode 100644 src/components/dex/UserOrders/components/MyOrdersApplyRow/index.tsx delete mode 100644 src/components/dex/UserOrders/components/MyOrdersApplyRow/types.ts delete mode 100644 src/components/dex/UserOrders/components/MyOrdersRow/index.tsx delete mode 100644 src/components/dex/UserOrders/components/MyOrdersRow/types.ts create mode 100644 src/components/dex/UserOrders/components/RequestActionCell/index.tsx create mode 100644 src/components/dex/UserOrders/components/RequestActionCell/types.ts create mode 100644 src/components/dex/UserOrders/components/TotalUsdCell/index.tsx create mode 100644 src/components/dex/UserOrders/components/TotalUsdCell/types.ts create mode 100644 src/interfaces/common/UserPendingType.ts 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(' '); From 61b3208e0dcb3629ef7b0096f0236400ff428758 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Fri, 22 Aug 2025 18:01:46 +0500 Subject: [PATCH 06/27] update: my requests orders --- src/components/dex/InputPanelItem/index.tsx | 3 ++- .../dex/UserOrders/columns/index.tsx | 21 ++++++++++++++++++- .../dex/UserOrders/columns/types.ts | 1 + src/components/dex/UserOrders/index.tsx | 16 ++++---------- src/components/dex/UserOrders/types.ts | 5 ++--- src/interfaces/common/UserPendingType.ts | 7 +++++++ .../InputPanelItem/InputPanelItemProps.ts | 1 + src/pages/dex/trading/[id].tsx | 14 ++++++++++--- 8 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/components/dex/InputPanelItem/index.tsx b/src/components/dex/InputPanelItem/index.tsx index 1176bc4..93a1a79 100644 --- a/src/components/dex/InputPanelItem/index.tsx +++ b/src/components/dex/InputPanelItem/index.tsx @@ -34,6 +34,7 @@ function InputPanelItem(props: InputPanelItemProps) { totalUsd, scrollToOrderList, currencyNames, + onAfter, } = props; const { state } = useContext(Store); @@ -79,7 +80,7 @@ function InputPanelItem(props: InputPanelItemProps) { if (result.data?.immediateMatch) { setHasImmediateMatch(true); } - + onAfter(); resetForm(); } else { setAlertState('error'); diff --git a/src/components/dex/UserOrders/columns/index.tsx b/src/components/dex/UserOrders/columns/index.tsx index 092a917..73bf3a8 100644 --- a/src/components/dex/UserOrders/columns/index.tsx +++ b/src/components/dex/UserOrders/columns/index.tsx @@ -230,6 +230,7 @@ export function buildApplyTipsColumns({ export function buildMyRequestsColumns({ firstCurrencyName, secondCurrencyName, + matrixAddresses, onAfter, }: BuildMyRequestsColumnsArgs): ColumnDef[] { return [ @@ -264,6 +265,24 @@ export function buildMyRequestsColumns({

), }, + { + 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}), @@ -379,7 +398,7 @@ export function buildOrderHistoryColumns({ { key: 'time', header: 'Time', - width: '180px', + width: '100px', cell: (row) =>

{formatTimestamp(row.timestamp)}

, }, ]; diff --git a/src/components/dex/UserOrders/columns/types.ts b/src/components/dex/UserOrders/columns/types.ts index 63a6624..9ca3468 100644 --- a/src/components/dex/UserOrders/columns/types.ts +++ b/src/components/dex/UserOrders/columns/types.ts @@ -24,6 +24,7 @@ export interface BuildApplyTipsColumnsArgs { export interface BuildMyRequestsColumnsArgs { firstCurrencyName: string; secondCurrencyName: string; + matrixAddresses: MatrixAddress[]; onAfter: () => Promise; } diff --git a/src/components/dex/UserOrders/index.tsx b/src/components/dex/UserOrders/index.tsx index 008d241..b360b9e 100644 --- a/src/components/dex/UserOrders/index.tsx +++ b/src/components/dex/UserOrders/index.tsx @@ -30,9 +30,7 @@ const UserOrders = ({ orderListRef, matrixAddresses, secondAssetUsdPrice, - updateOrders, - updateUserOrders, - fetchTrades, + onAfter, pairData, }: UserOrdersProps) => { const { state } = useContext(Store); @@ -79,13 +77,6 @@ const UserOrders = ({ 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) { @@ -104,7 +95,7 @@ const UserOrders = ({ offersCountByOrderId, onAfter, }), - [firstCurrencyName, secondCurrencyName, secondAssetUsdPrice, offersCountByOrderId, onAfter], + [userOrders, applyTips, onAfter], ); const columnsSuitables = useMemo( @@ -135,9 +126,10 @@ const UserOrders = ({ buildMyRequestsColumns({ firstCurrencyName, secondCurrencyName, + matrixAddresses, onAfter, }), - [firstCurrencyName, secondCurrencyName, onAfter], + [firstCurrencyName, secondCurrencyName, onAfter, matrixAddresses], ); const columnsOffers = useMemo( diff --git a/src/components/dex/UserOrders/types.ts b/src/components/dex/UserOrders/types.ts index 92aaf97..1ba1cd0 100644 --- a/src/components/dex/UserOrders/types.ts +++ b/src/components/dex/UserOrders/types.ts @@ -2,6 +2,7 @@ import ApplyTip from '@/interfaces/common/ApplyTip'; import MatrixAddress from '@/interfaces/common/MatrixAddress'; import OrderRow from '@/interfaces/common/OrderRow'; import PairData from '@/interfaces/common/PairData'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; import { Dispatch, ForwardedRef, SetStateAction } from 'react'; export type OrderType = 'opened' | 'suitable' | 'requests' | 'offers' | 'history'; @@ -21,9 +22,7 @@ export interface UserOrdersProps { setOrdersType: Dispatch>; handleCancelAllOrders: () => void; secondAssetUsdPrice: number | undefined; - updateOrders: () => Promise; - updateUserOrders: () => Promise; - fetchTrades: () => Promise; matrixAddresses: MatrixAddress[]; pairData: PairData | null; + onAfter: () => Promise; } diff --git a/src/interfaces/common/UserPendingType.ts b/src/interfaces/common/UserPendingType.ts index bc51be8..f8b8690 100644 --- a/src/interfaces/common/UserPendingType.ts +++ b/src/interfaces/common/UserPendingType.ts @@ -1,6 +1,13 @@ interface UserPendingType { id: number; amount: string; + price: string; + finalizer: { + address: string; + alias: string; + id: number; + order_id: number; + }; buy_order_id: number; sell_order_id: number; creator: 'sell' | 'buy'; diff --git a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts index d3897ea..67bb931 100644 --- a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts +++ b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts @@ -21,6 +21,7 @@ interface InputPanelItemProps { totalValid: boolean; totalUsd: string | undefined; scrollToOrderList: () => void; + onAfter: () => Promise; } export default InputPanelItemProps; diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 829b827..0702ac2 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -31,6 +31,7 @@ import useTradeInit from '@/hook/useTradeInit'; import useMatrixAddresses from '@/hook/useMatrixAddresses'; import takeOrderClick from '@/utils/takeOrderClick'; import { OrderType } from '@/components/dex/UserOrders/types'; +import useUpdateUser from '@/hook/useUpdateUser'; const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }]; const DEFAULT_CHART = CHART_OPTIONS[0]; @@ -42,6 +43,7 @@ function Trading() { const { elementRef: orderFormRef, scrollToElement: scrollToOrderForm } = useScroll(); + const fetchUser = useUpdateUser(); const [ordersHistory, setOrdersHistory] = useState([]); const [userOrders, setUserOrders] = useState([]); const [periodsState, setPeriodsState] = useState(periods[0]); @@ -139,6 +141,13 @@ function Trading() { tradesType, }); + const onAfter = async () => { + await updateOrders(); + await updateUserOrders(); + await fetchUser(); + await fetchTrades(); + }; + return ( <>
@@ -209,10 +218,8 @@ function Trading() { handleCancelAllOrders={handleCancelAllOrders} matrixAddresses={matrixAddresses} secondAssetUsdPrice={secondAssetUsdPrice} - updateOrders={updateOrders} - updateUserOrders={updateUserOrders} - fetchTrades={fetchTrades} pairData={pairData} + onAfter={onAfter} />
@@ -239,6 +246,7 @@ function Trading() { totalValid={form.totalValid} totalUsd={form.totalUsd} scrollToOrderList={scrollToOrdersList} + onAfter={onAfter} /> ); })} From 04abd67496a88165e6f485c43d77767cfa631b0b Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Mon, 25 Aug 2025 17:51:34 +0500 Subject: [PATCH 07/27] WIP: dex redesign --- src/components/UI/Tabs/index.tsx | 22 ++ src/components/UI/Tabs/styles.module.scss | 30 ++ src/components/UI/Tabs/types.ts | 13 + src/components/default/GenericTable/index.tsx | 6 +- src/components/default/GenericTable/types.ts | 5 + src/components/dex/AliasCell/index.tsx | 96 +++++++ .../dex/AliasCell/styles.module.scss | 33 +++ .../components => }/AliasCell/types.ts | 0 .../dex/CandleChart/styles.module.scss | 2 - .../components/LabeledInput/index.tsx | 21 +- .../LabeledInput/styles.module.scss | 35 ++- src/components/dex/InputPanelItem/index.tsx | 117 +++++--- .../dex/InputPanelItem/styles.module.scss | 64 ++++- .../dex/MatrixConnectionBadge/index.tsx | 16 +- .../MatrixConnectionBadge/styles.module.scss | 7 +- .../dex/MatrixConnectionBadge/types.ts | 1 + .../dex/OrdersPool/columns/index.tsx | 83 ++++++ .../dex/OrdersPool/columns/types.ts | 12 + .../OrdersPool/components/OrdersRow/index.tsx | 54 ---- .../components/OrdersRow/styles.module.scss | 48 ---- .../OrdersPool/components/OrdersRow/types.ts | 14 - src/components/dex/OrdersPool/index.tsx | 259 +++++++++++++----- .../dex/OrdersPool/styles.module.scss | 161 ++++++++--- src/components/dex/OrdersPool/types.ts | 11 +- .../components => }/TotalUsdCell/index.tsx | 10 +- .../components => }/TotalUsdCell/types.ts | 1 + .../dex/UserOrders/columns/index.tsx | 4 +- .../UserOrders/components/AliasCell/index.tsx | 47 ---- src/components/dex/UserOrders/index.tsx | 93 ++++--- .../dex/UserOrders/styles.module.scss | 82 +----- src/components/dex/UserOrders/types.ts | 12 +- src/hook/useFilteredData.ts | 20 +- src/hook/useOrdereForm.ts | 2 - src/hook/useTradeInit.ts | 13 +- .../InputPanelItem/InputPanelItemProps.ts | 2 +- .../InputPanelItem/LabeledInputProps.ts | 2 - src/pages/dex/trading/[id].tsx | 90 +++--- src/styles/Trading.module.scss | 16 +- src/styles/themes/dark.scss | 4 +- src/styles/themes/light.scss | 4 +- src/utils/takeOrderClick.ts | 82 ++---- src/utils/utils.ts | 9 +- 42 files changed, 925 insertions(+), 678 deletions(-) create mode 100644 src/components/UI/Tabs/index.tsx create mode 100644 src/components/UI/Tabs/styles.module.scss create mode 100644 src/components/UI/Tabs/types.ts create mode 100644 src/components/dex/AliasCell/index.tsx create mode 100644 src/components/dex/AliasCell/styles.module.scss rename src/components/dex/{UserOrders/components => }/AliasCell/types.ts (100%) create mode 100644 src/components/dex/OrdersPool/columns/index.tsx create mode 100644 src/components/dex/OrdersPool/columns/types.ts delete mode 100644 src/components/dex/OrdersPool/components/OrdersRow/index.tsx delete mode 100644 src/components/dex/OrdersPool/components/OrdersRow/styles.module.scss delete mode 100644 src/components/dex/OrdersPool/components/OrdersRow/types.ts rename src/components/dex/{UserOrders/components => }/TotalUsdCell/index.tsx (60%) rename src/components/dex/{UserOrders/components => }/TotalUsdCell/types.ts (87%) delete mode 100644 src/components/dex/UserOrders/components/AliasCell/index.tsx diff --git a/src/components/UI/Tabs/index.tsx b/src/components/UI/Tabs/index.tsx new file mode 100644 index 0000000..da029b6 --- /dev/null +++ b/src/components/UI/Tabs/index.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { classes } from '@/utils/utils'; +import styles from './styles.module.scss'; +import { TabsProps } from './types'; + +const Tabs = ({ data, value, setValue }: TabsProps) => { + return ( +
+ {data.map((tab) => ( + + ))} +
+ ); +}; + +export default Tabs; diff --git a/src/components/UI/Tabs/styles.module.scss b/src/components/UI/Tabs/styles.module.scss new file mode 100644 index 0000000..8897161 --- /dev/null +++ b/src/components/UI/Tabs/styles.module.scss @@ -0,0 +1,30 @@ +.tabs { + width: 100%; + border-bottom: 1px solid var(--delimiter-color); + display: flex; + align-items: center; + gap: 22px; + + &__item { + cursor: pointer; + padding-bottom: 7px; + position: relative; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + font-size: 14px; + font-weight: 500; + border-bottom: 2px solid transparent; + background-color: transparent; + color: #1f8feb; + + &.active { + color: var(--text-color); + border-color: #1f8feb; + } + + &:hover { + border-color: #1f8feb; + background-color: transparent; + } + } +} diff --git a/src/components/UI/Tabs/types.ts b/src/components/UI/Tabs/types.ts new file mode 100644 index 0000000..c7c0066 --- /dev/null +++ b/src/components/UI/Tabs/types.ts @@ -0,0 +1,13 @@ +import { Dispatch, SetStateAction } from 'react'; + +export type tabsType = { + title: string; + type: string; + length?: number; +}; + +export interface TabsProps { + value: tabsType; + setValue: Dispatch>; + data: tabsType[]; +} diff --git a/src/components/default/GenericTable/index.tsx b/src/components/default/GenericTable/index.tsx index da606e8..fe4df57 100644 --- a/src/components/default/GenericTable/index.tsx +++ b/src/components/default/GenericTable/index.tsx @@ -13,6 +13,7 @@ export default function GenericTable(props: GenericTableProps) { data, getRowKey, emptyMessage = 'No data', + getRowProps, } = props; return ( @@ -65,7 +66,10 @@ export default function GenericTable(props: GenericTableProps) { {data.map((row, i) => ( - + {columns.map((col) => ( = { cell: (_row: T, _rowIndex: number) => React.ReactNode; }; +export type RowProps = React.HTMLAttributes & { + className?: string; +}; + export type GenericTableProps = { className?: string; tableClassName?: string; @@ -18,4 +22,5 @@ export type GenericTableProps = { data: T[]; getRowKey: (_row: T, _rowIndex: number) => React.Key; emptyMessage?: string; + getRowProps?: (_row: T, _index: number) => RowProps | undefined; }; diff --git a/src/components/dex/AliasCell/index.tsx b/src/components/dex/AliasCell/index.tsx new file mode 100644 index 0000000..64673c6 --- /dev/null +++ b/src/components/dex/AliasCell/index.tsx @@ -0,0 +1,96 @@ +import { useEffect, useRef, useState } from 'react'; +import Tooltip from '@/components/UI/Tooltip/Tooltip'; +import { cutAddress } from '@/utils/utils'; +import MatrixConnectionBadge from '@/components/dex/MatrixConnectionBadge'; +import BadgeStatus from '@/components/dex/BadgeStatus'; +import { createPortal } from 'react-dom'; +import styles from './styles.module.scss'; +import { AliasCellProps } from './types'; + +export default function AliasCell({ + alias, + address, + matrixAddresses, + isInstant, + max = 12, +}: AliasCellProps) { + const display = alias ? cutAddress(alias, max) : 'no alias'; + + const [open, setOpen] = useState(false); + const [pos, setPos] = useState<{ top: number; left: number } | null>(null); + const anchorRef = useRef(null); + + const updatePosition = () => { + const el = anchorRef.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + setPos({ + top: rect.bottom + 8, + left: rect.left + rect.width / 2, + }); + }; + + useEffect(() => { + if (!open) return; + updatePosition(); + const onScrollOrResize = () => updatePosition(); + window.addEventListener('scroll', onScrollOrResize, true); + window.addEventListener('resize', onScrollOrResize); + return () => { + window.removeEventListener('scroll', onScrollOrResize, true); + window.removeEventListener('resize', onScrollOrResize); + }; + }, [open]); + + return ( +

+ { + setOpen(true); + requestAnimationFrame(updatePosition); + }} + onMouseLeave={() => setOpen(false)} + className={styles.alias__text} + > + @{display} + + + + + {isInstant && ( +

+ +
+ )} + + {open && + pos && + createPortal( + <> + {alias && alias.length > max && ( + +

{alias}

+
+ )} + , + document.body, + )} +

+ ); +} diff --git a/src/components/dex/AliasCell/styles.module.scss b/src/components/dex/AliasCell/styles.module.scss new file mode 100644 index 0000000..64a3495 --- /dev/null +++ b/src/components/dex/AliasCell/styles.module.scss @@ -0,0 +1,33 @@ +.alias { + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + + &__text { + color: var(--font-main-color) !important; + } + + path { + fill: none; + } +} + +.tooltip { + position: absolute; + top: 30px; + left: 5%; + background-color: var(--trade-table-tooltip); + z-index: 9999 !important; + + &__text { + font-size: 12px !important; + color: var(--font-main-color) !important; + } + + &__arrow { + border-radius: 2px; + left: 30% !important; + background-color: var(--trade-table-tooltip) !important; + } +} \ No newline at end of file diff --git a/src/components/dex/UserOrders/components/AliasCell/types.ts b/src/components/dex/AliasCell/types.ts similarity index 100% rename from src/components/dex/UserOrders/components/AliasCell/types.ts rename to src/components/dex/AliasCell/types.ts diff --git a/src/components/dex/CandleChart/styles.module.scss b/src/components/dex/CandleChart/styles.module.scss index b2e1950..6743437 100644 --- a/src/components/dex/CandleChart/styles.module.scss +++ b/src/components/dex/CandleChart/styles.module.scss @@ -4,8 +4,6 @@ height: auto; height: 100%; - height: 515px; - > canvas { width: 100%; height: 100%; diff --git a/src/components/dex/InputPanelItem/components/LabeledInput/index.tsx b/src/components/dex/InputPanelItem/components/LabeledInput/index.tsx index 5c0cb27..087af7d 100644 --- a/src/components/dex/InputPanelItem/components/LabeledInput/index.tsx +++ b/src/components/dex/InputPanelItem/components/LabeledInput/index.tsx @@ -1,21 +1,12 @@ import LabeledInputProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps'; -import { classes, formatDollarValue } from '@/utils/utils'; +import { classes } from '@/utils/utils'; import { useRef } from 'react'; import Input from '@/components/UI/Input/Input'; import styles from './styles.module.scss'; function LabeledInput(props: LabeledInputProps) { const labelRef = useRef(null); - const { - label = '', - placeholder = '', - currency = '', - value, - readonly, - usd, - setValue, - invalid, - } = props; + const { label = '', currency = '', value, readonly, setValue, invalid } = props; const handleInput = (e: React.FormEvent) => { if (!readonly && setValue) { @@ -29,18 +20,12 @@ function LabeledInput(props: LabeledInputProps) {
- {usd && ( -
-

~${formatDollarValue(usd)}

-
- )} -

{currency}

diff --git a/src/components/dex/InputPanelItem/components/LabeledInput/styles.module.scss b/src/components/dex/InputPanelItem/components/LabeledInput/styles.module.scss index 80c2834..9cd5606 100644 --- a/src/components/dex/InputPanelItem/components/LabeledInput/styles.module.scss +++ b/src/components/dex/InputPanelItem/components/LabeledInput/styles.module.scss @@ -4,8 +4,9 @@ gap: 8px; &__label { - font-size: 11px; - font-family: 700; + font-size: 12px; + font-family: 500; + line-height: 100%; color: var(--table-th-color); } @@ -13,47 +14,43 @@ width: 100%; position: relative; background-color: var(--bordered-input-bg); - border: 1px solid var(--window-border-color); + border: 1px solid transparent; border-radius: 8px; display: flex; overflow: hidden; + &:hover { + border-color: var(--window-border-color); + } + + &:focus-within { + border-color: #1f8feb; + } + &.invalid { border-color: #ff6767; } input { width: 100%; - padding: 13px 15px; + padding: 15px; background-color: transparent; border: none; - font-size: 16px; - font-weight: 400; - } - } - - &__value { - padding-right: 10px; - display: flex; - align-items: center; - - > p { - color: var(--table-th-color); - font-size: 12px; + font-size: 14px; font-weight: 400; } } &__currency { - min-width: 82px; max-width: 150px; padding: 0 15px; - background-color: var(--dex-input-currency); display: flex; align-items: center; justify-content: center; > p { + font-size: 14px; + font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/src/components/dex/InputPanelItem/index.tsx b/src/components/dex/InputPanelItem/index.tsx index 93a1a79..6f8eac7 100644 --- a/src/components/dex/InputPanelItem/index.tsx +++ b/src/components/dex/InputPanelItem/index.tsx @@ -5,7 +5,7 @@ import RangeInput from '@/components/UI/RangeInput/RangeInput'; import ConnectButton from '@/components/UI/ConnectButton/ConnectButton'; import Button from '@/components/UI/Button/Button'; import { useRouter } from 'next/router'; -import { classes } from '@/utils/utils'; +import { classes, formatDollarValue } from '@/utils/utils'; import InputPanelItemProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps'; import CreateOrderData from '@/interfaces/fetch-data/create-order/CreateOrderData'; import Decimal from 'decimal.js'; @@ -13,6 +13,7 @@ import Alert from '@/components/UI/Alert/Alert'; import infoIcon from '@/assets/images/UI/info_alert_icon.svg'; import Image from 'next/image'; import { useAlert } from '@/hook/useAlert'; +import { buySellValues } from '@/constants'; import styles from './styles.module.scss'; import LabeledInput from './components/LabeledInput'; @@ -21,8 +22,8 @@ function InputPanelItem(props: InputPanelItemProps) { priceState = '', amountState = '', totalState = '', - buySellValues, buySellState = buySellValues[0], + setBuySellState, setPriceFunction, setAmountFunction, setRangeInputValue, @@ -138,46 +139,83 @@ function InputPanelItem(props: InputPanelItemProps) { )}
-
- {isBuy ? 'Buy' : 'Sell'} {secondCurrencyName} -
- -

- Fee: 0.01 Zano -

+
Trade
- {LabeledInput({ - value: priceState, - setValue: setPriceFunction, - currency: secondCurrencyName, - placeholder: '0.00', - label: 'Price', - invalid: !!priceState && !priceValid, - })} +
+ + +
- {LabeledInput({ - value: amountState, - setValue: setAmountFunction, - currency: firstCurrencyName, - placeholder: '0.00', - label: 'Amount', - invalid: !!amountState && !amountValid, - })} + - + - {LabeledInput({ - value: totalState, - setValue: () => undefined, - currency: secondCurrencyName, - placeholder: '0.00', - label: 'Total', - readonly: true, - invalid: !!totalState && !totalValid, - usd: totalUsd, - })} +
+ +
+

0%

+

100%

+
+
+ +
+

Available Balance

+

+ {balance || 0} {firstCurrencyName} +

+
+ +
+ undefined} + currency={secondCurrencyName} + label="Total" + readonly={true} + invalid={!!totalState && !totalValid} + /> + +
+

+ Fee: 0.01 ZANO +

+ {totalUsd && ( +

+ ~ ${formatDollarValue(totalUsd)} +

+ )} +
+
{state.wallet?.connected ? (
diff --git a/src/components/dex/InputPanelItem/styles.module.scss b/src/components/dex/InputPanelItem/styles.module.scss index bb00386..93550f4 100644 --- a/src/components/dex/InputPanelItem/styles.module.scss +++ b/src/components/dex/InputPanelItem/styles.module.scss @@ -1,5 +1,7 @@ .inputPanel { width: 100%; + max-width: 415px; + height: 100%; padding: 15px; background: var(--window-bg-color); border: 1px solid var(--delimiter-color); @@ -9,7 +11,7 @@ display: flex; justify-content: space-between; align-items: center; - padding-bottom: 10px; + padding-bottom: 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); margin-bottom: 10px; @@ -19,21 +21,65 @@ } } - &__fees { - color: var(--table-th-color); - font-weight: 400; - font-size: 12px; + &__selector { + display: flex; + background-color: var(--main-bg-color); + padding: 4px; + border-radius: 20px; - span { - font-weight: 400; - font-size: 12px; + &_item { + cursor: pointer; + width: 50%; + background-color: transparent; + border-radius: 100px; + padding-block: 9px; + font-size: 14px; + font-weight: 500; + line-height: 100%; + + &:hover { + background-color: transparent; + opacity: 0.8; + } + + &.buy { + background-color: #16d1d6; + } + + &.sell { + background-color: #ff6767; + } } } &__body { display: flex; flex-direction: column; - gap: 10px; + gap: 15px; + + &_labels { + margin-top: 5px; + display: flex; + align-items: center; + justify-content: space-between; + + &__item { + font-size: 12px; + font-weight: 500; + line-height: 100%; + color: var(--table-th-color); + + span { + font-size: 12px; + } + } + } + + &_total { + display: flex; + flex-direction: column; + gap: 5px; + } &_btn { margin-top: 10px; diff --git a/src/components/dex/MatrixConnectionBadge/index.tsx b/src/components/dex/MatrixConnectionBadge/index.tsx index 5d47033..faf040f 100644 --- a/src/components/dex/MatrixConnectionBadge/index.tsx +++ b/src/components/dex/MatrixConnectionBadge/index.tsx @@ -2,6 +2,7 @@ import Tooltip from '@/components/UI/Tooltip/Tooltip'; import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { ReactComponent as ConnectionIcon } from '@/assets/images/UI/connection.svg'; +import { classes } from '@/utils/utils'; import styles from './styles.module.scss'; import { MatrixConnectionBadgeProps } from './types'; @@ -9,13 +10,14 @@ function MatrixConnectionBadge({ userAdress, userAlias, matrixAddresses, + className, }: MatrixConnectionBadgeProps) { const hasConnection = (address: string) => matrixAddresses.some((item) => item.address === address && item.registered); const [open, setOpen] = useState(false); const [pos, setPos] = useState<{ top: number; left: number } | null>(null); - const anchorRef = useRef(null); + const anchorRef = useRef(null); const updatePosition = () => { const el = anchorRef.current; @@ -42,11 +44,13 @@ function MatrixConnectionBadge({ if (!userAdress || !hasConnection(userAdress)) return <>; return ( -
- +

{ + e.preventDefault(); + window.open(`https://matrix.to/#/@${userAlias}:zano.org`); + }} onMouseEnter={() => { setOpen(true); requestAnimationFrame(updatePosition); @@ -55,7 +59,7 @@ function MatrixConnectionBadge({ className={styles.badge__link} > - +

{open && pos && diff --git a/src/components/dex/MatrixConnectionBadge/styles.module.scss b/src/components/dex/MatrixConnectionBadge/styles.module.scss index 323ddc6..b4e816b 100644 --- a/src/components/dex/MatrixConnectionBadge/styles.module.scss +++ b/src/components/dex/MatrixConnectionBadge/styles.module.scss @@ -8,13 +8,8 @@ &__tooltip { padding: 10px; - position: absolute; - top: 30px; - left: 50%; - transform: translateX(-50%); background-color: var(--trade-table-tooltip); font-size: 12px; - z-index: 9999; &_text { font-size: 12px !important; @@ -26,4 +21,4 @@ background-color: var(--trade-table-tooltip) !important; } } -} +} \ No newline at end of file diff --git a/src/components/dex/MatrixConnectionBadge/types.ts b/src/components/dex/MatrixConnectionBadge/types.ts index 08f8141..08a6668 100644 --- a/src/components/dex/MatrixConnectionBadge/types.ts +++ b/src/components/dex/MatrixConnectionBadge/types.ts @@ -1,6 +1,7 @@ import MatrixAddress from '@/interfaces/common/MatrixAddress'; export interface MatrixConnectionBadgeProps { + className?: string; userAdress?: string; userAlias?: string; matrixAddresses: MatrixAddress[]; diff --git a/src/components/dex/OrdersPool/columns/index.tsx b/src/components/dex/OrdersPool/columns/index.tsx new file mode 100644 index 0000000..16aec5c --- /dev/null +++ b/src/components/dex/OrdersPool/columns/index.tsx @@ -0,0 +1,83 @@ +import { formatTimestamp, notationToString } from '@/utils/utils'; +import { ColumnDef } from '@/components/default/GenericTable/types'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; +import { BuildOrderPoolColumnsArgs, BuildTradesColumnsArgs } from './types'; +import TotalUsdCell from '../../TotalUsdCell'; +import AliasCell from '../../AliasCell'; + +export function buildOrderPoolColumns({ + firstCurrencyName, + secondCurrencyName, + matrixAddresses, +}: BuildOrderPoolColumnsArgs): ColumnDef[] { + return [ + { + key: 'alias', + header: 'Alias', + width: '100px', + className: 'alias', + cell: (row) => ( + + ), + }, + { + key: 'price', + header: <>Price ({secondCurrencyName}), + width: '80px', + cell: (row) => ( +

+ {notationToString(row.price, 8)} +

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

{notationToString(row.amount, 8)}

, + }, + { + key: 'total', + header: <>Total ({secondCurrencyName}), + width: '80px', + cell: (row) => , + }, + ]; +} + +export function buildTradesColumns({ + firstCurrencyName, + secondCurrencyName, +}: BuildTradesColumnsArgs): ColumnDef[] { + return [ + { + key: 'price', + header: <>Price ({secondCurrencyName}), + width: '80px', + cell: (row) => ( +

+ {notationToString(row.price)} +

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

{notationToString(row.amount)}

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

{formatTimestamp(row.timestamp)}

, + }, + ]; +} diff --git a/src/components/dex/OrdersPool/columns/types.ts b/src/components/dex/OrdersPool/columns/types.ts new file mode 100644 index 0000000..e4136b6 --- /dev/null +++ b/src/components/dex/OrdersPool/columns/types.ts @@ -0,0 +1,12 @@ +import MatrixAddress from '@/interfaces/common/MatrixAddress'; + +export interface BuildOrderPoolColumnsArgs { + firstCurrencyName: string; + secondCurrencyName: string; + matrixAddresses: MatrixAddress[]; +} + +export interface BuildTradesColumnsArgs { + firstCurrencyName: string; + secondCurrencyName: string; +} diff --git a/src/components/dex/OrdersPool/components/OrdersRow/index.tsx b/src/components/dex/OrdersPool/components/OrdersRow/index.tsx deleted file mode 100644 index a4c3eb7..0000000 --- a/src/components/dex/OrdersPool/components/OrdersRow/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { classes, notationToString } from '@/utils/utils'; -import { nanoid } from 'nanoid'; -import Decimal from 'decimal.js'; -import styles from './styles.module.scss'; -import { OrdersRowProps } from './types'; -import OrderRowTooltipCell from '../../../OrderRowTooltipCell'; - -function OrdersRow({ - orderData, - percentage, - takeOrderClick, - setOrdersInfoTooltip, -}: OrdersRowProps) { - const e = orderData || {}; - - const totalDecimal = new Decimal(e.left).mul(new Decimal(e.price)); - - return ( - setOrdersInfoTooltip(e)} - onClick={(event) => takeOrderClick(event, e)} - className={classes(styles.row, e.type === 'sell' && styles.sell_section)} - style={{ '--line-width': `${percentage}%` } as React.CSSProperties} - key={nanoid(16)} - > - - {notationToString(e.price)} - - {notationToString(e.amount)} - - {notationToString(totalDecimal.toString())} - - - ); -} - -export default OrdersRow; diff --git a/src/components/dex/OrdersPool/components/OrdersRow/styles.module.scss b/src/components/dex/OrdersPool/components/OrdersRow/styles.module.scss deleted file mode 100644 index 34a925f..0000000 --- a/src/components/dex/OrdersPool/components/OrdersRow/styles.module.scss +++ /dev/null @@ -1,48 +0,0 @@ -.row { - cursor: pointer; - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 4px 0; - - &:nth-child(even) { - background-color: var(--table-even-bg); - } - - &::after { - content: ''; - pointer-events: none; - position: absolute; - z-index: 1; - right: 0; - top: 0; - width: var(--line-width, 0%); - height: 100%; - background: #16d1d61a; - } - - &.sell_section { - &::after { - background: #ff67671a; - } - } - - td { - position: relative; - - &:last-child { - > p { - text-align: right; - } - } - - > p { - min-width: 80px; - width: 100%; - font-size: 12px; - font-weight: 400; - } - } -} diff --git a/src/components/dex/OrdersPool/components/OrdersRow/types.ts b/src/components/dex/OrdersPool/components/OrdersRow/types.ts deleted file mode 100644 index 0f7ea0a..0000000 --- a/src/components/dex/OrdersPool/components/OrdersRow/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; -import { Dispatch, SetStateAction } from 'react'; - -export interface OrdersRowProps { - orderData: PageOrderData; - percentage: number; - takeOrderClick: ( - _event: - | React.MouseEvent - | React.MouseEvent, - _e: PageOrderData, - ) => void; - setOrdersInfoTooltip: Dispatch>; -} diff --git a/src/components/dex/OrdersPool/index.tsx b/src/components/dex/OrdersPool/index.tsx index 194094b..7f3632a 100644 --- a/src/components/dex/OrdersPool/index.tsx +++ b/src/components/dex/OrdersPool/index.tsx @@ -1,17 +1,30 @@ -import React, { useRef, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { classes, cutAddress, formatDollarValue, notationToString } from '@/utils/utils'; import { nanoid } from 'nanoid'; import Decimal from 'decimal.js'; import Tooltip from '@/components/UI/Tooltip/Tooltip'; import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; import { buySellValues } from '@/constants'; -import EmptyMessage from '@/components/UI/EmptyMessage'; import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; import useMouseLeave from '@/hook/useMouseLeave'; -import OrdersRow from './components/OrdersRow'; +import { tabsType } from '@/components/UI/Tabs/types'; +import Tabs from '@/components/UI/Tabs'; +import GenericTable from '@/components/default/GenericTable'; import styles from './styles.module.scss'; import BadgeStatus from '../BadgeStatus'; import { OrdersPoolProps } from './types'; +import { buildOrderPoolColumns, buildTradesColumns } from './columns'; + +const tabsData: tabsType[] = [ + { + title: 'Order Pool', + type: 'orders', + }, + { + title: 'Recent Trades', + type: 'trades', + }, +]; const OrdersPool = (props: OrdersPoolProps) => { const { @@ -22,103 +35,197 @@ const OrdersPool = (props: OrdersPoolProps) => { filteredOrdersHistory, secondAssetUsdPrice, takeOrderClick, + matrixAddresses, + trades, + tradesLoading, } = props; const ordersInfoRef = useRef(null); const { firstCurrencyName, secondCurrencyName } = currencyNames; const [infoTooltipPos, setInfoTooltipPos] = useState({ x: 0, y: 0 }); const [ordersInfoTooltip, setOrdersInfoTooltip] = useState(null); + const [currentOrder, setCurrentOrder] = useState(tabsData[0]); + const { maxBuyLeftValue, maxSellLeftValue } = filteredOrdersHistory.reduce( + (acc, order) => { + const left = parseFloat(String(order.left)) || 0; + if (order.type === 'buy') acc.maxBuyLeftValue = Math.max(acc.maxBuyLeftValue, left); + if (order.type === 'sell') acc.maxSellLeftValue = Math.max(acc.maxSellLeftValue, left); + return acc; + }, + { maxBuyLeftValue: 0, maxSellLeftValue: 0 }, + ); + + const totalLeft = maxBuyLeftValue + maxSellLeftValue; const moveInfoTooltip = (event: React.MouseEvent) => { setInfoTooltipPos({ x: event.clientX, y: event.clientY }); }; + const ordersPool = useMemo( + () => + buildOrderPoolColumns({ + firstCurrencyName, + secondCurrencyName, + matrixAddresses, + }), + [firstCurrencyName, secondCurrencyName, matrixAddresses], + ); + + const tradeOrders = useMemo( + () => + buildTradesColumns({ + firstCurrencyName, + secondCurrencyName, + }), + [firstCurrencyName, secondCurrencyName], + ); + + const renderTable = () => { + switch (currentOrder.type) { + case 'orders': + return ( + <> + {!ordersLoading ? ( +
+ { + if (a.type === b.type) return 0; + return a.type === 'buy' ? -1 : 1; + })} + getRowKey={(r) => r.id} + getRowProps={(row) => ({ + className: styles[row.type], + style: { + '--precentage': `${( + (parseFloat(String(row.left)) / + (row.type === 'buy' + ? maxBuyLeftValue + : maxSellLeftValue)) * + 100 + ).toFixed(2)}%`, + } as React.CSSProperties, + onClick: (event) => { + takeOrderClick(event, row); + }, + onMouseMove: (event) => { + const tr = event.target as HTMLElement; + if (tr.classList.contains('alias')) { + setOrdersInfoTooltip(null); + } + }, + onMouseEnter: () => { + setOrdersInfoTooltip(row); + }, + onMouseLeave: () => { + setOrdersInfoTooltip(null); + }, + })} + /> +
+ ) : ( + + )} + + ); + case 'trades': + return ( + <> + {!tradesLoading ? ( + r.id} + /> + ) : ( + + )} + + ); + default: + return null; + } + }; + useMouseLeave(ordersInfoRef, () => setOrdersInfoTooltip(null)); return ( <>
-
Orders pool
+ -
- + {currentOrder.type === 'orders' && ( +
+ - + - -
+ +
+ )}
- - - - - - - - + {renderTable()} - {!ordersLoading && !!filteredOrdersHistory.length && ( - setOrdersInfoTooltip(null)} - className="orders-scroll" + {currentOrder.type === 'orders' && ( +
+
- {filteredOrdersHistory?.map((e) => { - const maxValue = Math.max( - ...filteredOrdersHistory.map((order) => - parseFloat(String(order.left)), - ), - ); - const percentage = ( - (parseFloat(String(e.left)) / maxValue) * - 100 - ).toFixed(2); - - return ( - - ); - })} -
- )} -
Price ({secondCurrencyName})Amount ({firstCurrencyName})Total ({secondCurrencyName})
- - {!filteredOrdersHistory.length && !ordersLoading && ( - +
B
{' '} + {notationToString((maxBuyLeftValue / totalLeft) * 100, 0)}% +
+
+ {notationToString((maxSellLeftValue / totalLeft) * 100, 0)}%{' '} +
S
+
+
)} - {ordersLoading && }
diff --git a/src/components/dex/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss index d16c375..6ef00e6 100644 --- a/src/components/dex/OrdersPool/styles.module.scss +++ b/src/components/dex/OrdersPool/styles.module.scss @@ -1,28 +1,18 @@ .ordersPool { + position: relative; max-width: 415px; width: 100%; + height: 100%; padding: 5px; background: var(--window-bg-color); border: 1px solid var(--delimiter-color); border-radius: 10px; - @media screen and (max-width: 1480px) { - max-width: 340px; - } - &__header { display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; gap: 10px; padding: 10px; - padding-bottom: 10px; - border-bottom: 1px solid var(--delimiter-color); - - &_title { - font-size: 18px; - font-weight: 600; - } &_type { display: flex; @@ -38,10 +28,11 @@ font-weight: 700; transition: 0.3s opacity ease; color: #ffffff; + opacity: 60%; &.selected, &:hover { - opacity: 80%; + opacity: 100%; } &.all { @@ -63,44 +54,138 @@ display: flex; flex-direction: column; padding-top: 10px; + width: 100%; - table { + &_orders { width: 100%; + height: 410px; - thead { - display: flex; + .table { width: 100%; - padding-inline: 10px; - margin-bottom: 9px; - tr { - width: 100%; - display: flex; - justify-content: space-between; + &__header { + position: relative; + z-index: 3; th { - font-size: 11px; - font-weight: 700; - text-align: start; color: var(--table-th-color); - min-width: 80px; + } + } - &:last-child { - text-align: right; + &__body { + tr { + cursor: pointer; + position: relative; + + &:hover { + background-color: var(--table-tr-hover-color); + } + + &.buy { + td { + &:last-child { + &::before { + background-color: var(--dex-buy-percentage); + } + } + } + } + + &.sell { + td { + &:last-child { + &::before { + background-color: var(--dex-sell-percentage); + } + } + } + } + + td { + position: static !important; + + + + &:last-child { + + + &::before { + content: ''; + pointer-events: none; + position: absolute; + z-index: 1; + right: 0; + top: 0; + width: var(--precentage); + height: 100%; + } + } + + p, + span { + position: relative; + z-index: 2; + font-size: 12px; + font-weight: 400; + } } } } } + } - tbody { - height: 29dvh; - min-height: 265px; - max-height: 380px; + &_stats { + position: absolute; + bottom: 15px; + left: 50%; + transform: translateX(-50%); + width: calc(100% - 30px); + display: flex; + align-items: center; + justify-content: space-between; + gap: 1px; + + .stat_item { display: flex; - flex-direction: column; - overflow: auto; - padding-bottom: 20px; - padding: 10px; + align-items: center; + gap: 6px; + font-size: 12px; + line-height: 100%; + font-weight: 400; + min-width: 60px; + + &.buy { + width: var(--width); + background-color: var(--dex-buy-percentage); + color: #16d1d6; + + .stat_item__badge { + background-color: #16d1d6; + } + } + + &.sell { + width: var(--width); + background-color: var(--dex-sell-percentage); + justify-content: flex-end; + color: #ff6767; + + .stat_item__badge { + background-color: #ff6767; + } + } + + &__badge { + width: 20px; + height: 20px; + border-radius: 4px; + display: grid; + place-content: center; + font-size: 12px; + font-weight: 700; + line-height: 120%; + color: #ffffff; + } } } } @@ -148,4 +233,4 @@ font-size: 11px; font-weight: 400; } -} +} \ No newline at end of file diff --git a/src/components/dex/OrdersPool/types.ts b/src/components/dex/OrdersPool/types.ts index c5c5a8b..21406a3 100644 --- a/src/components/dex/OrdersPool/types.ts +++ b/src/components/dex/OrdersPool/types.ts @@ -1,9 +1,12 @@ import { Dispatch, SetStateAction } from 'react'; import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import MatrixAddress from '@/interfaces/common/MatrixAddress'; +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; export interface OrdersPoolProps { ordersBuySell: SelectValue; + secondAssetUsdPrice: number | undefined; setOrdersBuySell: Dispatch>; currencyNames: { firstCurrencyName: string; @@ -11,11 +14,11 @@ export interface OrdersPoolProps { }; ordersLoading: boolean; filteredOrdersHistory: PageOrderData[]; - secondAssetUsdPrice: number | undefined; + trades: Trade[]; + tradesLoading: boolean; + matrixAddresses: MatrixAddress[]; takeOrderClick: ( - _event: - | React.MouseEvent - | React.MouseEvent, + _event: React.MouseEvent, _e: PageOrderData, ) => void; } diff --git a/src/components/dex/UserOrders/components/TotalUsdCell/index.tsx b/src/components/dex/TotalUsdCell/index.tsx similarity index 60% rename from src/components/dex/UserOrders/components/TotalUsdCell/index.tsx rename to src/components/dex/TotalUsdCell/index.tsx index 2f494a7..64b62c0 100644 --- a/src/components/dex/UserOrders/components/TotalUsdCell/index.tsx +++ b/src/components/dex/TotalUsdCell/index.tsx @@ -3,7 +3,12 @@ import Decimal from 'decimal.js'; import { notationToString, formatDollarValue } from '@/utils/utils'; import { TotalUsdCellProps } from './types'; -export default function TotalUsdCell({ amount, price, secondAssetUsdPrice }: TotalUsdCellProps) { +export default function TotalUsdCell({ + amount, + price, + secondAssetUsdPrice, + fixed, +}: TotalUsdCellProps) { const total = useMemo( () => new Decimal(amount || 0).mul(new Decimal(price || 0)), [amount, price], @@ -12,7 +17,8 @@ export default function TotalUsdCell({ amount, price, secondAssetUsdPrice }: Tot return (

- {notationToString(total.toString())} ~ ${usd && formatDollarValue(usd)} + {notationToString((fixed ? total.toFixed(fixed) : total).toString())}{' '} + {secondAssetUsdPrice && ~ ${usd && formatDollarValue(usd)}}

); } diff --git a/src/components/dex/UserOrders/components/TotalUsdCell/types.ts b/src/components/dex/TotalUsdCell/types.ts similarity index 87% rename from src/components/dex/UserOrders/components/TotalUsdCell/types.ts rename to src/components/dex/TotalUsdCell/types.ts index b09e12c..b66b811 100644 --- a/src/components/dex/UserOrders/components/TotalUsdCell/types.ts +++ b/src/components/dex/TotalUsdCell/types.ts @@ -2,4 +2,5 @@ export interface TotalUsdCellProps { amount: string | number; price: string | number; secondAssetUsdPrice?: number; + fixed?: number; } diff --git a/src/components/dex/UserOrders/columns/index.tsx b/src/components/dex/UserOrders/columns/index.tsx index 73bf3a8..84a60bf 100644 --- a/src/components/dex/UserOrders/columns/index.tsx +++ b/src/components/dex/UserOrders/columns/index.tsx @@ -5,8 +5,8 @@ import ApplyTip from '@/interfaces/common/ApplyTip'; import UserPendingType from '@/interfaces/common/UserPendingType'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; import CancelActionCell from '../components/CancelActionCell'; -import AliasCell from '../components/AliasCell'; -import TotalUsdCell from '../components/TotalUsdCell'; +import AliasCell from '../../AliasCell'; +import TotalUsdCell from '../../TotalUsdCell'; import RequestActionCell from '../components/RequestActionCell'; import { BuildApplyTipsColumnsArgs, diff --git a/src/components/dex/UserOrders/components/AliasCell/index.tsx b/src/components/dex/UserOrders/components/AliasCell/index.tsx deleted file mode 100644 index 169e95b..0000000 --- a/src/components/dex/UserOrders/components/AliasCell/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useState } from 'react'; -import Tooltip from '@/components/UI/Tooltip/Tooltip'; -import { cutAddress } from '@/utils/utils'; -import MatrixConnectionBadge from '@/components/dex/MatrixConnectionBadge'; -import BadgeStatus from '@/components/dex/BadgeStatus'; -import styles from '../../styles.module.scss'; -import { AliasCellProps } from './types'; - -export default function AliasCell({ - alias, - address, - matrixAddresses, - isInstant, - max = 12, -}: AliasCellProps) { - const [show, setShow] = useState(false); - const display = alias ? cutAddress(alias, max) : 'no alias'; - - return ( -

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

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

{alias}

-
- )} -

- ); -} diff --git a/src/components/dex/UserOrders/index.tsx b/src/components/dex/UserOrders/index.tsx index b360b9e..0aa98c6 100644 --- a/src/components/dex/UserOrders/index.tsx +++ b/src/components/dex/UserOrders/index.tsx @@ -2,7 +2,7 @@ import { classes } from '@/utils/utils'; import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; import useUpdateUser from '@/hook/useUpdateUser'; import EmptyMessage from '@/components/UI/EmptyMessage'; -import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import GenericTable from '@/components/default/GenericTable'; import ActionBtn from '@/components/UI/ActionBtn'; import { getUserOrders, getUserPendings } from '@/utils/methods'; @@ -11,7 +11,9 @@ import { Store } from '@/store/store-reducer'; import { UserOrderData } from '@/interfaces/responses/orders/GetUserOrdersRes'; import { useAlert } from '@/hook/useAlert'; import Alert from '@/components/UI/Alert/Alert'; -import { tabsType, UserOrdersProps } from './types'; +import { tabsType } from '@/components/UI/Tabs/types'; +import Tabs from '@/components/UI/Tabs'; +import { UserOrdersProps } from './types'; import styles from './styles.module.scss'; import { buildApplyTipsColumns, @@ -24,8 +26,6 @@ const UserOrders = ({ userOrders, applyTips, myOrdersLoading, - ordersType, - setOrdersType, handleCancelAllOrders, orderListRef, matrixAddresses, @@ -43,6 +43,45 @@ const UserOrders = ({ const [userRequests, setUserRequests] = useState([]); const [ordersHistory, setOrdersHistory] = useState([]); + const tabsData: tabsType[] = useMemo( + () => [ + { + title: 'Open Orders', + type: 'opened', + length: userOrders.length, + }, + { + title: 'Suitable', + type: 'suitable', + length: suitables.length, + }, + { + title: 'My requests', + type: 'requests', + length: userRequests.length, + }, + { + title: 'Offers', + type: 'offers', + length: offers.length, + }, + { + title: 'Order history', + type: 'history', + length: ordersHistory.length, + }, + ], + [ + offers.length, + userOrders.length, + suitables.length, + userRequests.length, + ordersHistory.length, + ], + ); + + const [ordersType, setOrdersType] = useState(tabsData[0]); + useEffect(() => { (async () => { const requestsData = await getUserPendings(); @@ -166,7 +205,7 @@ const UserOrders = ({ ); const renderTable = () => { - switch (ordersType) { + switch (ordersType.type) { case 'opened': return (
-
- {tabsData.map((tab) => ( - - ))} -
+ - {ordersType === 'opened' && userOrders.length > 0 && ( + {ordersType?.type === 'opened' && userOrders.length > 0 && ( p { + >p { width: 100%; font-size: 12px; font-weight: 400; @@ -109,7 +76,7 @@ } } - > span { + >span { line-height: 1; color: var(--font-dimmed-color); font-size: 11px; @@ -118,37 +85,4 @@ } } } -} - -.alias { - display: flex; - align-items: center; - gap: 4px; - font-size: 14px; - - &__text { - color: var(--font-main-color) !important; - } - - path { - fill: none; - } -} - -.tooltip { - position: absolute; - top: 30px; - left: 5%; - background-color: var(--trade-table-tooltip); - z-index: 9999; - - &__text { - font-size: 12px !important; - } - - &__arrow { - border-radius: 2px; - left: 30% !important; - background-color: var(--trade-table-tooltip) !important; - } -} +} \ No newline at end of file diff --git a/src/components/dex/UserOrders/types.ts b/src/components/dex/UserOrders/types.ts index 1ba1cd0..cd10ded 100644 --- a/src/components/dex/UserOrders/types.ts +++ b/src/components/dex/UserOrders/types.ts @@ -2,24 +2,14 @@ import ApplyTip from '@/interfaces/common/ApplyTip'; import MatrixAddress from '@/interfaces/common/MatrixAddress'; import OrderRow from '@/interfaces/common/OrderRow'; import PairData from '@/interfaces/common/PairData'; -import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; -import { Dispatch, ForwardedRef, SetStateAction } from 'react'; +import { ForwardedRef } from 'react'; export type OrderType = 'opened' | 'suitable' | 'requests' | 'offers' | 'history'; - -export type tabsType = { - title: string; - type: OrderType; - length: number; -}; - export interface UserOrdersProps { orderListRef: ForwardedRef; userOrders: OrderRow[]; applyTips: ApplyTip[]; myOrdersLoading: boolean; - ordersType: OrderType; - setOrdersType: Dispatch>; handleCancelAllOrders: () => void; secondAssetUsdPrice: number | undefined; matrixAddresses: MatrixAddress[]; diff --git a/src/hook/useFilteredData.ts b/src/hook/useFilteredData.ts index 43b3122..d3aa71d 100644 --- a/src/hook/useFilteredData.ts +++ b/src/hook/useFilteredData.ts @@ -1,33 +1,16 @@ import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; -import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; import { Store } from '@/store/store-reducer'; import { useContext } from 'react'; interface useFilteredDataParams { - trades: Trade[]; ordersHistory: PageOrderData[]; ordersBuySell: SelectValue; - tradesType: 'all' | 'my'; } -const useFilteredData = ({ - ordersHistory, - trades, - ordersBuySell, - tradesType, -}: useFilteredDataParams) => { +const useFilteredData = ({ ordersHistory, ordersBuySell }: useFilteredDataParams) => { const { state } = useContext(Store); - const filteredTrades = - tradesType === 'my' - ? trades.filter( - (trade) => - trade.buyer.address === state.wallet?.address || - trade.seller.address === state.wallet?.address, - ) - : trades; - const filteredOrdersHistory = ordersHistory ?.filter((e) => (ordersBuySell.code === 'all' ? e : e.type === ordersBuySell.code)) ?.filter((e) => e.user.address !== state.wallet?.address) @@ -40,7 +23,6 @@ const useFilteredData = ({ return { filteredOrdersHistory, - filteredTrades, }; }; diff --git a/src/hook/useOrdereForm.ts b/src/hook/useOrdereForm.ts index b4adc3a..aaef1a5 100644 --- a/src/hook/useOrdereForm.ts +++ b/src/hook/useOrdereForm.ts @@ -5,14 +5,12 @@ import OrderFormOutput from '@/interfaces/common/orderFormOutput'; import { handleInputChange } from '@/utils/handleInputChange'; interface UseOrderFormParams { - type: 'buy' | 'sell'; pairData: PairData | null; balance: string | undefined; assetsRates: Map; } export function useOrderForm({ - type, pairData, balance, assetsRates, diff --git a/src/hook/useTradeInit.ts b/src/hook/useTradeInit.ts index 08c7a99..4c6181f 100644 --- a/src/hook/useTradeInit.ts +++ b/src/hook/useTradeInit.ts @@ -37,15 +37,7 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => { ? new Decimal(pairStats.rate).mul(secondAssetUsdPrice).toFixed(2) : undefined; - const buyForm = useOrderForm({ - type: 'buy', - pairData, - balance, - assetsRates: state.assetsRates, - }); - - const sellForm = useOrderForm({ - type: 'sell', + const orderForm = useOrderForm({ pairData, balance, assetsRates: state.assetsRates, @@ -57,8 +49,7 @@ const useTradeInit = ({ pairData, pairStats }: useTradeInitParams) => { secondAssetLink, secondAssetUsdPrice, balance, - buyForm, - sellForm, + orderForm, pairRateUsd, }; }; diff --git a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts index 67bb931..b3fe758 100644 --- a/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts +++ b/src/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps.ts @@ -9,8 +9,8 @@ interface InputPanelItemProps { priceState: string; amountState: string; totalState: string; - buySellValues: SelectValue[]; buySellState: SelectValue; + setBuySellState: Dispatch>; setPriceFunction: (_value: string) => void; setAmountFunction: (_value: string) => void; setRangeInputValue: Dispatch>; diff --git a/src/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps.ts b/src/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps.ts index e506c47..3a3df12 100644 --- a/src/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps.ts +++ b/src/interfaces/props/pages/dex/trading/InputPanelItem/LabeledInputProps.ts @@ -2,9 +2,7 @@ interface LabeledInputProps { label: string; value: string; setValue: (_value: string) => void; - placeholder: string; currency: string; - usd?: string; readonly?: boolean; invalid?: boolean; } diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 0702ac2..cd02895 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -30,7 +30,6 @@ import useFilteredData from '@/hook/useFilteredData'; import useTradeInit from '@/hook/useTradeInit'; import useMatrixAddresses from '@/hook/useMatrixAddresses'; import takeOrderClick from '@/utils/takeOrderClick'; -import { OrderType } from '@/components/dex/UserOrders/types'; import useUpdateUser from '@/hook/useUpdateUser'; const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }]; @@ -52,15 +51,13 @@ function Trading() { const [trades, setTrades] = useState([]); const [myOrdersLoading, setMyOrdersLoading] = useState(true); const [ordersBuySell, setOrdersBuySell] = useState(buySellValues[0]); - const [tradesType, setTradesType] = useState<'all' | 'my'>('all'); - const [ordersType, setOrdersType] = useState('opened'); const [pairStats, setPairStats] = useState(null); const [applyTips, setApplyTips] = useState([]); const matrixAddresses = useMatrixAddresses(ordersHistory); + const [orderFormType, setOrderFormType] = useState(buySellValues[1]); const { - buyForm, - sellForm, + orderForm, currencyNames, firstAssetLink, secondAssetLink, @@ -99,23 +96,21 @@ function Trading() { // Take order from trades const onHandleTakeOrder = useCallback( - ( - event: - | React.MouseEvent - | React.MouseEvent, - e: PageOrderData, - ) => { + (event: React.MouseEvent, e: PageOrderData) => { + setOrderFormType(() => { + return e.type === 'buy' ? buySellValues[2] : buySellValues[1]; + }); + takeOrderClick({ event, PageOrderData: e, balance, - buyForm, + orderForm, pairData, scrollToOrderForm, - sellForm, }); }, - [balance, buyForm, pairData, scrollToOrderForm, sellForm], + [balance, orderForm, pairData, scrollToOrderForm], ); // Cancel all user orders @@ -134,11 +129,9 @@ function Trading() { } }, [userOrders, updateUserOrders]); - const { filteredOrdersHistory, filteredTrades } = useFilteredData({ + const { filteredOrdersHistory } = useFilteredData({ ordersBuySell, ordersHistory, - trades, - tradesType, }); const onAfter = async () => { @@ -166,12 +159,15 @@ function Trading() {
@@ -198,13 +194,34 @@ function Trading() { )}
- + +
+ {/* + /> */}
@@ -213,8 +230,6 @@ function Trading() { userOrders={userOrders} applyTips={applyTips} myOrdersLoading={myOrdersLoading} - ordersType={ordersType} - setOrdersType={setOrdersType} handleCancelAllOrders={handleCancelAllOrders} matrixAddresses={matrixAddresses} secondAssetUsdPrice={secondAssetUsdPrice} @@ -222,35 +237,6 @@ function Trading() { onAfter={onAfter} />
-
- {['buy', 'sell'].map((type) => { - const isBuy = type === 'buy'; - const form = isBuy ? buyForm : sellForm; - - return ( - - ); - })} -
{alertState && ( ; PageOrderData: PageOrderData; pairData: PairDataType | null; - buyForm: OrderFormOutput; - sellForm: OrderFormOutput; + orderForm: OrderFormOutput; balance: string | undefined; scrollToOrderForm: () => void; } @@ -22,8 +21,7 @@ function takeOrderClick({ event, PageOrderData, pairData, - buyForm, - sellForm, + orderForm, balance, scrollToOrderForm, }: takeOrderClickParams) { @@ -36,59 +34,31 @@ function takeOrderClick({ const secondCurrencyDP = pairData?.second_currency?.asset_info?.decimal_point || 12; const firstCurrencyDP = pairData?.first_currency?.asset_info?.decimal_point || 12; - if (e.type === 'sell') { - handleInputChange({ - inputValue: priceStr, - priceOrAmount: 'price', - otherValue: amountStr, - thisDP: secondCurrencyDP, - totalDP: secondCurrencyDP, - setThisState: buyForm.setPrice, - setTotalState: buyForm.setTotal, - setThisValid: buyForm.setPriceValid, - setTotalValid: buyForm.setTotalValid, - }); + handleInputChange({ + inputValue: priceStr, + priceOrAmount: 'price', + otherValue: amountStr, + thisDP: secondCurrencyDP, + totalDP: secondCurrencyDP, + setThisState: orderForm.setPrice, + setTotalState: orderForm.setTotal, + setThisValid: orderForm.setPriceValid, + setTotalValid: orderForm.setTotalValid, + }); - handleInputChange({ - inputValue: amountStr, - priceOrAmount: 'amount', - otherValue: priceStr, - thisDP: firstCurrencyDP, - totalDP: secondCurrencyDP, - setThisState: buyForm.setAmount, - setTotalState: buyForm.setTotal, - setThisValid: buyForm.setAmountValid, - setTotalValid: buyForm.setTotalValid, - balance, - setRangeInputValue: buyForm.setRangeInputValue, - }); - } else { - handleInputChange({ - inputValue: priceStr, - priceOrAmount: 'price', - otherValue: amountStr, - thisDP: secondCurrencyDP, - totalDP: secondCurrencyDP, - setThisState: sellForm.setPrice, - setTotalState: sellForm.setTotal, - setThisValid: sellForm.setPriceValid, - setTotalValid: sellForm.setTotalValid, - }); - - handleInputChange({ - inputValue: amountStr, - priceOrAmount: 'amount', - otherValue: priceStr, - thisDP: firstCurrencyDP, - totalDP: secondCurrencyDP, - setThisState: sellForm.setAmount, - setTotalState: sellForm.setTotal, - setThisValid: sellForm.setAmountValid, - setTotalValid: sellForm.setTotalValid, - balance, - setRangeInputValue: sellForm.setRangeInputValue, - }); - } + handleInputChange({ + inputValue: amountStr, + priceOrAmount: 'amount', + otherValue: priceStr, + thisDP: firstCurrencyDP, + totalDP: secondCurrencyDP, + setThisState: orderForm.setAmount, + setTotalState: orderForm.setTotal, + setThisValid: orderForm.setAmountValid, + setTotalValid: orderForm.setTotalValid, + balance, + setRangeInputValue: orderForm.setRangeInputValue, + }); scrollToOrderForm(); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 0daf700..65a5d3f 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -86,13 +86,14 @@ export const roundTo = (x: number | string, digits = 7) => { return fixedValue.replace(/(\.\d*?[1-9])0+$/g, '$1').replace(/\.0+$/, ''); }; -export const notationToString = (notation: number | string) => { +export const notationToString = (notation: number | string, fixed?: number) => { const decimalValue = new Decimal(notation || '0'); - const fixedValue = decimalValue.toFixed(); + if (fixed !== undefined) { + return decimalValue.toDecimalPlaces(fixed).toString(); + } - // Remove trailing zeros - return fixedValue; + return decimalValue.toString(); }; export const localeTimeLeft = (now: number | null, timestamp: number) => { From dea3162ccdff8ed218d8adfa6b8d1fa678406a7a Mon Sep 17 00:00:00 2001 From: Dmitrii Kolpakov <85789854+jejolare@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:49:05 +0700 Subject: [PATCH 08/27] build From 193e9df821d29bd7823a405f80ef844e4a1e6d1e Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Mon, 25 Aug 2025 19:49:47 +0500 Subject: [PATCH 09/27] fix: notation --- src/components/dex/InputPanelItem/index.tsx | 4 ++-- src/components/dex/OrdersPool/index.tsx | 26 +++++++++++++-------- src/components/dex/OrdersPool/types.ts | 1 + src/pages/dex/trading/[id].tsx | 3 ++- src/utils/utils.ts | 4 ++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/components/dex/InputPanelItem/index.tsx b/src/components/dex/InputPanelItem/index.tsx index 6f8eac7..8a13902 100644 --- a/src/components/dex/InputPanelItem/index.tsx +++ b/src/components/dex/InputPanelItem/index.tsx @@ -5,7 +5,7 @@ import RangeInput from '@/components/UI/RangeInput/RangeInput'; import ConnectButton from '@/components/UI/ConnectButton/ConnectButton'; import Button from '@/components/UI/Button/Button'; import { useRouter } from 'next/router'; -import { classes, formatDollarValue } from '@/utils/utils'; +import { classes, formatDollarValue, notationToString } from '@/utils/utils'; import InputPanelItemProps from '@/interfaces/props/pages/dex/trading/InputPanelItem/InputPanelItemProps'; import CreateOrderData from '@/interfaces/fetch-data/create-order/CreateOrderData'; import Decimal from 'decimal.js'; @@ -197,7 +197,7 @@ function InputPanelItem(props: InputPanelItemProps) {
undefined} currency={secondCurrencyName} label="Total" diff --git a/src/components/dex/OrdersPool/index.tsx b/src/components/dex/OrdersPool/index.tsx index 7f3632a..bd680ee 100644 --- a/src/components/dex/OrdersPool/index.tsx +++ b/src/components/dex/OrdersPool/index.tsx @@ -29,6 +29,7 @@ const tabsData: tabsType[] = [ const OrdersPool = (props: OrdersPoolProps) => { const { ordersBuySell, + OrdersHistory, setOrdersBuySell, currencyNames, ordersLoading, @@ -44,7 +45,7 @@ const OrdersPool = (props: OrdersPoolProps) => { const [infoTooltipPos, setInfoTooltipPos] = useState({ x: 0, y: 0 }); const [ordersInfoTooltip, setOrdersInfoTooltip] = useState(null); const [currentOrder, setCurrentOrder] = useState(tabsData[0]); - const { maxBuyLeftValue, maxSellLeftValue } = filteredOrdersHistory.reduce( + const { maxBuyLeftValue, maxSellLeftValue } = OrdersHistory.reduce( (acc, order) => { const left = parseFloat(String(order.left)) || 0; if (order.type === 'buy') acc.maxBuyLeftValue = Math.max(acc.maxBuyLeftValue, left); @@ -200,7 +201,7 @@ const OrdersPool = (props: OrdersPoolProps) => {
{renderTable()} - {currentOrder.type === 'orders' && ( + {currentOrder.type === 'orders' && totalLeft > 0 && (
{ : '#FF6767', }} > - {notationToString(ordersInfoTooltip?.price)} + {ordersInfoTooltip?.price}

~ {secondAssetUsdPrice && ordersInfoTooltip?.price !== undefined ? (() => { - const total = - secondAssetUsdPrice * ordersInfoTooltip.price; - const formatted = - ordersInfoTooltip.price < 0.9 - ? `$${total.toFixed(5)}` - : `$${total.toFixed(2)}`; - return formatted; + const total = new Decimal(secondAssetUsdPrice).mul( + ordersInfoTooltip.price, + ); + + if (total.abs().lt(0.01)) { + return `$${total + .toFixed(8) + .replace(/(\.\d*?[1-9])0+$/, '$1') + .replace(/\.0+$/, '')}`; + } + + return `$${total.toFixed(2).replace(/\.0+$/, '')}`; })() : 'undefined'} diff --git a/src/components/dex/OrdersPool/types.ts b/src/components/dex/OrdersPool/types.ts index 21406a3..3b42068 100644 --- a/src/components/dex/OrdersPool/types.ts +++ b/src/components/dex/OrdersPool/types.ts @@ -13,6 +13,7 @@ export interface OrdersPoolProps { secondCurrencyName: string; }; ordersLoading: boolean; + OrdersHistory: PageOrderData[]; filteredOrdersHistory: PageOrderData[]; trades: Trade[]; tradesLoading: boolean; diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index cd02895..1b9d8f3 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -163,11 +163,12 @@ function Trading() { ordersBuySell={ordersBuySell} ordersLoading={ordersLoading} filteredOrdersHistory={filteredOrdersHistory} - trades={trades} + trades={trades.slice(0, 100)} tradesLoading={tradesLoading} setOrdersBuySell={setOrdersBuySell} takeOrderClick={onHandleTakeOrder} matrixAddresses={matrixAddresses} + OrdersHistory={ordersHistory} />
diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 65a5d3f..38df361 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -90,10 +90,10 @@ export const notationToString = (notation: number | string, fixed?: number) => { const decimalValue = new Decimal(notation || '0'); if (fixed !== undefined) { - return decimalValue.toDecimalPlaces(fixed).toString(); + return decimalValue.toFixed(fixed).replace(/\.?0+$/, ''); } - return decimalValue.toString(); + return decimalValue.toFixed(); }; export const localeTimeLeft = (now: number | null, timestamp: number) => { From c36a8d9bb4fc4a76de78ab28fdc61c649140dc49 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Mon, 25 Aug 2025 19:53:34 +0500 Subject: [PATCH 10/27] fix: price decimals --- src/components/dex/OrdersPool/columns/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dex/OrdersPool/columns/index.tsx b/src/components/dex/OrdersPool/columns/index.tsx index 16aec5c..b7a24fb 100644 --- a/src/components/dex/OrdersPool/columns/index.tsx +++ b/src/components/dex/OrdersPool/columns/index.tsx @@ -47,7 +47,7 @@ export function buildOrderPoolColumns({ key: 'total', header: <>Total ({secondCurrencyName}), width: '80px', - cell: (row) => , + cell: (row) => , }, ]; } From 8c9e23ee4e10ebc36c9567f5f29b78c9f93aec2c Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Mon, 25 Aug 2025 21:10:08 +0500 Subject: [PATCH 11/27] format --- .../dex/AliasCell/styles.module.scss | 50 +++++++++---------- .../MatrixConnectionBadge/styles.module.scss | 2 +- .../dex/OrdersPool/styles.module.scss | 6 +-- .../dex/UserOrders/styles.module.scss | 6 +-- src/styles/themes/dark.scss | 2 +- src/styles/themes/light.scss | 6 +-- 6 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/components/dex/AliasCell/styles.module.scss b/src/components/dex/AliasCell/styles.module.scss index 64a3495..030713b 100644 --- a/src/components/dex/AliasCell/styles.module.scss +++ b/src/components/dex/AliasCell/styles.module.scss @@ -1,33 +1,33 @@ .alias { - display: flex; - align-items: center; - gap: 4px; - font-size: 14px; + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; - &__text { - color: var(--font-main-color) !important; - } + &__text { + color: var(--font-main-color) !important; + } - path { - fill: none; - } + path { + fill: none; + } } .tooltip { - position: absolute; - top: 30px; - left: 5%; - background-color: var(--trade-table-tooltip); - z-index: 9999 !important; + position: absolute; + top: 30px; + left: 5%; + background-color: var(--trade-table-tooltip); + z-index: 9999 !important; - &__text { - font-size: 12px !important; - color: var(--font-main-color) !important; - } + &__text { + font-size: 12px !important; + color: var(--font-main-color) !important; + } - &__arrow { - border-radius: 2px; - left: 30% !important; - background-color: var(--trade-table-tooltip) !important; - } -} \ No newline at end of file + &__arrow { + border-radius: 2px; + left: 30% !important; + background-color: var(--trade-table-tooltip) !important; + } +} diff --git a/src/components/dex/MatrixConnectionBadge/styles.module.scss b/src/components/dex/MatrixConnectionBadge/styles.module.scss index b4e816b..6ac3d79 100644 --- a/src/components/dex/MatrixConnectionBadge/styles.module.scss +++ b/src/components/dex/MatrixConnectionBadge/styles.module.scss @@ -21,4 +21,4 @@ background-color: var(--trade-table-tooltip) !important; } } -} \ No newline at end of file +} diff --git a/src/components/dex/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss index 6ef00e6..f587124 100644 --- a/src/components/dex/OrdersPool/styles.module.scss +++ b/src/components/dex/OrdersPool/styles.module.scss @@ -104,11 +104,7 @@ td { position: static !important; - - &:last-child { - - &::before { content: ''; pointer-events: none; @@ -233,4 +229,4 @@ font-size: 11px; font-weight: 400; } -} \ No newline at end of file +} diff --git a/src/components/dex/UserOrders/styles.module.scss b/src/components/dex/UserOrders/styles.module.scss index ff08160..2ec7a8f 100644 --- a/src/components/dex/UserOrders/styles.module.scss +++ b/src/components/dex/UserOrders/styles.module.scss @@ -57,7 +57,7 @@ td { position: relative; - >p { + > p { width: 100%; font-size: 12px; font-weight: 400; @@ -76,7 +76,7 @@ } } - >span { + > span { line-height: 1; color: var(--font-dimmed-color); font-size: 11px; @@ -85,4 +85,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/styles/themes/dark.scss b/src/styles/themes/dark.scss index eac3a55..251b51a 100644 --- a/src/styles/themes/dark.scss +++ b/src/styles/themes/dark.scss @@ -52,4 +52,4 @@ --dex-tooltip-border-color: #1f8feb26; --dex-sell-percentage: #272757; --dex-buy-percentage: #103262; -} \ No newline at end of file +} diff --git a/src/styles/themes/light.scss b/src/styles/themes/light.scss index f65dc65..652b905 100644 --- a/src/styles/themes/light.scss +++ b/src/styles/themes/light.scss @@ -50,6 +50,6 @@ --table-tr-hover-color: #dcf0ff; --dex-tooltip-bg: #eff8ff; --dex-tooltip-border-color: #1f8feb33; - --dex-sell-percentage: #FCEDED; - --dex-buy-percentage: #E5F8F8; -} \ No newline at end of file + --dex-sell-percentage: #fceded; + --dex-buy-percentage: #e5f8f8; +} From 37e267a2197a60d3d8fb1ce7b962e8648720f81d Mon Sep 17 00:00:00 2001 From: Dmitrii Kolpakov <85789854+jejolare@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:15:02 +0700 Subject: [PATCH 12/27] build From a6fceae5d56b4efb7b383eeb4b021da8e48110c1 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Tue, 26 Aug 2025 14:24:13 +0500 Subject: [PATCH 13/27] fix: orders --- src/components/dex/OrdersPool/index.tsx | 100 ++++++++++++------ .../dex/OrdersPool/styles.module.scss | 4 +- src/pages/dex/trading/[id].tsx | 2 +- src/utils/utils.ts | 21 ++++ 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/src/components/dex/OrdersPool/index.tsx b/src/components/dex/OrdersPool/index.tsx index bd680ee..0cf2043 100644 --- a/src/components/dex/OrdersPool/index.tsx +++ b/src/components/dex/OrdersPool/index.tsx @@ -1,5 +1,11 @@ import React, { useMemo, useRef, useState } from 'react'; -import { classes, cutAddress, formatDollarValue, notationToString } from '@/utils/utils'; +import { + classes, + createOrderSorter, + cutAddress, + formatDollarValue, + notationToString, +} from '@/utils/utils'; import { nanoid } from 'nanoid'; import Decimal from 'decimal.js'; import Tooltip from '@/components/UI/Tooltip/Tooltip'; @@ -80,6 +86,11 @@ const OrdersPool = (props: OrdersPoolProps) => { [firstCurrencyName, secondCurrencyName], ); + const sortedTrades = createOrderSorter({ + getPrice: (e) => e.price, + getSide: (e) => e.type, + }); + const renderTable = () => { switch (currentOrder.type) { case 'orders': @@ -93,10 +104,7 @@ const OrdersPool = (props: OrdersPoolProps) => { tbodyClassName={styles.table__body} theadClassName={styles.table__header} columns={ordersPool} - data={filteredOrdersHistory.sort((a, b) => { - if (a.type === b.type) return 0; - return a.type === 'buy' ? -1 : 1; - })} + data={filteredOrdersHistory.sort(sortedTrades)} getRowKey={(r) => r.id} getRowProps={(row) => ({ className: styles[row.type], @@ -201,32 +209,62 @@ const OrdersPool = (props: OrdersPoolProps) => {
{renderTable()} - {currentOrder.type === 'orders' && totalLeft > 0 && ( -
-
-
B
{' '} - {notationToString((maxBuyLeftValue / totalLeft) * 100, 0)}% -
-
- {notationToString((maxSellLeftValue / totalLeft) * 100, 0)}%{' '} -
S
-
-
- )} + {currentOrder.type === 'orders' && + totalLeft > 0 && + (() => { + const buy = new Decimal(maxBuyLeftValue || 0); + const sell = new Decimal(maxSellLeftValue || 0); + const total = new Decimal(totalLeft); + + let buyPct = total.gt(0) ? buy.mul(100).div(total) : new Decimal(0); + let sellPct = total.gt(0) ? sell.mul(100).div(total) : new Decimal(0); + + if (buy.isZero() && sell.gt(0)) { + buyPct = new Decimal(0); + sellPct = new Decimal(100); + } else if (sell.isZero() && buy.gt(0)) { + sellPct = new Decimal(0); + buyPct = new Decimal(100); + } + + const clamp = (d: Decimal) => Decimal.max(0, Decimal.min(100, d)); + + const buyPctClamped = clamp(buyPct); + const sellPctClamped = clamp(sellPct); + + const buyLabel = buyPctClamped + .toDecimalPlaces(0, Decimal.ROUND_DOWN) + .toString(); + const sellLabel = sellPctClamped + .toDecimalPlaces(0, Decimal.ROUND_DOWN) + .toString(); + + return ( +
+
+
B
{buyLabel}% +
+
+ {sellLabel}%{' '} +
S
+
+
+ ); + })()}
diff --git a/src/components/dex/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss index f587124..99a8495 100644 --- a/src/components/dex/OrdersPool/styles.module.scss +++ b/src/components/dex/OrdersPool/styles.module.scss @@ -1,6 +1,6 @@ .ordersPool { position: relative; - max-width: 415px; + max-width: 440px; width: 100%; height: 100%; padding: 5px; @@ -229,4 +229,4 @@ font-size: 11px; font-weight: 400; } -} +} \ No newline at end of file diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 1b9d8f3..172039b 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -163,7 +163,7 @@ function Trading() { ordersBuySell={ordersBuySell} ordersLoading={ordersLoading} filteredOrdersHistory={filteredOrdersHistory} - trades={trades.slice(0, 100)} + trades={trades} tradesLoading={tradesLoading} setOrdersBuySell={setOrdersBuySell} takeOrderClick={onHandleTakeOrder} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 38df361..fceb632 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -174,6 +174,27 @@ export const getAssetIcon = (assetId: string): string => { return '/tokens/token.png'; }; +type Getters = { + getSide: (_item: T) => 'buy' | 'sell'; + getPrice: (_item: T) => string | number | Decimal; +}; + +export function createOrderSorter({ getSide, getPrice }: Getters) { + return (a: T, b: T) => { + const aSide = getSide(a); + const bSide = getSide(b); + if (aSide !== bSide) return aSide === 'buy' ? -1 : 1; + + const ap = new Decimal(getPrice(a)); + const bp = new Decimal(getPrice(b)); + + if (aSide === 'buy') { + return bp.comparedTo(ap); + } + return ap.comparedTo(bp); + }; +} + export const ZANO_ASSET_ID = 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a'; const knownCurrencies = ['zano', 'xmr', 'btc', 'firo', 'usd', 'eur', 'cad', 'jpy']; From b0a54821465e6d360b1b4b9692e19a032bae8e07 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Tue, 26 Aug 2025 16:58:03 +0500 Subject: [PATCH 14/27] add: fixes --- src/assets/images/UI/connection.svg | 2 +- src/components/UI/Tabs/types.ts | 4 +- src/components/dex/AliasCell/index.tsx | 4 +- src/components/dex/AliasCell/types.ts | 1 + src/components/dex/AllTrades/index.tsx | 82 ------------- .../dex/AllTrades/styles.module.scss | 116 ------------------ src/components/dex/AllTrades/types.ts | 15 --- src/components/dex/InputPanelItem/index.tsx | 14 +++ .../dex/MatrixConnectionBadge/index.tsx | 4 +- .../MatrixConnectionBadge/styles.module.scss | 12 ++ .../dex/MatrixConnectionBadge/types.ts | 2 +- .../dex/OrdersPool/columns/index.tsx | 3 + src/components/dex/OrdersPool/index.tsx | 4 +- .../dex/OrdersPool/styles.module.scss | 6 +- src/components/dex/UserOrders/index.tsx | 25 +++- src/hook/useQuerySyncedTab.ts | 71 +++++++++++ src/pages/dex/trading/[id].tsx | 8 -- src/utils/utils.ts | 4 +- 18 files changed, 139 insertions(+), 238 deletions(-) delete mode 100644 src/components/dex/AllTrades/index.tsx delete mode 100644 src/components/dex/AllTrades/styles.module.scss delete mode 100644 src/components/dex/AllTrades/types.ts create mode 100644 src/hook/useQuerySyncedTab.ts diff --git a/src/assets/images/UI/connection.svg b/src/assets/images/UI/connection.svg index 54066a9..f5d5c55 100644 --- a/src/assets/images/UI/connection.svg +++ b/src/assets/images/UI/connection.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/UI/Tabs/types.ts b/src/components/UI/Tabs/types.ts index c7c0066..2ef8d95 100644 --- a/src/components/UI/Tabs/types.ts +++ b/src/components/UI/Tabs/types.ts @@ -1,5 +1,3 @@ -import { Dispatch, SetStateAction } from 'react'; - export type tabsType = { title: string; type: string; @@ -8,6 +6,6 @@ export type tabsType = { export interface TabsProps { value: tabsType; - setValue: Dispatch>; + setValue: (_next: tabsType) => void; data: tabsType[]; } diff --git a/src/components/dex/AliasCell/index.tsx b/src/components/dex/AliasCell/index.tsx index 64673c6..1a5c923 100644 --- a/src/components/dex/AliasCell/index.tsx +++ b/src/components/dex/AliasCell/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import Tooltip from '@/components/UI/Tooltip/Tooltip'; -import { cutAddress } from '@/utils/utils'; +import { classes, cutAddress } from '@/utils/utils'; import MatrixConnectionBadge from '@/components/dex/MatrixConnectionBadge'; import BadgeStatus from '@/components/dex/BadgeStatus'; import { createPortal } from 'react-dom'; @@ -12,6 +12,7 @@ export default function AliasCell({ address, matrixAddresses, isInstant, + isSm, max = 12, }: AliasCellProps) { const display = alias ? cutAddress(alias, max) : 'no alias'; @@ -59,6 +60,7 @@ export default function AliasCell({ userAdress={address} userAlias={alias} matrixAddresses={matrixAddresses} + isSm={isSm} /> {isInstant && ( diff --git a/src/components/dex/AliasCell/types.ts b/src/components/dex/AliasCell/types.ts index c661e7b..d92c771 100644 --- a/src/components/dex/AliasCell/types.ts +++ b/src/components/dex/AliasCell/types.ts @@ -5,5 +5,6 @@ export interface AliasCellProps { address?: string; matrixAddresses: MatrixAddress[]; isInstant?: boolean; + isSm?: boolean; max?: number; } diff --git a/src/components/dex/AllTrades/index.tsx b/src/components/dex/AllTrades/index.tsx deleted file mode 100644 index 8e9a3cb..0000000 --- a/src/components/dex/AllTrades/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { classes, formatTime } from '@/utils/utils'; -import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; -import EmptyMessage from '@/components/UI/EmptyMessage'; -import styles from './styles.module.scss'; -import { AllTradesProps } from './types'; - -const AllTrades = ({ - setTradesType, - tradesType, - filteredTrades, - tradesLoading, - currencyNames, -}: AllTradesProps) => { - return ( -
-
- - - -
- -
- - - - - - - - - - {!tradesLoading && !!filteredTrades.length && ( - - {filteredTrades.map((trade) => ( - - - - - - ))} - - )} -
Price ({currencyNames.secondCurrencyName})Amount ({currencyNames.firstCurrencyName})Time
-

- {trade.price} -

-
-

{trade.amount}

-
-

{formatTime(trade.timestamp)}

-
- - {!filteredTrades.length && !tradesLoading && } - - {tradesLoading && } -
-
- ); -}; - -export default AllTrades; diff --git a/src/components/dex/AllTrades/styles.module.scss b/src/components/dex/AllTrades/styles.module.scss deleted file mode 100644 index aae3f21..0000000 --- a/src/components/dex/AllTrades/styles.module.scss +++ /dev/null @@ -1,116 +0,0 @@ -.allTrades { - max-width: 415px; - width: 100%; - padding: 5px; - background: var(--window-bg-color); - border: 1px solid var(--delimiter-color); - border-radius: 10px; - - @media screen and (max-width: 1480px) { - max-width: 340px; - } - - &__header { - border-bottom: 1px solid var(--delimiter-color); - display: flex; - align-items: center; - gap: 22px; - padding: 10px; - padding-bottom: 0; - - &_btn { - padding-bottom: 7px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - font-size: 16px; - border-bottom: 2px solid transparent; - font-weight: 600; - background-color: transparent !important; - cursor: pointer; - - &.active { - border-color: #1f8feb; - } - - &:hover { - color: #1f8feb; - } - } - } - - &__content { - display: flex; - flex-direction: column; - padding-top: 10px; - - table { - width: 100%; - - thead { - display: flex; - width: 100%; - padding-inline: 10px; - margin-bottom: 9px; - - tr { - width: 100%; - display: flex; - justify-content: space-between; - - th { - min-width: 80px; - font-size: 11px; - font-weight: 700; - text-align: start; - color: var(--table-th-color); - - &:last-child { - text-align: right; - } - } - } - } - - tbody { - height: 29dvh; - min-height: 265px; - max-height: 380px; - display: flex; - flex-direction: column; - overflow: auto; - padding: 10px; - padding-bottom: 20px; - - tr { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 4px 0; - - &:nth-child(even) { - background-color: var(--table-even-bg); - } - - td { - position: relative; - - &:last-child { - > p { - text-align: right; - } - } - - > p { - min-width: 80px; - width: 100%; - font-size: 12px; - font-weight: 400; - } - } - } - } - } - } -} diff --git a/src/components/dex/AllTrades/types.ts b/src/components/dex/AllTrades/types.ts deleted file mode 100644 index 1fb3096..0000000 --- a/src/components/dex/AllTrades/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; -import { Dispatch, SetStateAction } from 'react'; - -type tradeType = 'all' | 'my'; - -export interface AllTradesProps { - setTradesType: Dispatch>; - tradesType: tradeType; - filteredTrades: Trade[]; - tradesLoading: boolean; - currencyNames: { - firstCurrencyName: string; - secondCurrencyName: string; - }; -} diff --git a/src/components/dex/InputPanelItem/index.tsx b/src/components/dex/InputPanelItem/index.tsx index 8a13902..5e61ecd 100644 --- a/src/components/dex/InputPanelItem/index.tsx +++ b/src/components/dex/InputPanelItem/index.tsx @@ -14,6 +14,7 @@ import infoIcon from '@/assets/images/UI/info_alert_icon.svg'; import Image from 'next/image'; import { useAlert } from '@/hook/useAlert'; import { buySellValues } from '@/constants'; +import { usePathname, useSearchParams } from 'next/navigation'; import styles from './styles.module.scss'; import LabeledInput from './components/LabeledInput'; @@ -40,6 +41,8 @@ function InputPanelItem(props: InputPanelItemProps) { const { state } = useContext(Store); const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); const { setAlertState, setAlertSubtitle } = useAlert(); const [creatingState, setCreatingState] = useState(false); const { firstCurrencyName, secondCurrencyName } = currencyNames; @@ -47,6 +50,16 @@ function InputPanelItem(props: InputPanelItemProps) { const [hasImmediateMatch, setHasImmediateMatch] = useState(false); const isBuy = buySellState?.code === 'buy'; + function goToSuitableTab() { + const params = new URLSearchParams(searchParams.toString()); + params.set('tab', 'suitable'); + + router.replace(`${pathname}?${params.toString()}`, undefined, { + shallow: true, + scroll: false, + }); + } + function resetForm() { setPriceFunction(''); setAmountFunction(''); @@ -126,6 +139,7 @@ function InputPanelItem(props: InputPanelItemProps) { className={styles.applyAlert__button} onClick={() => { scrollToOrderList(); + goToSuitableTab(); setHasImmediateMatch(false); }} > diff --git a/src/components/dex/MatrixConnectionBadge/index.tsx b/src/components/dex/MatrixConnectionBadge/index.tsx index faf040f..1d28078 100644 --- a/src/components/dex/MatrixConnectionBadge/index.tsx +++ b/src/components/dex/MatrixConnectionBadge/index.tsx @@ -10,7 +10,7 @@ function MatrixConnectionBadge({ userAdress, userAlias, matrixAddresses, - className, + isSm, }: MatrixConnectionBadgeProps) { const hasConnection = (address: string) => matrixAddresses.some((item) => item.address === address && item.registered); @@ -44,7 +44,7 @@ function MatrixConnectionBadge({ 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 6ac3d79..61504d8 100644 --- a/src/components/dex/MatrixConnectionBadge/styles.module.scss +++ b/src/components/dex/MatrixConnectionBadge/styles.module.scss @@ -1,9 +1,21 @@ .badge { position: relative; + &.sm { + .badge__link svg { + width: 14px; + height: 14px; + } + } + &__link { margin-top: 4px; cursor: pointer; + + svg { + width: 16px; + height: 16px; + } } &__tooltip { diff --git a/src/components/dex/MatrixConnectionBadge/types.ts b/src/components/dex/MatrixConnectionBadge/types.ts index 08a6668..1e49526 100644 --- a/src/components/dex/MatrixConnectionBadge/types.ts +++ b/src/components/dex/MatrixConnectionBadge/types.ts @@ -1,7 +1,7 @@ import MatrixAddress from '@/interfaces/common/MatrixAddress'; export interface MatrixConnectionBadgeProps { - className?: string; + isSm?: boolean; userAdress?: string; userAlias?: string; matrixAddresses: MatrixAddress[]; diff --git a/src/components/dex/OrdersPool/columns/index.tsx b/src/components/dex/OrdersPool/columns/index.tsx index b7a24fb..3a982c0 100644 --- a/src/components/dex/OrdersPool/columns/index.tsx +++ b/src/components/dex/OrdersPool/columns/index.tsx @@ -19,6 +19,7 @@ export function buildOrderPoolColumns({ className: 'alias', cell: (row) => ( Total ({secondCurrencyName}), width: '80px', + align: 'right', cell: (row) => , }, ]; @@ -75,6 +77,7 @@ export function buildTradesColumns({ }, { key: 'time', + align: 'right', header: <>Time, width: '80px', cell: (row) =>

{formatTimestamp(row.timestamp)}

, diff --git a/src/components/dex/OrdersPool/index.tsx b/src/components/dex/OrdersPool/index.tsx index 0cf2043..04f2d17 100644 --- a/src/components/dex/OrdersPool/index.tsx +++ b/src/components/dex/OrdersPool/index.tsx @@ -145,12 +145,12 @@ const OrdersPool = (props: OrdersPoolProps) => { <> {!tradesLoading ? ( r.id} /> ) : ( diff --git a/src/components/dex/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss index 99a8495..d3ef712 100644 --- a/src/components/dex/OrdersPool/styles.module.scss +++ b/src/components/dex/OrdersPool/styles.module.scss @@ -60,6 +60,10 @@ width: 100%; height: 410px; + &.full { + height: 480px; + } + .table { width: 100%; @@ -229,4 +233,4 @@ font-size: 11px; font-weight: 400; } -} \ No newline at end of file +} diff --git a/src/components/dex/UserOrders/index.tsx b/src/components/dex/UserOrders/index.tsx index 0aa98c6..691b9a7 100644 --- a/src/components/dex/UserOrders/index.tsx +++ b/src/components/dex/UserOrders/index.tsx @@ -1,4 +1,3 @@ -import { classes } from '@/utils/utils'; import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; import useUpdateUser from '@/hook/useUpdateUser'; import EmptyMessage from '@/components/UI/EmptyMessage'; @@ -13,6 +12,9 @@ import { useAlert } from '@/hook/useAlert'; import Alert from '@/components/UI/Alert/Alert'; import { tabsType } from '@/components/UI/Tabs/types'; import Tabs from '@/components/UI/Tabs'; +import { createOrderSorter } from '@/utils/utils'; +import ApplyTip from '@/interfaces/common/ApplyTip'; +import { useQuerySyncedTab } from '@/hook/useQuerySyncedTab'; import { UserOrdersProps } from './types'; import styles from './styles.module.scss'; import { @@ -80,9 +82,15 @@ const UserOrders = ({ ], ); - const [ordersType, setOrdersType] = useState(tabsData[0]); + const { active: ordersType, setActiveTab } = useQuerySyncedTab({ + tabs: tabsData, + defaultType: 'opened', + queryKey: 'tab', + }); useEffect(() => { + if (!loggedIn) return; + (async () => { const requestsData = await getUserPendings(); @@ -204,6 +212,11 @@ const UserOrders = ({ [firstCurrencyName, secondCurrencyName, secondAssetUsdPrice], ); + const sortedSuitables = createOrderSorter({ + getPrice: (e) => e.price, + getSide: (e) => e.type, + }); + const renderTable = () => { switch (ordersType.type) { case 'opened': @@ -221,7 +234,7 @@ const UserOrders = ({ r.id} emptyMessage="No suitables" /> @@ -265,7 +278,11 @@ const UserOrders = ({ <>
- + setActiveTab(t.type)} + /> {ordersType?.type === 'opened' && userOrders.length > 0 && ( = { + title: string; + type: T; + length?: number; +}; + +type Options = { + tabs: TabItem[]; + queryKey?: string; + defaultType?: T; + replace?: boolean; +}; + +export function useQuerySyncedTab({ + tabs, + queryKey = 'tab', + defaultType, + replace = true, +}: Options) { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const urlValue = searchParams.get(queryKey) as T | null; + + const initialTab = useMemo(() => { + const fallback = (defaultType ?? tabs[0]?.type) as T; + if (!urlValue) return tabs.find((t) => t.type === fallback) ?? tabs[0]; + return ( + tabs.find((t) => t.type === urlValue) ?? + tabs.find((t) => t.type === fallback) ?? + tabs[0] + ); + }, [tabs, urlValue, defaultType]); + + const [active, setActive] = useState>(initialTab); + + useEffect(() => { + setActive(initialTab); + }, [initialTab]); + + const setActiveTab = (next: TabItem | T) => { + const nextType = (typeof next === 'string' ? next : next.type) as T; + + const found = tabs.find((t) => t.type === nextType); + if (found) setActive(found); + + const params = new URLSearchParams(searchParams.toString()); + const def = (defaultType ?? tabs[0]?.type) as T; + if (nextType === def) { + params.delete(queryKey); + } else { + params.set(queryKey, nextType); + } + + const url = params.toString() ? `${pathname}?${params}` : pathname; + + if (replace) { + router.replace(url, { scroll: false }); + } else { + router.push(url, { scroll: false }); + } + }; + + return { active, setActiveTab }; +} diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 172039b..09f0ad2 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -23,7 +23,6 @@ import TradingHeader from '@/components/dex/TradingHeader'; import UserOrders from '@/components/dex/UserOrders'; import OrdersPool from '@/components/dex/OrdersPool'; import CandleChart from '@/components/dex/CandleChart'; -import AllTrades from '@/components/dex/AllTrades'; import { useSocketListeners } from '@/hook/useSocketListeners'; import { useTradingData } from '@/hook/useTradingData'; import useFilteredData from '@/hook/useFilteredData'; @@ -216,13 +215,6 @@ function Trading() { onAfter={onAfter} />
- {/* */}
diff --git a/src/utils/utils.ts b/src/utils/utils.ts index fceb632..e0be10b 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -154,10 +154,10 @@ export function formatTimestamp(ms: number | string) { 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 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}`; + return `${hh}:${mm}:${ss} ${DD}-${MM}-${YYYY}`; } export function classes(...classes: (string | boolean | undefined)[]): string { From 6b552a4b11d0d38dd349a624f9b961a4d0f97427 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Tue, 26 Aug 2025 21:36:38 +0500 Subject: [PATCH 15/27] change: footer links --- src/components/default/Footer/Footer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/default/Footer/Footer.tsx b/src/components/default/Footer/Footer.tsx index 55099d8..134e646 100644 --- a/src/components/default/Footer/Footer.tsx +++ b/src/components/default/Footer/Footer.tsx @@ -29,12 +29,12 @@ const links: { title: 'Auction', type: 'auction', link: 'https://wrapped.zano.org/', + disabled: true, }, { title: 'Messenger', type: 'messenger', - link: 'https://zano.org/', - disabled: true, + link: 'https://messenger.zano.org/', }, { title: 'Wrapped Zano', From bfa8ddfff275e632af3d8f3f709682e77cf218ca Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Thu, 28 Aug 2025 15:31:39 +0500 Subject: [PATCH 16/27] finish: redesign --- .../UI/ActionBtn/styles.module.scss | 7 +- .../HorizontalSelect.module.scss | 19 +- .../UI/HorizontalSelect/HorizontalSelect.tsx | 8 +- .../default/BackButton/BackButton.module.scss | 9 +- .../default/BackButton/BackButton.tsx | 11 +- src/components/default/BackButton/types.ts | 4 + .../default/Header/Header.module.scss | 49 ++- src/components/default/Header/Header.tsx | 4 +- .../default/Header/NavBar/NavBar.module.scss | 23 +- .../default/Header/NavBar/NavBar.tsx | 9 +- src/components/dex/CandleChart/index.tsx | 301 ++++++++---------- .../dex/CandleChart/styles.module.scss | 35 +- src/components/dex/CandleChart/types.ts | 1 + src/components/dex/CandleChart/utils.ts | 116 +++++++ .../components/AssetRow/styles.module.scss | 4 +- .../components}/StatItem/index.tsx | 4 +- .../components}/StatItem/styles.module.scss | 14 +- src/components/dex/TradingHeader/index.tsx | 66 ++-- .../dex/TradingHeader/styles.module.scss | 21 +- src/constants/index.ts | 26 +- src/interfaces/common/Period.ts | 2 +- .../HorizontalSelect/HorizontalSelectProps.ts | 1 + .../default/Header/NavBar/NavBarProps.ts | 1 + .../CandleChartProps/CandleChartProps.ts | 4 + src/pages/dex/trading/[id].tsx | 20 +- src/styles/themes/dark.scss | 3 +- src/styles/themes/light.scss | 3 +- 27 files changed, 492 insertions(+), 273 deletions(-) create mode 100644 src/components/default/BackButton/types.ts create mode 100644 src/components/dex/CandleChart/types.ts create mode 100644 src/components/dex/CandleChart/utils.ts rename src/components/dex/{ => TradingHeader/components}/StatItem/index.tsx (88%) rename src/components/dex/{ => TradingHeader/components}/StatItem/styles.module.scss (81%) diff --git a/src/components/UI/ActionBtn/styles.module.scss b/src/components/UI/ActionBtn/styles.module.scss index 416c224..6d08052 100644 --- a/src/components/UI/ActionBtn/styles.module.scss +++ b/src/components/UI/ActionBtn/styles.module.scss @@ -4,7 +4,7 @@ outline: none; padding: 6px 12px; border-radius: 5px; - background: #273666; + background: var(--action-btn-bg); font-size: 12px; font-weight: 500; line-height: 100%; @@ -16,8 +16,7 @@ } &:hover { - opacity: 0.8; - background: #273666; + background: var(--action-btn-bg); } &.primary { @@ -31,4 +30,4 @@ &.danger { color: #ff6767; } -} +} \ No newline at end of file diff --git a/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss b/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss index 954b222..6de89ee 100644 --- a/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss +++ b/src/components/UI/HorizontalSelect/HorizontalSelect.module.scss @@ -4,10 +4,23 @@ overflow: auto; padding-bottom: 3px; + &.sm { + gap: 5px !important; + + >div { + a { + font-size: 14px !important; + padding: 8px !important; + border-radius: 8px !important; + line-height: 100%; + } + } + } + &.tab { gap: 3px; - > div { + >div { a { font-size: 16px; font-weight: 500; @@ -26,7 +39,7 @@ } } - > div { + >div { position: relative; .profile__filters__notification { @@ -58,4 +71,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/components/UI/HorizontalSelect/HorizontalSelect.tsx b/src/components/UI/HorizontalSelect/HorizontalSelect.tsx index c1d833f..6758c3c 100644 --- a/src/components/UI/HorizontalSelect/HorizontalSelect.tsx +++ b/src/components/UI/HorizontalSelect/HorizontalSelect.tsx @@ -2,6 +2,7 @@ import Link from 'next/link'; import { nanoid } from 'nanoid'; import HorizontalSelectProps from '@/interfaces/props/components/UI/HorizontalSelect/HorizontalSelectProps'; import HorizontalSelectValue from '@/interfaces/common/HorizontalSelectValue'; +import { classes } from '@/utils/utils'; import NotificationIndicator from '../NotificationIndicator/NotificationIndicator'; import styles from './HorizontalSelect.module.scss'; @@ -13,7 +14,12 @@ function HorizontalSelect(props: HorizontalSele return (
{props.body.map((e) => (
diff --git a/src/components/default/BackButton/BackButton.module.scss b/src/components/default/BackButton/BackButton.module.scss index 4c7d844..885af78 100644 --- a/src/components/default/BackButton/BackButton.module.scss +++ b/src/components/default/BackButton/BackButton.module.scss @@ -1,4 +1,11 @@ .back_btn { display: flex; gap: 12px; -} + + &.sm { + + padding: 12px 22px; + font-size: 14px; + font-weight: 500; + } +} \ No newline at end of file diff --git a/src/components/default/BackButton/BackButton.tsx b/src/components/default/BackButton/BackButton.tsx index c3b05d7..a89c8cd 100644 --- a/src/components/default/BackButton/BackButton.tsx +++ b/src/components/default/BackButton/BackButton.tsx @@ -1,14 +1,19 @@ import { ReactComponent as ArrowWhiteIcon } from '@/assets/images/UI/arrow_white.svg'; import Button from '@/components/UI/Button/Button'; import { useRouter } from 'next/router'; +import { classes } from '@/utils/utils'; import styles from './BackButton.module.scss'; +import { BackButtonProps } from './types'; -function BackButton() { +function BackButton({ className, isSm }: BackButtonProps) { const router = useRouter(); return ( - diff --git a/src/components/default/BackButton/types.ts b/src/components/default/BackButton/types.ts new file mode 100644 index 0000000..51a9baa --- /dev/null +++ b/src/components/default/BackButton/types.ts @@ -0,0 +1,4 @@ +export interface BackButtonProps { + className?: string; + isSm?: boolean; +} diff --git a/src/components/default/Header/Header.module.scss b/src/components/default/Header/Header.module.scss index 2560849..68cb924 100644 --- a/src/components/default/Header/Header.module.scss +++ b/src/components/default/Header/Header.module.scss @@ -12,6 +12,39 @@ &.lg { padding-inline: 60px; + height: 65px; + + .header__logo { + width: 180px; + height: 38px; + } + + .header__currency__check { + height: 48px !important; + gap: 25px !important; + } + + .header__account__wrapper { + .header__account__info { + p { + &:first-child { + font-size: 16px; + } + + font-size: 14px; + } + } + + .header__account__btn { + min-width: 48px !important; + height: 48px !important; + } + } + + .header__connect_btn { + height: 50px; + } + @media screen and (max-width: 1600px) { padding-inline: 40px; @@ -27,7 +60,7 @@ width: 202px; height: 40px; - > a { + >a { display: flex; } } @@ -77,6 +110,7 @@ .header__login { width: 100%; display: flex; + align-items: center; gap: 10px; transition: none; } @@ -106,7 +140,7 @@ .header__button__wrapper { position: relative; - > .offers__tooltip { + >.offers__tooltip { padding: 13px 20px; position: absolute; background: #11316b; @@ -206,7 +240,7 @@ display: flex; gap: 5px; - > span { + >span { max-width: 100px; overflow: hidden; text-overflow: ellipsis; @@ -231,6 +265,7 @@ } &.header__menu__mobile { + .header__account, .header__login { padding: 20px; @@ -255,11 +290,11 @@ cursor: pointer; transition: none; - > svg { + >svg { scale: 0.8; } - > div { + >div { width: 24px; height: 2px; margin-bottom: 6px; @@ -286,7 +321,7 @@ max-height: 200px; overflow: hidden; - > div { + >div { width: 100%; } @@ -391,4 +426,4 @@ background-color: #0000004c; z-index: 1; transform: all 0; -} +} \ No newline at end of file diff --git a/src/components/default/Header/Header.tsx b/src/components/default/Header/Header.tsx index e1c5cea..86d9d8f 100644 --- a/src/components/default/Header/Header.tsx +++ b/src/components/default/Header/Header.tsx @@ -355,9 +355,11 @@ function Header({ isLg }: { isLg?: boolean }) {
- +
+ {Menu()} +
diff --git a/src/components/default/Header/NavBar/NavBar.module.scss b/src/components/default/Header/NavBar/NavBar.module.scss index 87ef6fa..345de66 100644 --- a/src/components/default/Header/NavBar/NavBar.module.scss +++ b/src/components/default/Header/NavBar/NavBar.module.scss @@ -1,4 +1,25 @@ .nav { + &.lg { + a { + gap: 8px; + + h6 { + font-size: 14px; + font-weight: 600; + line-height: 140%; + } + + svg { + transform: scale(0.9); + } + + .badge { + padding: 3px; + min-width: 22px; + } + } + } + a { display: flex; align-items: center; @@ -73,4 +94,4 @@ font-size: 32px; } } -} +} \ No newline at end of file diff --git a/src/components/default/Header/NavBar/NavBar.tsx b/src/components/default/Header/NavBar/NavBar.tsx index 140ace6..d9fcfab 100644 --- a/src/components/default/Header/NavBar/NavBar.tsx +++ b/src/components/default/Header/NavBar/NavBar.tsx @@ -9,6 +9,7 @@ import NavBarProps from '@/interfaces/props/components/default/Header/NavBar/Nav import NotificationIndicator from '@/components/UI/NotificationIndicator/NotificationIndicator'; import { useContext } from 'react'; import { Store } from '@/store/store-reducer'; +import { classes } from '@/utils/utils'; import styles from './NavBar.module.scss'; function NavBar(props: NavBarProps) { @@ -40,14 +41,18 @@ function NavBar(props: NavBarProps) {
{title}
- + ); } return (