From c440afc762b87b184a2f1fdebc06afe7cc01f9e4 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Mon, 15 Sep 2025 12:12:01 +0500 Subject: [PATCH 1/6] add: instant badge for orders --- src/components/dex/AliasCell/index.tsx | 2 +- .../dex/InputPanelItem/styles.module.scss | 4 +-- .../dex/OrdersPool/columns/index.tsx | 11 +++++- .../dex/OrdersPool/styles.module.scss | 2 +- src/styles/Dex.module.scss | 36 +++++++++---------- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/components/dex/AliasCell/index.tsx b/src/components/dex/AliasCell/index.tsx index 1a5c923..e42257e 100644 --- a/src/components/dex/AliasCell/index.tsx +++ b/src/components/dex/AliasCell/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import Tooltip from '@/components/UI/Tooltip/Tooltip'; -import { classes, cutAddress } from '@/utils/utils'; +import { cutAddress } from '@/utils/utils'; import MatrixConnectionBadge from '@/components/dex/MatrixConnectionBadge'; import BadgeStatus from '@/components/dex/BadgeStatus'; import { createPortal } from 'react-dom'; diff --git a/src/components/dex/InputPanelItem/styles.module.scss b/src/components/dex/InputPanelItem/styles.module.scss index a24daa2..8173b90 100644 --- a/src/components/dex/InputPanelItem/styles.module.scss +++ b/src/components/dex/InputPanelItem/styles.module.scss @@ -109,7 +109,7 @@ } .disabled { - opacity: .5; + opacity: 0.5; pointer-events: none; } @@ -199,4 +199,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/dex/OrdersPool/columns/index.tsx b/src/components/dex/OrdersPool/columns/index.tsx index 86a19da..779e176 100644 --- a/src/components/dex/OrdersPool/columns/index.tsx +++ b/src/components/dex/OrdersPool/columns/index.tsx @@ -5,6 +5,7 @@ import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; import { BuildColumnsArgs } from './types'; import TotalUsdCell from '../../TotalUsdCell'; import styles from '../styles.module.scss'; +import BadgeStatus from '../../BadgeStatus'; export function buildOrderPoolColumns({ firstCurrencyName, @@ -16,8 +17,16 @@ export function buildOrderPoolColumns({ header: <>Price ({secondCurrencyName}), width: '80px', cell: (row) => ( -

+

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

), }, diff --git a/src/components/dex/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss index 9e6360a..606d65e 100644 --- a/src/components/dex/OrdersPool/styles.module.scss +++ b/src/components/dex/OrdersPool/styles.module.scss @@ -262,4 +262,4 @@ .tooltip { display: none; } -} \ No newline at end of file +} diff --git a/src/styles/Dex.module.scss b/src/styles/Dex.module.scss index 6478dd5..02e31ee 100644 --- a/src/styles/Dex.module.scss +++ b/src/styles/Dex.module.scss @@ -8,7 +8,7 @@ height: 54px; padding: 0 12px; - >div { + > div { display: flex; align-items: center; gap: 10px; @@ -136,12 +136,12 @@ width: 240px; height: 42px; - >div:nth-child(1) { + > div:nth-child(1) { border-radius: 10px; padding: 11px 20px; - >div { - >div p { + > div { + > div p { font-size: 14px; font-weight: 500; } @@ -160,12 +160,12 @@ } } - >div:nth-child(2) { - >div { - >div { + > div:nth-child(2) { + > div { + > div { padding: 11px 20px; - >div { + > div { p { font-size: 14px; font-weight: 500; @@ -305,7 +305,7 @@ left: 50%; transform: translateX(-50%); - >p { + > p { width: 100%; font-size: 16px; text-wrap: wrap; @@ -323,11 +323,11 @@ } } - >p { + > p { margin-right: 6px; } - >input { + > input { margin-left: 33px; height: 18px; width: 18px; @@ -389,7 +389,7 @@ left: 50%; transform: translateX(-50%); - >p { + > p { width: 100%; font-size: 16px; text-wrap: wrap; @@ -401,14 +401,14 @@ display: block; } - >svg>* { + > svg > * { opacity: 1; } } } } - >.curve__chart { + > .curve__chart { display: block; width: 100%; height: 50px; @@ -419,7 +419,7 @@ align-items: center; gap: 15px; - >div:first-child { + > div:first-child { width: 48px; height: 48px; padding: 10px; @@ -429,13 +429,13 @@ background: var(--icon-bg-color); border-radius: 50%; - >img { + > img { width: auto; height: 100%; } } - >div:last-child { + > div:last-child { height: 48px; display: flex; flex-direction: column; @@ -504,4 +504,4 @@ } } } -} \ No newline at end of file +} From 0cc67c341d4401197b83795ec63b0eed33e115e0 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Wed, 17 Sep 2025 16:08:59 +0500 Subject: [PATCH 2/6] fix: insufficient balance --- src/components/dex/InputPanelItem/index.tsx | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/dex/InputPanelItem/index.tsx b/src/components/dex/InputPanelItem/index.tsx index a9595c8..a4f8e86 100644 --- a/src/components/dex/InputPanelItem/index.tsx +++ b/src/components/dex/InputPanelItem/index.tsx @@ -67,6 +67,11 @@ function InputPanelItem(props: InputPanelItemProps) { setRangeInputValue('50'); } + const numericBalance = Number(balance); + const numericZanoBalance = Number(zanoBalance); + const hasValidAssetBalance = Number.isFinite(numericBalance); + const hasValidZanoBalance = Number.isFinite(numericZanoBalance); + async function postOrder() { const price = new Decimal(priceState); const amount = new Decimal(amountState); @@ -81,22 +86,21 @@ function InputPanelItem(props: InputPanelItemProps) { if (!isFull) return; + const assetAmount = new Decimal(hasValidAssetBalance ? String(numericBalance) : '0'); + const zanoAmount = new Decimal(hasValidZanoBalance ? String(numericZanoBalance) : '0'); + if (isBuy) { - const zanoAmount = new Decimal(zanoBalance); if (zanoAmount.lessThan(total)) { setAlertState('error'); setAlertSubtitle('Insufficient ZANO balance'); setTimeout(() => setAlertState(null), 3000); return; } - } else { - const assetAmount = new Decimal(balance); - if (assetAmount.lessThan(amount)) { - setAlertState('error'); - setAlertSubtitle(`Insufficient ${firstCurrencyName} balance`); - setTimeout(() => setAlertState(null), 3000); - return; - } + } else if (assetAmount.lessThan(amount)) { + setAlertState('error'); + setAlertSubtitle(`Insufficient ${firstCurrencyName} balance`); + setTimeout(() => setAlertState(null), 3000); + return; } const orderData: CreateOrderData = { From 0d7b0e70a3c9504e401909a0dcaadc8e406a060e Mon Sep 17 00:00:00 2001 From: jejolare Date: Mon, 22 Sep 2025 19:56:52 +0700 Subject: [PATCH 3/6] fix 24h volume currency --- src/components/dex/TradingHeader/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dex/TradingHeader/index.tsx b/src/components/dex/TradingHeader/index.tsx index 8195d1d..554f744 100644 --- a/src/components/dex/TradingHeader/index.tsx +++ b/src/components/dex/TradingHeader/index.tsx @@ -53,7 +53,7 @@ const TradingHeader = ({ }, { Img: VolumeIcon, - title: `24h volume (${firstCurrencyName})`, + title: `24h volume (${secondCurrencyName})`, value: `${roundTo(notationToString(pairStats?.volume || 0), 4)}`, }, ]; From cbac820db7e1a735cc168c4267476d014594900e Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Thu, 25 Sep 2025 15:27:17 +0500 Subject: [PATCH 4/6] fix: safari bugs --- .../OrderNotification/OrderNotification.tsx | 4 +- .../dex/OrdersPool/styles.module.scss | 42 ++++++------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/components/default/OrderNotification/OrderNotification.tsx b/src/components/default/OrderNotification/OrderNotification.tsx index d982049..dfa4d2f 100644 --- a/src/components/default/OrderNotification/OrderNotification.tsx +++ b/src/components/default/OrderNotification/OrderNotification.tsx @@ -72,7 +72,9 @@ export default function OrderNotification({
- Details + + Details +
); diff --git a/src/components/dex/OrdersPool/styles.module.scss b/src/components/dex/OrdersPool/styles.module.scss index 606d65e..7262555 100644 --- a/src/components/dex/OrdersPool/styles.module.scss +++ b/src/components/dex/OrdersPool/styles.module.scss @@ -78,48 +78,29 @@ &__body { tr { cursor: pointer; - position: relative; + position: relative !important; &:hover { background-color: var(--table-tr-hover-color); } &.buy { - td { - &:last-child { - &::before { - background-color: var(--dex-buy-percentage); - } - } - } + background: linear-gradient( + to left, + var(--dex-buy-percentage) var(--precentage), + transparent var(--precentage) + ); } &.sell { - td { - &:last-child { - &::before { - background-color: var(--dex-sell-percentage); - } - } - } + background: linear-gradient( + to left, + var(--dex-sell-percentage) var(--precentage), + transparent var(--precentage) + ); } td { - position: static !important; - - &:last-child { - &::before { - content: ''; - pointer-events: none; - position: absolute; - z-index: 1; - right: 0; - top: 0; - width: var(--precentage); - height: 100%; - } - } - p, span { position: relative; @@ -199,6 +180,7 @@ padding: 10px; transform: translateX(-50%); background-color: var(--dex-tooltip-bg); + transition: none !important; &__arrow { border-top: 1px solid var(--dex-tooltip-border-color); From 0a52dcad1735175e11653a8edc2b434fa7a02118 Mon Sep 17 00:00:00 2001 From: Dmitrii Kolpakov <85789854+jejolare@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:12:02 +0700 Subject: [PATCH 5/6] Add CodeQL analysis workflow configuration --- .github/workflows/codeql.yml | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..9d263a3 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,99 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '21 18 * * 6' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From f6c7656bb92ac72e22b6567bacc2b5b42dd989f9 Mon Sep 17 00:00:00 2001 From: AzizbekFayziyev Date: Fri, 9 Jan 2026 20:18:35 +0500 Subject: [PATCH 6/6] feat: add SSR --- package-lock.json | 15 ++++++ package.json | 2 + src/components/dex/CandleChart/index.tsx | 27 ++++++---- .../dex/CandleChart/styles.module.scss | 15 ++++-- src/constants/index.ts | 2 + src/hook/useTradingData.ts | 33 ++++++++++-- .../props/pages/dex/trading/TradingProps.ts | 13 +++++ src/pages/_app.tsx | 23 +++++++- src/pages/dex/index.tsx | 33 ++++++++---- src/pages/dex/trading/[id].tsx | 52 ++++++++++++++++--- src/pages/dex/trading/find-pair/index.tsx | 3 +- src/styles/Trading.module.scss | 3 +- src/styles/globals.scss | 21 ++++++-- src/utils/methods.ts | 16 +++--- src/utils/socket.ts | 3 +- 15 files changed, 212 insertions(+), 49 deletions(-) create mode 100644 src/interfaces/props/pages/dex/trading/TradingProps.ts diff --git a/package-lock.json b/package-lock.json index 97a328c..32c1e28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "next": "^13.2.4", "next-themes": "^0.2.1", "node-fetch": "^3.3.1", + "nprogress": "^0.2.0", "react": "18.2.0", "react-apexcharts": "^1.5.0", "react-dom": "18.2.0", @@ -42,6 +43,7 @@ "@types/big.js": "^6.2.0", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", + "@types/nprogress": "^0.2.3", "@types/pg": "^8.10.2", "@types/react": "18.2.16", "@types/react-dom": "^18.2.7", @@ -4214,6 +4216,13 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/nprogress": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.3.tgz", + "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/pg": { "version": "8.15.4", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", @@ -9876,6 +9885,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", diff --git a/package.json b/package.json index 61da024..a858f7d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "next": "^13.2.4", "next-themes": "^0.2.1", "node-fetch": "^3.3.1", + "nprogress": "^0.2.0", "react": "18.2.0", "react-apexcharts": "^1.5.0", "react-dom": "18.2.0", @@ -54,6 +55,7 @@ "@types/big.js": "^6.2.0", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", + "@types/nprogress": "^0.2.3", "@types/pg": "^8.10.2", "@types/react": "18.2.16", "@types/react-dom": "^18.2.7", diff --git a/src/components/dex/CandleChart/index.tsx b/src/components/dex/CandleChart/index.tsx index 0795d8c..c4ba464 100644 --- a/src/components/dex/CandleChart/index.tsx +++ b/src/components/dex/CandleChart/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef, useMemo } from 'react'; +import { useEffect, useState, useRef, useMemo, useLayoutEffect } from 'react'; import useAdvancedTheme from '@/hook/useTheme'; import ReactECharts from 'echarts-for-react'; import type CandleChartProps from '@/interfaces/props/pages/dex/trading/CandleChartProps/CandleChartProps'; @@ -18,7 +18,6 @@ import { function CandleChart(props: CandleChartProps) { const { theme } = useAdvancedTheme(); const chartRef = useRef(null); - const [candles, setCandles] = useState([]); const [isLoaded, setIsLoaded] = useState(false); @@ -148,6 +147,12 @@ function CandleChart(props: CandleChartProps) { }; }, [candles, props.period, theme]); + useLayoutEffect(() => { + requestAnimationFrame(() => { + window.dispatchEvent(new Event('resize')); + }); + }, []); + return (
{/* Header */} @@ -179,14 +184,16 @@ function CandleChart(props: CandleChartProps) {
- +
+ +
{!candles.length && isLoaded && (

[ Low volume ]

diff --git a/src/components/dex/CandleChart/styles.module.scss b/src/components/dex/CandleChart/styles.module.scss index 04fbf4f..b2f3e0a 100644 --- a/src/components/dex/CandleChart/styles.module.scss +++ b/src/components/dex/CandleChart/styles.module.scss @@ -40,10 +40,19 @@ } } - > canvas { + &__body { width: 100%; height: 100%; - cursor: crosshair; + + div, + canvas { + width: 100% !important; + height: 100% !important; + } + + canvas { + cursor: crosshair; + } } &__lowVolume { @@ -63,4 +72,4 @@ font-size: 36px; } } -} +} \ No newline at end of file diff --git a/src/constants/index.ts b/src/constants/index.ts index 1e4cc39..31a769c 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,6 +1,8 @@ import PeriodState from '@/interfaces/states/pages/dex/trading/InputPanelItem/PeriodState'; import SelectValue from '@/interfaces/states/pages/dex/trading/InputPanelItem/SelectValue'; +export const API_URL = process.env.NEXT_PUBLIC_API_URL; + export const periods: PeriodState[] = [ // { name: '1s', code: '1sec' }, { name: '1m', code: '1min' }, diff --git a/src/hook/useTradingData.ts b/src/hook/useTradingData.ts index 0c0ec6c..85d213a 100644 --- a/src/hook/useTradingData.ts +++ b/src/hook/useTradingData.ts @@ -7,7 +7,7 @@ import { getTrades, } from '@/utils/methods'; import useUpdateUser from '@/hook/useUpdateUser'; -import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react'; +import { Dispatch, SetStateAction, useContext, useEffect, useRef, useState } from 'react'; import CandleRow from '@/interfaces/common/CandleRow'; import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; @@ -45,9 +45,12 @@ export function useTradingData({ 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 isFirstLoad = useRef(true); + const prevPeriod = useRef(null); + + const [candlesLoaded, setCandlesLoaded] = useState(true); + const [ordersLoading, setOrdersLoading] = useState(false); + const [tradesLoading, setTradesLoading] = useState(false); const pairId = typeof router.query.id === 'string' ? router.query.id : ''; const loggedIn = !!state.wallet?.connected; @@ -108,17 +111,37 @@ export function useTradingData({ } useEffect(() => { + if (isFirstLoad.current) { + isFirstLoad.current = false; + + setOrdersLoading(false); + return; + } + fetchPairStats(); getPairData(); updateOrders(); }, []); useEffect(() => { + if (!prevPeriod.current) { + prevPeriod.current = periodsState.code; + return; + } + + if (prevPeriod.current === periodsState.code) return; + + prevPeriod.current = periodsState.code; fetchCandles(); - }, [periodsState]); + }, [periodsState.code]); useEffect(() => { (async () => { + if (isFirstLoad.current) { + setTradesLoading(false); + return; + } + await fetchTrades(); })(); }, [pairId]); diff --git a/src/interfaces/props/pages/dex/trading/TradingProps.ts b/src/interfaces/props/pages/dex/trading/TradingProps.ts new file mode 100644 index 0000000..499e72b --- /dev/null +++ b/src/interfaces/props/pages/dex/trading/TradingProps.ts @@ -0,0 +1,13 @@ +import CandleRow from '@/interfaces/common/CandleRow'; +import PairData from '@/interfaces/common/PairData'; +import { PageOrderData } from '@/interfaces/responses/orders/GetOrdersPageRes'; +import { PairStats } from '@/interfaces/responses/orders/GetPairStatsRes'; +import { Trade } from '@/interfaces/responses/trades/GetTradeRes'; + +export interface TradingProps { + initialPair: PairData | null; + initialStats: PairStats | null; + initialOrders: PageOrderData[]; + initialTrades: Trade[]; + initialCandles: CandleRow[]; +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index dc7bb30..cb215c2 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -7,8 +7,29 @@ import NextApp, { AppContext, AppProps } from 'next/app'; import { ThemeProvider } from 'next-themes'; import GetConfigRes, { GetConfigResData } from '@/interfaces/responses/config/GetConfigRes'; import inter from '@/utils/font'; +import { API_URL } from '@/constants'; +import NProgress from 'nprogress'; +import { Router } from 'next/router'; import PageHandler from './PageHandler'; import NotificationPopups from './NotificationPopups'; +import 'nprogress/nprogress.css'; + +NProgress.configure({ + showSpinner: false, + trickleSpeed: 200, +}); + +Router.events.on('routeChangeStart', () => { + NProgress.start(); +}); + +Router.events.on('routeChangeComplete', () => { + NProgress.done(); +}); + +Router.events.on('routeChangeError', () => { + NProgress.done(); +}); function App(data: AppProps & { config?: GetConfigResData }) { const { Component, pageProps } = data; @@ -63,7 +84,7 @@ App.getInitialProps = async (context: AppContext) => { try { const pageProps = await NextApp.getInitialProps(context); - const configRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/config`, { + const configRes = await fetch(`${API_URL}/api/config`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/src/pages/dex/index.tsx b/src/pages/dex/index.tsx index 28dcced..b64e2cd 100644 --- a/src/pages/dex/index.tsx +++ b/src/pages/dex/index.tsx @@ -14,16 +14,19 @@ import { useInView } from 'react-intersection-observer'; import Preloader from '@/components/UI/Preloader/Preloader'; import { PairSortOption } from '@/interfaces/enum/pair'; import PairsTable from '@/components/default/PairsTable/PairsTable'; +import axios from 'axios'; +import { API_URL } from '@/constants'; import DexHeader from './DexHeader/DexHeader'; import PairsList from './pairs/PairsList/PairsList'; -function Dex() { +function Dex({ initialPairs }: { initialPairs: PairData[] }) { const fetchIdRef = useRef(nanoid()); const bottomInView = useRef(false); + const isFirstLoad = useRef(true); const [pairInputState, setPairInputState] = useState(''); - const [pairs, setPairs] = useState([]); - const [allLoaded, setLoadedState] = useState(false); + const [pairs, setPairs] = useState(initialPairs); + const [allLoaded, setLoadedState] = useState(initialPairs.length > 0); const [pagesAmount, setPagesAmount] = useState(); const [pageLoading, setPageLoading] = useState(false); const [currentPage, setCurrentPage] = useState(1); @@ -36,7 +39,7 @@ function Dex() { }); useEffect(() => { - if (typeof window !== undefined) { + if (typeof window !== 'undefined') { const currentState = localStorage.getItem('all_pairs') ?? ''; if (currentState === '' || currentState === 'true') { @@ -66,14 +69,14 @@ function Dex() { async function fetchPairs() { const initial = currentPage === 1; - if (initial) { - setLoadedState(false); - setPageLoading(false); - } else { - setPageLoading(true); - setLoadedState(true); + if (initial && initialPairs.length > 0 && isFirstLoad.current) { + isFirstLoad.current = false; + return; } + if (!initial) { + setPageLoading(true); + } const newFetchId = nanoid(); fetchIdRef.current = newFetchId; const result = await getPairsPage(currentPage, pairInputState, whitelistedOnly, sortOption); @@ -162,4 +165,14 @@ function Dex() { ); } +export async function getServerSideProps() { + const result = await getPairsPage(1, '', true, PairSortOption.VOLUME_HIGH_TO_LOW); + + return { + props: { + initialPairs: result.success ? result.data : [], + }, + }; +} + export default Dex; diff --git a/src/pages/dex/trading/[id].tsx b/src/pages/dex/trading/[id].tsx index 54f3895..61c196b 100644 --- a/src/pages/dex/trading/[id].tsx +++ b/src/pages/dex/trading/[id].tsx @@ -3,7 +3,14 @@ import Footer from '@/components/default/Footer/Footer'; import Header from '@/components/default/Header/Header'; import HorizontalSelect from '@/components/UI/HorizontalSelect/HorizontalSelect'; import { useCallback, useState } from 'react'; -import { cancelOrder } from '@/utils/methods'; +import { + cancelOrder, + getCandles, + getOrdersPage, + getPair, + getPairStats, + getTrades, +} from '@/utils/methods'; import ContentPreloader from '@/components/UI/ContentPreloader/ContentPreloader'; import Alert from '@/components/UI/Alert/Alert'; import PeriodState from '@/interfaces/states/pages/dex/trading/InputPanelItem/PeriodState'; @@ -30,24 +37,33 @@ import useMatrixAddresses from '@/hook/useMatrixAddresses'; import takeOrderClick from '@/utils/takeOrderClick'; import useUpdateUser from '@/hook/useUpdateUser'; import { GuideProvider } from '@/store/guide-provider'; +import { GetServerSidePropsContext } from 'next'; +import { TradingProps } from '@/interfaces/props/pages/dex/trading/TradingProps'; -function Trading() { +function Trading({ + initialOrders, + initialPair, + initialStats, + initialTrades, + initialCandles, +}: TradingProps) { const { alertState, alertSubtitle, setAlertState } = useAlert(); const { elementRef: orderListRef, scrollToElement: scrollToOrdersList } = useScroll(); const { elementRef: orderFormRef, scrollToElement: scrollToOrderForm } = useScroll(); + const [pairData, setPairData] = useState(initialPair); + const [pairStats, setPairStats] = useState(initialStats); + const [ordersHistory, setOrdersHistory] = useState(initialOrders); + const [candles, setCandles] = useState(initialCandles); + const [trades, setTrades] = useState(initialTrades); + const fetchUser = useUpdateUser(); - const [ordersHistory, setOrdersHistory] = useState([]); const [userOrders, setUserOrders] = useState([]); const [periodsState, setPeriodsState] = useState(periods[4]); - const [pairData, setPairData] = useState(null); - const [candles, setCandles] = useState([]); - const [trades, setTrades] = useState([]); const [myOrdersLoading, setMyOrdersLoading] = useState(true); const [ordersBuySell, setOrdersBuySell] = useState(buySellValues[0]); - const [pairStats, setPairStats] = useState(null); const [applyTips, setApplyTips] = useState([]); const matrixAddresses = useMatrixAddresses(ordersHistory); const [orderFormType, setOrderFormType] = useState(buySellValues[1]); @@ -243,4 +259,26 @@ function Trading() { ); } +export async function getServerSideProps(ctx: GetServerSidePropsContext) { + const pairId = ctx.params?.id as string; + + const [pairRes, statsRes, ordersRes, tradesRes, candlesRes] = await Promise.all([ + getPair(pairId), + getPairStats(pairId), + getOrdersPage(pairId), + getTrades(pairId), + getCandles(pairId, '1h'), + ]); + + return { + props: { + initialPair: pairRes.success ? pairRes.data : null, + initialStats: statsRes.success ? statsRes.data : null, + initialOrders: ordersRes.success ? ordersRes.data : [], + initialTrades: tradesRes.success ? tradesRes.data : [], + initialCandles: candlesRes.success ? candlesRes.data : [], + }, + }; +} + export default Trading; diff --git a/src/pages/dex/trading/find-pair/index.tsx b/src/pages/dex/trading/find-pair/index.tsx index 24f94cf..d2401b5 100644 --- a/src/pages/dex/trading/find-pair/index.tsx +++ b/src/pages/dex/trading/find-pair/index.tsx @@ -2,8 +2,7 @@ import { GetServerSideProps } from 'next'; import { findPairID } from '@/utils/methods'; import styles from '@/styles/404.module.scss'; - -const API_URL = process.env.NEXT_PUBLIC_API_URL; +import { API_URL } from '@/constants'; export const getServerSideProps: GetServerSideProps = async (context) => { const { first, second } = context.query; diff --git a/src/styles/Trading.module.scss b/src/styles/Trading.module.scss index 4ba0c53..d6efdb5 100644 --- a/src/styles/Trading.module.scss +++ b/src/styles/Trading.module.scss @@ -68,6 +68,7 @@ @media screen and (max-width: 1024px) { .trading__top { + &_trades, &_form { max-width: 100%; @@ -89,4 +90,4 @@ .trading__top { height: 370px; } -} +} \ No newline at end of file diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 97e3749..fd2873e 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -33,7 +33,7 @@ main { padding: 80px 100px; &.with-separators { - > * { + >* { padding: 40px 0; border-bottom: 1px solid var(--delimiter-color); @@ -115,7 +115,7 @@ h6 { } h1, -h1 > * { +h1>* { color: var(--font-main-color); font-size: 48px; } @@ -174,8 +174,9 @@ svg { } @media screen and (max-width: 700px) { + h1, - h1 > * { + h1>* { font-size: 32px; } } @@ -276,3 +277,17 @@ svg { background-color: #1f8feb !important; } } + +#nprogress { + pointer-events: none; +} + +#nprogress .bar { + background: #1f8feb; + position: fixed; + z-index: 9999; + top: 0; + left: 0; + width: 100%; + height: 3px; +} \ No newline at end of file diff --git a/src/utils/methods.ts b/src/utils/methods.ts index 5e86cfb..f59e278 100644 --- a/src/utils/methods.ts +++ b/src/utils/methods.ts @@ -24,6 +24,10 @@ import GetChatChunkRes from '@/interfaces/responses/chats/GetChatChunkRes'; import axios from 'axios'; import GetPairsPagesAmountRes from '@/interfaces/responses/dex/GetPairsPagesAmountRes'; import { PairSortOption } from '@/interfaces/enum/pair'; +import { API_URL } from '@/constants'; + +const isServer = typeof window === 'undefined'; +const baseUrl = isServer ? API_URL : ''; export async function getUser(): Promise { return axios @@ -185,7 +189,7 @@ export async function getPairsPage( sortOption: PairSortOption, ): Promise { return axios - .post('/api/dex/get-pairs-page', { + .post(`${baseUrl}/api/dex/get-pairs-page`, { page, searchText, whitelistedOnly, @@ -208,7 +212,7 @@ export async function getPairsPagesAmount( export async function getPair(id: string): Promise { return axios - .post('/api/dex/get-pair', { + .post(`${baseUrl}/api/dex/get-pair`, { id, }) .then((res) => res.data); @@ -227,7 +231,7 @@ export async function createOrder( export async function getOrdersPage(pairId: string): Promise { return axios - .post('/api/orders/get-page', { + .post(`${baseUrl}/api/orders/get-page`, { pairId, }) .then((res) => res.data); @@ -273,7 +277,7 @@ export async function getCandles( period: Period, ): Promise { return axios - .post('/api/orders/get-candles', { + .post(`${baseUrl}/api/orders/get-candles`, { pairId, period, }) @@ -290,7 +294,7 @@ export async function getChartOrders(pairId: string): Promise { return axios - .post('/api/orders/get-pair-stats', { + .post(`${baseUrl}/api/orders/get-pair-stats`, { pairId, }) .then((res) => res.data); @@ -331,7 +335,7 @@ export async function getChatChunk( export async function getTrades(pairId: string) { return axios - .post(`/api/orders/get-trades`, { + .post(`${baseUrl}/api/orders/get-trades`, { pairId, }) .then((res) => res.data); diff --git a/src/utils/socket.ts b/src/utils/socket.ts index be3e0a6..93bce0b 100644 --- a/src/utils/socket.ts +++ b/src/utils/socket.ts @@ -1,6 +1,7 @@ +import { API_URL } from '@/constants'; import { io } from 'socket.io-client'; -const SOCKET_URL = process.env.NEXT_PUBLIC_API_URL as string; +const SOCKET_URL = API_URL as string; const socket = io(SOCKET_URL, { transports: ['websocket'],