From dbdfbd1ed6bd813fad3c6c1c52c2dbf123721947 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Fri, 8 Aug 2025 16:40:10 +0500 Subject: [PATCH] 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 ( -
- badge image - {!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 ( - -

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

- {isLongContent && !noTooltip && ( - - {tooltipText} - - )} - - ); - } - - 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 ( - - -

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 */} - {/* */} - - - {notationToString(e.price)} - - {notationToString(e.amount)} - - - {notationToString(totalDecimal.toString())}{' '} - ~ ${totalValue && formatDollarValue(totalValue)} - - - {/* {localeTimeLeft(now, parseInt(e.expiration_timestamp, 10))} */} - - -

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

- - - - {cancellingState ? 'Process' : 'Cancel'} - - - - ); - } - - 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 ( - - -

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} - - )} - - - - {notationToString(e.price)} - - {notationToString(e.left)} - - - {notationToString(totalDecimal.toString())}{' '} - ~ ${totalValue && formatDollarValue(totalValue)} - - - - - - {applyingState ? 'Process' : 'Apply'} - - - - ); - } - - 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 ( <>
-
-
-
-
-
- currency -
-
-

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

-
-

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

- {pairRateUsd && ( -

- ~ ${pairRateUsd} -

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

- currency{' '} - {firstCurrencyName}: -

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

- currency{' '} - {secondCurrencyName}: -

- - {shortenAddress(firstAssetId || '')} - -
-
- )} - - - - -
+
+ - -
+
+ -
-
-
-
- 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 && ( -
- -
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';