Merge pull request #36 from jejolare-dev/staging

sync staging
This commit is contained in:
Dmitrii Kolpakov 2026-01-10 17:27:08 +07:00 committed by GitHub
commit 769c34648f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 326 additions and 80 deletions

99
.github/workflows/codeql.yml vendored Normal file
View file

@ -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}}"

15
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -72,7 +72,9 @@ export default function OrderNotification({
</div>
<div className={styles['dex__order-notification_delimiter']} />
<div className={styles['dex__order-notification_details']}>
<Link href={pairLink}>Details</Link>
<Link href={pairLink} onClick={onCloseClick}>
Details
</Link>
</div>
</div>
);

View file

@ -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<ReactECharts>(null);
const [candles, setCandles] = useState<ResultCandle[]>([]);
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 (
<div className={styles.chart}>
{/* Header */}
@ -179,14 +184,16 @@ function CandleChart(props: CandleChartProps) {
</div>
</div>
<ReactECharts
ref={chartRef}
option={option}
style={{ height: '100%', width: '100%' }}
opts={{ devicePixelRatio: 2 }}
lazyUpdate
notMerge
/>
<div className={styles.chart__body}>
<ReactECharts
ref={chartRef}
option={option}
style={{ height: '100%', width: '100%' }}
opts={{ devicePixelRatio: 2 }}
lazyUpdate
notMerge
/>
</div>
{!candles.length && isLoaded && (
<h1 className={styles.chart__lowVolume}>[ Low volume ]</h1>

View file

@ -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;
}
}
}
}

View file

@ -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);

View file

@ -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' },

View file

@ -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<string | null>(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]);

View file

@ -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[];
}

View file

@ -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',

View file

@ -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<string>(nanoid());
const bottomInView = useRef<boolean>(false);
const isFirstLoad = useRef(true);
const [pairInputState, setPairInputState] = useState('');
const [pairs, setPairs] = useState<PairData[]>([]);
const [allLoaded, setLoadedState] = useState(false);
const [pairs, setPairs] = useState<PairData[]>(initialPairs);
const [allLoaded, setLoadedState] = useState(initialPairs.length > 0);
const [pagesAmount, setPagesAmount] = useState<number>();
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;

View file

@ -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<HTMLDivElement>();
const { elementRef: orderFormRef, scrollToElement: scrollToOrderForm } =
useScroll<HTMLDivElement>();
const [pairData, setPairData] = useState<PairData | null>(initialPair);
const [pairStats, setPairStats] = useState<PairStats | null>(initialStats);
const [ordersHistory, setOrdersHistory] = useState<PageOrderData[]>(initialOrders);
const [candles, setCandles] = useState<CandleRow[]>(initialCandles);
const [trades, setTrades] = useState<Trade[]>(initialTrades);
const fetchUser = useUpdateUser();
const [ordersHistory, setOrdersHistory] = useState<PageOrderData[]>([]);
const [userOrders, setUserOrders] = useState<OrderRow[]>([]);
const [periodsState, setPeriodsState] = useState<PeriodState>(periods[4]);
const [pairData, setPairData] = useState<PairData | null>(null);
const [candles, setCandles] = useState<CandleRow[]>([]);
const [trades, setTrades] = useState<Trade[]>([]);
const [myOrdersLoading, setMyOrdersLoading] = useState(true);
const [ordersBuySell, setOrdersBuySell] = useState(buySellValues[0]);
const [pairStats, setPairStats] = useState<PairStats | null>(null);
const [applyTips, setApplyTips] = useState<ApplyTip[]>([]);
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;

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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<ErrorRes | GetUserRes> {
return axios
@ -185,7 +189,7 @@ export async function getPairsPage(
sortOption: PairSortOption,
): Promise<ErrorRes | GetPairsPageRes> {
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<ErrorRes | GetPairRes> {
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<ErrorRes | GetOrdersPageRes> {
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<ErrorRes | GetCandlesRes> {
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<ErrorRes | GetChar
export async function getPairStats(pairId: string): Promise<ErrorRes | GetPairStatsRes> {
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);

View file

@ -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'],