finish: redesign
This commit is contained in:
parent
6b552a4b11
commit
bfa8ddfff2
27 changed files with 492 additions and 273 deletions
|
|
@ -4,7 +4,7 @@
|
|||
outline: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 5px;
|
||||
background: #273666;
|
||||
background: var(--action-btn-bg);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
|
|
@ -16,8 +16,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
background: #273666;
|
||||
background: var(--action-btn-bg);
|
||||
}
|
||||
|
||||
&.primary {
|
||||
|
|
@ -31,4 +30,4 @@
|
|||
&.danger {
|
||||
color: #ff6767;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,23 @@
|
|||
overflow: auto;
|
||||
padding-bottom: 3px;
|
||||
|
||||
&.sm {
|
||||
gap: 5px !important;
|
||||
|
||||
>div {
|
||||
a {
|
||||
font-size: 14px !important;
|
||||
padding: 8px !important;
|
||||
border-radius: 8px !important;
|
||||
line-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.tab {
|
||||
gap: 3px;
|
||||
|
||||
> div {
|
||||
>div {
|
||||
a {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
|
@ -26,7 +39,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
>div {
|
||||
position: relative;
|
||||
|
||||
.profile__filters__notification {
|
||||
|
|
@ -58,4 +71,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import Link from 'next/link';
|
|||
import { nanoid } from 'nanoid';
|
||||
import HorizontalSelectProps from '@/interfaces/props/components/UI/HorizontalSelect/HorizontalSelectProps';
|
||||
import HorizontalSelectValue from '@/interfaces/common/HorizontalSelectValue';
|
||||
import { classes } from '@/utils/utils';
|
||||
import NotificationIndicator from '../NotificationIndicator/NotificationIndicator';
|
||||
import styles from './HorizontalSelect.module.scss';
|
||||
|
||||
|
|
@ -13,7 +14,12 @@ function HorizontalSelect<T extends HorizontalSelectValue>(props: HorizontalSele
|
|||
return (
|
||||
<div
|
||||
style={props.withNotifications ? { paddingTop: '20px' } : {}}
|
||||
className={`${styles.horizontal_select} ${className || ''} ${props.isTab ? styles.tab : ''}`}
|
||||
className={classes(
|
||||
styles.horizontal_select,
|
||||
className,
|
||||
props.isTab && styles.tab,
|
||||
props.isSm && styles.sm,
|
||||
)}
|
||||
>
|
||||
{props.body.map((e) => (
|
||||
<div key={nanoid(16)}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
.back_btn {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&.sm {
|
||||
|
||||
padding: 12px 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
import { ReactComponent as ArrowWhiteIcon } from '@/assets/images/UI/arrow_white.svg';
|
||||
import Button from '@/components/UI/Button/Button';
|
||||
import { useRouter } from 'next/router';
|
||||
import { classes } from '@/utils/utils';
|
||||
import styles from './BackButton.module.scss';
|
||||
import { BackButtonProps } from './types';
|
||||
|
||||
function BackButton() {
|
||||
function BackButton({ className, isSm }: BackButtonProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Button className={styles.back_btn} transparent={true} onClick={router.back}>
|
||||
{/* <img src={ArrowWhiteIcon} alt="arrow"/> */}
|
||||
<Button
|
||||
className={classes(styles.back_btn, className, isSm && styles.sm)}
|
||||
transparent={true}
|
||||
onClick={router.back}
|
||||
>
|
||||
<ArrowWhiteIcon />
|
||||
Back
|
||||
</Button>
|
||||
|
|
|
|||
4
src/components/default/BackButton/types.ts
Normal file
4
src/components/default/BackButton/types.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export interface BackButtonProps {
|
||||
className?: string;
|
||||
isSm?: boolean;
|
||||
}
|
||||
|
|
@ -12,6 +12,39 @@
|
|||
|
||||
&.lg {
|
||||
padding-inline: 60px;
|
||||
height: 65px;
|
||||
|
||||
.header__logo {
|
||||
width: 180px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.header__currency__check {
|
||||
height: 48px !important;
|
||||
gap: 25px !important;
|
||||
}
|
||||
|
||||
.header__account__wrapper {
|
||||
.header__account__info {
|
||||
p {
|
||||
&:first-child {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.header__account__btn {
|
||||
min-width: 48px !important;
|
||||
height: 48px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.header__connect_btn {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
padding-inline: 40px;
|
||||
|
|
@ -27,7 +60,7 @@
|
|||
width: 202px;
|
||||
height: 40px;
|
||||
|
||||
> a {
|
||||
>a {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +110,7 @@
|
|||
.header__login {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: none;
|
||||
}
|
||||
|
|
@ -106,7 +140,7 @@
|
|||
.header__button__wrapper {
|
||||
position: relative;
|
||||
|
||||
> .offers__tooltip {
|
||||
>.offers__tooltip {
|
||||
padding: 13px 20px;
|
||||
position: absolute;
|
||||
background: #11316b;
|
||||
|
|
@ -206,7 +240,7 @@
|
|||
display: flex;
|
||||
gap: 5px;
|
||||
|
||||
> span {
|
||||
>span {
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -231,6 +265,7 @@
|
|||
}
|
||||
|
||||
&.header__menu__mobile {
|
||||
|
||||
.header__account,
|
||||
.header__login {
|
||||
padding: 20px;
|
||||
|
|
@ -255,11 +290,11 @@
|
|||
cursor: pointer;
|
||||
transition: none;
|
||||
|
||||
> svg {
|
||||
>svg {
|
||||
scale: 0.8;
|
||||
}
|
||||
|
||||
> div {
|
||||
>div {
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
margin-bottom: 6px;
|
||||
|
|
@ -286,7 +321,7 @@
|
|||
max-height: 200px;
|
||||
overflow: hidden;
|
||||
|
||||
> div {
|
||||
>div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
@ -391,4 +426,4 @@
|
|||
background-color: #0000004c;
|
||||
z-index: 1;
|
||||
transform: all 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -355,9 +355,11 @@ function Header({ isLg }: { isLg?: boolean }) {
|
|||
</div>
|
||||
|
||||
<div className={styles.header__desktop__navigation}>
|
||||
<NavBar />
|
||||
<NavBar isLg={isLg} />
|
||||
</div>
|
||||
|
||||
{Menu()}
|
||||
|
||||
<BurgerButton className={styles.header__burger} />
|
||||
|
||||
<div className={styles.header__account__mobile} style={mobileHeaderStyle}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,25 @@
|
|||
.nav {
|
||||
&.lg {
|
||||
a {
|
||||
gap: 8px;
|
||||
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
svg {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 3px;
|
||||
min-width: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -73,4 +94,4 @@
|
|||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import NavBarProps from '@/interfaces/props/components/default/Header/NavBar/Nav
|
|||
import NotificationIndicator from '@/components/UI/NotificationIndicator/NotificationIndicator';
|
||||
import { useContext } from 'react';
|
||||
import { Store } from '@/store/store-reducer';
|
||||
import { classes } from '@/utils/utils';
|
||||
import styles from './NavBar.module.scss';
|
||||
|
||||
function NavBar(props: NavBarProps) {
|
||||
|
|
@ -40,14 +41,18 @@ function NavBar(props: NavBarProps) {
|
|||
<Link href={href} className={linkClass}>
|
||||
<Img />
|
||||
<h6>{title}</h6>
|
||||
<NotificationIndicator count={notifications} />
|
||||
<NotificationIndicator className={styles.badge} count={notifications} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`${styles.nav} ${!props.mobile ? styles.nav__desktop : styles.nav__mobile}`}
|
||||
className={classes(
|
||||
styles.nav,
|
||||
!props.mobile ? styles.nav__desktop : styles.nav__mobile,
|
||||
props.isLg && styles.lg,
|
||||
)}
|
||||
>
|
||||
<NavItem
|
||||
title={'Exchange'}
|
||||
|
|
|
|||
|
|
@ -1,235 +1,194 @@
|
|||
import { useEffect, useState, useRef, useMemo } from 'react';
|
||||
import useAdvancedTheme from '@/hook/useTheme';
|
||||
import CandleChartProps from '@/interfaces/props/pages/dex/trading/CandleChartProps/CandleChartProps';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import Decimal from 'decimal.js';
|
||||
import * as echarts from 'echarts';
|
||||
import CandleRow from '@/interfaces/common/CandleRow';
|
||||
import testCandles from './testCandles.json';
|
||||
import type CandleChartProps from '@/interfaces/props/pages/dex/trading/CandleChartProps/CandleChartProps';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const TESTING_MODE = false;
|
||||
import { ResultCandle } from './types';
|
||||
import {
|
||||
buildCandles,
|
||||
d,
|
||||
diffFmt,
|
||||
fmt,
|
||||
pickWindowIndices,
|
||||
tsLabel,
|
||||
zoomStartByPeriod,
|
||||
chartColors,
|
||||
} from './utils';
|
||||
|
||||
function CandleChart(props: CandleChartProps) {
|
||||
type ResultCandle = [number, number, number, number, number];
|
||||
|
||||
const { theme } = useAdvancedTheme();
|
||||
const chartRef = useRef(null);
|
||||
const upColor = '#16D1D6cc';
|
||||
const upBorderColor = '#16D1D6cc';
|
||||
const downColor = '#ff6767cc';
|
||||
const downBorderColor = '#ff6767cc';
|
||||
const chartRef = useRef<ReactECharts>(null);
|
||||
|
||||
const [candles, setCandles] = useState<ResultCandle[]>([]);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
function timestampToString(timestamp: string) {
|
||||
const targetPattern =
|
||||
new Date(parseInt(timestamp, 10)).toDateString() === new Date().toDateString()
|
||||
? 'hh:mm:ss'
|
||||
: 'hh:mm:ss\ndd-MM-yyyy';
|
||||
|
||||
return echarts.format.formatTime(targetPattern, parseInt(timestamp, 10));
|
||||
}
|
||||
|
||||
function prepareCandles() {
|
||||
const candles = (TESTING_MODE ? (testCandles as CandleRow[]) : props.candles)
|
||||
.map((candle) => {
|
||||
const result = [
|
||||
parseInt(candle.timestamp, 10),
|
||||
candle.shadow_top || 0,
|
||||
candle.shadow_bottom || 0,
|
||||
candle.body_first || 0,
|
||||
candle.body_second || 0,
|
||||
];
|
||||
|
||||
return result as ResultCandle;
|
||||
})
|
||||
.filter((e) => e[0])
|
||||
.map((e) => {
|
||||
const decimals = e.map((el, i) => ({
|
||||
value: i !== 0 ? new Decimal(el) : undefined,
|
||||
index: i,
|
||||
}));
|
||||
|
||||
for (const decimal of decimals) {
|
||||
if (decimal.value !== undefined) {
|
||||
if (decimal.value.lessThan(0.00001)) {
|
||||
e[decimal.index] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
});
|
||||
|
||||
return candles;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const newCandles = prepareCandles();
|
||||
|
||||
setCandles(newCandles);
|
||||
setCandles(buildCandles(props.candles));
|
||||
setIsLoaded(true);
|
||||
}, [props.candles]);
|
||||
|
||||
// shown candle
|
||||
const shownIdx = candles.length ? candles.length - 1 : null;
|
||||
const prevIdx = shownIdx && shownIdx > 0 ? shownIdx - 1 : null;
|
||||
|
||||
const shown = shownIdx !== null ? candles[shownIdx] : undefined;
|
||||
const prev = prevIdx !== null ? candles[prevIdx] : undefined;
|
||||
|
||||
const O = shown?.[3];
|
||||
const H = shown?.[1];
|
||||
const L = shown?.[2];
|
||||
const C = shown?.[4];
|
||||
const P = prev?.[4];
|
||||
const delta = diffFmt(C, P);
|
||||
|
||||
const option = useMemo(() => {
|
||||
function splitData(rawData: ResultCandle[]) {
|
||||
const categoryData = [];
|
||||
const values = [];
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
categoryData.push(rawData[i][0]);
|
||||
values.push(rawData[i].slice(1));
|
||||
}
|
||||
return {
|
||||
categoryData,
|
||||
values,
|
||||
};
|
||||
}
|
||||
|
||||
const data0 = splitData(candles);
|
||||
|
||||
const now = new Date().getTime();
|
||||
const zoomStartTime = (() => {
|
||||
const date = +new Date();
|
||||
const hr1 = 60 * 60 * 1000;
|
||||
|
||||
const hoursDecrement = 24 * hr1;
|
||||
const daysDecrement = 7 * 24 * hr1;
|
||||
const weeksDecrement = 4 * 7 * 24 * hr1;
|
||||
const monthsDecrement = 52 * 7 * 24 * hr1;
|
||||
|
||||
switch (props.period) {
|
||||
case '1h':
|
||||
return date - hoursDecrement;
|
||||
case '1d':
|
||||
return date - daysDecrement;
|
||||
case '1w':
|
||||
return date - weeksDecrement;
|
||||
case '1m':
|
||||
return date - monthsDecrement;
|
||||
default:
|
||||
return date - hr1;
|
||||
}
|
||||
})();
|
||||
|
||||
const closestDateToStart = data0.categoryData.reduce((acc, curr) => {
|
||||
const currDiff = Math.abs(curr - zoomStartTime);
|
||||
const accDiff = Math.abs(acc - zoomStartTime);
|
||||
|
||||
return currDiff < accDiff ? curr : acc;
|
||||
}, now);
|
||||
|
||||
const lastDate = data0.categoryData[data0.categoryData.length - 1];
|
||||
|
||||
const closestDateIndex = data0.categoryData.indexOf(closestDateToStart);
|
||||
const lastDateIndex = data0.categoryData.indexOf(lastDate);
|
||||
const timestamps = candles.map((c) => c[0]);
|
||||
const { startIdx, endIdx } = pickWindowIndices(timestamps, zoomStartByPeriod(props.period));
|
||||
|
||||
return {
|
||||
grid: {
|
||||
top: '5%',
|
||||
left: '-1%',
|
||||
right: '6%',
|
||||
bottom: '10%',
|
||||
},
|
||||
grid: { top: '5%', left: '0%', right: '10%', bottom: '10%' },
|
||||
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
// data: data0.categoryData,
|
||||
boundaryGap: true,
|
||||
axisLine: { onZero: false },
|
||||
min: 'dataMin',
|
||||
max: 'dataMax',
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: theme === 'light' ? '#e3e3e8' : '#1f1f4a',
|
||||
color: theme === 'light' ? chartColors.light : chartColors.dark,
|
||||
},
|
||||
},
|
||||
min: 'dataMin',
|
||||
max: 'dataMax',
|
||||
axisLine: { onZero: false },
|
||||
axisLabel: { formatter: (v: string) => tsLabel(parseInt(v, 10)) },
|
||||
axisPointer: {
|
||||
show: true,
|
||||
type: 'line',
|
||||
label: {
|
||||
formatter: (params: { value: string }) => timestampToString(params.value),
|
||||
backgroundColor: '#4A90E2',
|
||||
color: '#ffffff',
|
||||
formatter: (p: { value: string }) => tsLabel(parseInt(p.value, 10)),
|
||||
backgroundColor: chartColors.default,
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
yAxis: {
|
||||
scale: true,
|
||||
position: 'right',
|
||||
splitArea: { show: false },
|
||||
min: (v: { min: number; max: number }) => {
|
||||
const min = d(v.min);
|
||||
const range = d(v.max).minus(min);
|
||||
const pad = range.lte(0) ? d(v.max).mul(0.05) : range.mul(0.1);
|
||||
return min.minus(pad).toNumber();
|
||||
},
|
||||
max: (v: { min: number; max: number }) => {
|
||||
const min = d(v.min);
|
||||
const max = d(v.max);
|
||||
const range = max.minus(min);
|
||||
const pad = range.lte(0) ? max.mul(0.05) : range.mul(0.1);
|
||||
return max.plus(pad).toNumber();
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: theme === 'light' ? chartColors.light : chartColors.dark,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: timestampToString,
|
||||
formatter: (val: number | string) => d(val).toDecimalPlaces(6).toString(),
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
scale: true,
|
||||
splitArea: { show: false },
|
||||
min: 0,
|
||||
max: (value: { max: string }) => new Decimal(value.max).mul(1.1).toNumber(),
|
||||
axisPointer: {
|
||||
show: true,
|
||||
type: 'line',
|
||||
label: {
|
||||
show: true,
|
||||
backgroundColor: '#4A90E2',
|
||||
color: '#ffffff',
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: theme === 'light' ? '#e3e3e8' : '#1f1f4a',
|
||||
color: '#fff',
|
||||
backgroundColor: (p: {
|
||||
seriesData?: Array<{ value?: (number | string)[] }>;
|
||||
}) => {
|
||||
const ts = p.seriesData?.[0]?.value?.[0];
|
||||
const idx = candles.findIndex((c) => c[0] === ts);
|
||||
if (idx === -1) return chartColors.default;
|
||||
const [, , , o, c] = candles[idx];
|
||||
let color = chartColors.default;
|
||||
|
||||
if (c > o) {
|
||||
color = chartColors.green;
|
||||
} else if (c < o) {
|
||||
color = chartColors.red;
|
||||
}
|
||||
|
||||
return color;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
startValue: closestDateIndex,
|
||||
endValue: lastDateIndex,
|
||||
},
|
||||
],
|
||||
|
||||
dataZoom: [{ type: 'inside', startValue: startIdx, endValue: endIdx }],
|
||||
|
||||
series: [
|
||||
{
|
||||
name: 'Candle Chart',
|
||||
type: 'candlestick',
|
||||
data: candles,
|
||||
itemStyle: {
|
||||
color: upColor,
|
||||
color0: downColor,
|
||||
borderColor: upBorderColor,
|
||||
borderColor0: downBorderColor,
|
||||
},
|
||||
barWidth: '75%',
|
||||
dimensions: ['date', 'highest', 'lowest', 'open', 'close'],
|
||||
encode: {
|
||||
x: 'date',
|
||||
y: ['open', 'close', 'highest', 'lowest'],
|
||||
itemStyle: {
|
||||
color: chartColors.green,
|
||||
color0: chartColors.red,
|
||||
borderColor: chartColors.green,
|
||||
borderColor0: chartColors.red,
|
||||
},
|
||||
dimensions: ['date', 'highest', 'lowest', 'open', 'close'],
|
||||
encode: { x: 'date', y: ['open', 'close', 'highest', 'lowest'] },
|
||||
large: true,
|
||||
largeThreshold: 2000000,
|
||||
largeThreshold: 2_000_000,
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [candles, theme]);
|
||||
|
||||
console.log('option', option);
|
||||
}, [candles, props.period, theme]);
|
||||
|
||||
return (
|
||||
<div className={styles.chart}>
|
||||
{/* Header */}
|
||||
<div className={styles.chart__top}>
|
||||
<div className={styles.chart__top_item}>
|
||||
<p>
|
||||
{props.currencyNames.firstCurrencyName}/
|
||||
{props.currencyNames.secondCurrencyName}
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.chart__top_item}>
|
||||
<p style={{ opacity: 0.7 }}>O</p>
|
||||
<span style={{ color: delta.color }}>{fmt(O)}</span>
|
||||
</div>
|
||||
<div className={styles.chart__top_item}>
|
||||
<span style={{ opacity: 0.7 }}>H</span>
|
||||
<span style={{ color: delta.color }}>{fmt(H)}</span>
|
||||
</div>
|
||||
<div className={styles.chart__top_item}>
|
||||
<span style={{ opacity: 0.7 }}>L</span>
|
||||
<span style={{ color: delta.color }}>{fmt(L)}</span>
|
||||
</div>
|
||||
<div className={styles.chart__top_item}>
|
||||
<span style={{ opacity: 0.7 }}>C</span>
|
||||
<span style={{ color: delta.color }}>{fmt(C)}</span>
|
||||
</div>
|
||||
<div className={styles.chart__top_item}>
|
||||
<p style={{ color: delta.color }}>{delta.txt}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ReactECharts
|
||||
option={option}
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
opts={{
|
||||
devicePixelRatio: 2,
|
||||
}}
|
||||
lazyUpdate={true}
|
||||
notMerge={true}
|
||||
ref={chartRef}
|
||||
option={option}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
opts={{ devicePixelRatio: 2 }}
|
||||
lazyUpdate
|
||||
notMerge
|
||||
/>
|
||||
|
||||
{!candles?.length && isLoaded && (
|
||||
{!candles.length && isLoaded && (
|
||||
<h1 className={styles.chart__lowVolume}>[ Low volume ]</h1>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,38 @@
|
|||
height: auto;
|
||||
height: 100%;
|
||||
|
||||
> canvas {
|
||||
&__top {
|
||||
padding-bottom: 10px;
|
||||
background-color: var(--main-bg-color);
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
z-index: 2;
|
||||
|
||||
&_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
p {
|
||||
color: var(--footer-selected-link);
|
||||
}
|
||||
|
||||
p,
|
||||
span {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: crosshair;
|
||||
|
|
@ -27,4 +58,4 @@
|
|||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/components/dex/CandleChart/types.ts
Normal file
1
src/components/dex/CandleChart/types.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type ResultCandle = [ts: number, high: number, low: number, open: number, close: number];
|
||||
116
src/components/dex/CandleChart/utils.ts
Normal file
116
src/components/dex/CandleChart/utils.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import CandleRow from '@/interfaces/common/CandleRow';
|
||||
import CandleChartProps from '@/interfaces/props/pages/dex/trading/CandleChartProps/CandleChartProps';
|
||||
import Decimal from 'decimal.js';
|
||||
import * as echarts from 'echarts';
|
||||
import { ResultCandle } from './types';
|
||||
import testCandles from './testCandles.json';
|
||||
|
||||
export const TESTING_MODE = false;
|
||||
|
||||
export const chartColors = {
|
||||
green: '#16D1D6',
|
||||
red: '#FF6767',
|
||||
default: '#3D3D6C',
|
||||
light: '#e3e3e8',
|
||||
dark: '#1f1f4a',
|
||||
textColor: '#ffffff',
|
||||
};
|
||||
|
||||
export const d = (v: number | string) => new Decimal(v);
|
||||
export const fmt = (n?: number | string) =>
|
||||
n === undefined || n === null || Number.isNaN(Number(n))
|
||||
? '-'
|
||||
: d(n).toDecimalPlaces(6).toString();
|
||||
|
||||
export function diffFmt(curr?: number | string, prev?: number | string) {
|
||||
if (curr === undefined || prev === undefined || Number(prev) === 0) {
|
||||
return { txt: '-', color: '#9CA3AF' };
|
||||
}
|
||||
|
||||
const diff = d(curr).minus(prev);
|
||||
const pct = diff.div(prev).mul(100);
|
||||
|
||||
let sign = '';
|
||||
if (diff.greaterThan(0)) {
|
||||
sign = '+';
|
||||
} else if (diff.isZero()) {
|
||||
sign = '';
|
||||
}
|
||||
|
||||
const txt = `${sign}${diff.toDecimalPlaces(8).toString()} (${sign}${pct.toDecimalPlaces(2).toString()}%)`;
|
||||
|
||||
let color = '#9CA3AF';
|
||||
if (diff.greaterThan(0)) {
|
||||
color = chartColors.green;
|
||||
} else if (diff.lessThan(0)) {
|
||||
color = chartColors.red;
|
||||
}
|
||||
|
||||
return { txt, color };
|
||||
}
|
||||
|
||||
const isTodayTs = (ms: number) => new Date(ms).toDateString() === new Date().toDateString();
|
||||
|
||||
export const tsLabel = (ms: number) =>
|
||||
echarts.format.formatTime(isTodayTs(ms) ? 'hh:mm:ss' : 'hh:mm:ss\ndd-MM-yyyy', ms);
|
||||
|
||||
export function buildCandles(src: CandleRow[]): ResultCandle[] {
|
||||
const arr = (TESTING_MODE ? (testCandles as CandleRow[]) : src).map(
|
||||
(c) =>
|
||||
[
|
||||
parseInt(c.timestamp, 10),
|
||||
c.shadow_top || 0,
|
||||
c.shadow_bottom || 0,
|
||||
c.body_first || 0,
|
||||
c.body_second || 0,
|
||||
] as ResultCandle,
|
||||
);
|
||||
|
||||
return arr
|
||||
.filter((e) => e[0])
|
||||
.map((e) => {
|
||||
for (let i = 1; i < 5; i++) if (d(e[i]).lessThan(0.00001)) e[i] = 0;
|
||||
return e;
|
||||
});
|
||||
}
|
||||
|
||||
export function zoomStartByPeriod(period: CandleChartProps['period']) {
|
||||
const now = Date.now();
|
||||
const hr = 3600_000;
|
||||
switch (period) {
|
||||
case '1sec':
|
||||
return now - 1_000;
|
||||
case '1min':
|
||||
return now - hr / 60;
|
||||
case '5min':
|
||||
return now - 5 * (hr / 60);
|
||||
case '15min':
|
||||
return now - 15 * (hr / 60);
|
||||
case '30min':
|
||||
return now - 30 * (hr / 60);
|
||||
case '1h':
|
||||
return now - hr * 24;
|
||||
case '4h':
|
||||
return now - hr * 4;
|
||||
case '1d':
|
||||
return now - hr * 24 * 7;
|
||||
case '1w':
|
||||
return now - hr * 24 * 7 * 4;
|
||||
case '1m':
|
||||
return now - hr * 24 * 7 * 52;
|
||||
default:
|
||||
return now - hr;
|
||||
}
|
||||
}
|
||||
|
||||
export function pickWindowIndices(timestamps: number[], startTs: number) {
|
||||
if (!timestamps.length) return { startIdx: 0, endIdx: 0 };
|
||||
const nearest = timestamps.reduce(
|
||||
(a, b) => (Math.abs(b - startTs) < Math.abs(a - startTs) ? b : a),
|
||||
timestamps[0],
|
||||
);
|
||||
return {
|
||||
startIdx: timestamps.indexOf(nearest),
|
||||
endIdx: timestamps.length - 1,
|
||||
};
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
|
||||
&__name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import styles from './styles.module.scss';
|
|||
function StatItem({ Img, title, value, className, coefficient }: StatItemProps) {
|
||||
return (
|
||||
<div className={classes(styles.statItem, className)}>
|
||||
<div className={styles.statItem__nav}>
|
||||
<div className={styles.statItem__top}>
|
||||
<Img />
|
||||
<p className={styles.statItem__nav_title}>{title}</p>
|
||||
<p className={styles.statItem__top_title}>{title}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.statItem__content}>
|
||||
|
|
@ -3,16 +3,20 @@
|
|||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
&__nav {
|
||||
&__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
svg {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
&_title {
|
||||
color: var(--footer-selected-link);
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,13 +28,13 @@
|
|||
&_val {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&_coefficient {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
|
||||
&.green {
|
||||
color: #16d1d6;
|
||||
|
|
@ -3,9 +3,9 @@ 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 { roundTo, notationToString } from '@/utils/utils';
|
||||
import { roundTo, notationToString, classes } from '@/utils/utils';
|
||||
import styles from './styles.module.scss';
|
||||
import StatItem from '../StatItem';
|
||||
import StatItem from './components/StatItem';
|
||||
import { TradingHeaderProps } from './types';
|
||||
import CurrencyIcon from './components/CurrencyIcon';
|
||||
import AssetRow from './components/AssetRow';
|
||||
|
|
@ -36,56 +36,60 @@ const TradingHeader = ({
|
|||
{
|
||||
Img: ClockIcon,
|
||||
title: '24h change',
|
||||
value: `${roundTo(notationToString(pairStats?.rate || 0), 4)} ${secondCurrencyName}`,
|
||||
value: `${roundTo(notationToString(pairStats?.rate || 0), 4)}`,
|
||||
coefficient: coefficientOutput,
|
||||
},
|
||||
{
|
||||
Img: UpIcon,
|
||||
title: '24h high',
|
||||
value: `${roundTo(notationToString(pairStats?.high || 0), 4)} ${secondCurrencyName}`,
|
||||
value: `${roundTo(notationToString(pairStats?.high || 0), 4)}`,
|
||||
},
|
||||
{
|
||||
Img: DownIcon,
|
||||
title: '24h low',
|
||||
value: `${roundTo(notationToString(pairStats?.low || 0), 4)} ${secondCurrencyName}`,
|
||||
value: `${roundTo(notationToString(pairStats?.low || 0), 4)}`,
|
||||
},
|
||||
{
|
||||
Img: VolumeIcon,
|
||||
title: '24h volume',
|
||||
value: `${roundTo(notationToString(pairStats?.volume || 0), 4)} ${secondCurrencyName}`,
|
||||
title: `24h volume (${firstCurrencyName})`,
|
||||
value: `${roundTo(notationToString(pairStats?.volume || 0), 4)}`,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.header__currency}>
|
||||
<div className={styles.header__currency_icon}>
|
||||
<CurrencyIcon code={firstAssetId} />
|
||||
</div>
|
||||
<div className={styles.header__stats}>
|
||||
<div className={styles.header__currency}>
|
||||
<div className={styles.header__currency_icon}>
|
||||
<CurrencyIcon code={firstAssetId} />
|
||||
</div>
|
||||
|
||||
<div className={styles.header__currency_item}>
|
||||
<p className={styles.currencyName}>
|
||||
{!pairData ? (
|
||||
'...'
|
||||
) : (
|
||||
<>
|
||||
{firstCurrencyName}
|
||||
<span>/{secondCurrencyName}</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className={styles.price}>
|
||||
<p className={styles.price__secondCurrency}>
|
||||
{roundTo(notationToString(pairStats?.rate || 0), 4)}{' '}
|
||||
{secondCurrencyName}
|
||||
<div className={styles.header__currency_item}>
|
||||
<p className={styles.currencyName}>
|
||||
{!pairData ? (
|
||||
'...'
|
||||
) : (
|
||||
<>
|
||||
{firstCurrencyName}
|
||||
<span>/{secondCurrencyName}</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{pairRateUsd && <p className={styles.price__usd}>~ ${pairRateUsd}</p>}
|
||||
|
||||
<div className={styles.price}>
|
||||
<p
|
||||
className={classes(
|
||||
styles.price__secondCurrency,
|
||||
coefficientOutput >= 0 ? styles.green : styles.red,
|
||||
)}
|
||||
>
|
||||
{roundTo(notationToString(pairStats?.rate || 0, 8))}
|
||||
</p>
|
||||
{pairRateUsd && <p className={styles.price__usd}>~ ${pairRateUsd}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.header__stats}>
|
||||
{pairData && firstAssetLink && secondAssetLink && (
|
||||
<div className={styles.header__stats_assets}>
|
||||
<AssetRow
|
||||
|
|
@ -115,7 +119,7 @@ const TradingHeader = ({
|
|||
))}
|
||||
</div>
|
||||
|
||||
<BackButton />
|
||||
<BackButton isSm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@
|
|||
&__currency {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
padding-right: 20px;
|
||||
border-right: 1px solid var(--delimiter-color);
|
||||
|
||||
&_icon {
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -21,7 +23,7 @@
|
|||
border-radius: 50%;
|
||||
|
||||
> img {
|
||||
width: 26px;
|
||||
width: 24px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,9 +32,10 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
|
||||
.currencyName {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
span {
|
||||
|
|
@ -48,6 +51,14 @@
|
|||
&__secondCurrency {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
|
||||
&.green {
|
||||
color: #16d1d6;
|
||||
}
|
||||
|
||||
&.red {
|
||||
color: #ff6767;
|
||||
}
|
||||
}
|
||||
|
||||
&__usd {
|
||||
|
|
|
|||
|
|
@ -2,22 +2,16 @@ import PeriodState from '@/interfaces/states/pages/dex/trading/InputPanelItem/Pe
|
|||
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',
|
||||
},
|
||||
{ name: '1s', code: '1sec' },
|
||||
{ name: '1m', code: '1min' },
|
||||
{ name: '5m', code: '5min' },
|
||||
{ name: '15m', code: '15min' },
|
||||
{ name: '30m', code: '30min' },
|
||||
{ name: '1h', code: '1h' },
|
||||
{ name: '4h', code: '4h' },
|
||||
{ name: '1d', code: '1d' },
|
||||
{ name: '1w', code: '1w' },
|
||||
{ name: '1M', code: '1m' },
|
||||
];
|
||||
|
||||
export const buySellValues: SelectValue[] = [
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
type Period = '1h' | '1d' | '1w' | '1m';
|
||||
type Period = '1sec' | '1min' | '5min' | '15min' | '30min' | '1h' | '4h' | '1d' | '1w' | '1m';
|
||||
|
||||
export default Period;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ interface HorizontalSelectProps<T extends HorizontalSelectValue> {
|
|||
setValue: Dispatch<SetStateAction<T>>;
|
||||
className?: string;
|
||||
isTab?: boolean;
|
||||
isSm?: boolean;
|
||||
}
|
||||
|
||||
export default HorizontalSelectProps;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
interface NavBarProps {
|
||||
mobile?: boolean;
|
||||
isLg?: boolean;
|
||||
}
|
||||
|
||||
export default NavBarProps;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import Period from '@/interfaces/common/Period';
|
|||
interface CandleChartProps {
|
||||
candles: CandleRow[];
|
||||
period: Period;
|
||||
currencyNames: {
|
||||
firstCurrencyName: string;
|
||||
secondCurrencyName: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default CandleChartProps;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import styles from '@/styles/Trading.module.scss';
|
||||
import Footer from '@/components/default/Footer/Footer';
|
||||
import Header from '@/components/default/Header/Header';
|
||||
import Dropdown from '@/components/UI/Dropdown/Dropdown';
|
||||
import HorizontalSelect from '@/components/UI/HorizontalSelect/HorizontalSelect';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { cancelOrder } from '@/utils/methods';
|
||||
|
|
@ -31,9 +30,6 @@ import useMatrixAddresses from '@/hook/useMatrixAddresses';
|
|||
import takeOrderClick from '@/utils/takeOrderClick';
|
||||
import useUpdateUser from '@/hook/useUpdateUser';
|
||||
|
||||
const CHART_OPTIONS = [{ name: 'Zano Chart' }, { name: 'Trading View', disabled: true }];
|
||||
const DEFAULT_CHART = CHART_OPTIONS[0];
|
||||
|
||||
function Trading() {
|
||||
const { alertState, alertSubtitle, setAlertState } = useAlert();
|
||||
const { elementRef: orderListRef, scrollToElement: scrollToOrdersList } =
|
||||
|
|
@ -44,7 +40,7 @@ function Trading() {
|
|||
const fetchUser = useUpdateUser();
|
||||
const [ordersHistory, setOrdersHistory] = useState<PageOrderData[]>([]);
|
||||
const [userOrders, setUserOrders] = useState<OrderRow[]>([]);
|
||||
const [periodsState, setPeriodsState] = useState<PeriodState>(periods[0]);
|
||||
const [periodsState, setPeriodsState] = useState<PeriodState>(periods[5]);
|
||||
const [pairData, setPairData] = useState<PairData | null>(null);
|
||||
const [candles, setCandles] = useState<CandleRow[]>([]);
|
||||
const [trades, setTrades] = useState<Trade[]>([]);
|
||||
|
|
@ -177,18 +173,16 @@ function Trading() {
|
|||
value={periodsState}
|
||||
setValue={setPeriodsState}
|
||||
isTab
|
||||
/>
|
||||
<Dropdown
|
||||
body={CHART_OPTIONS}
|
||||
className={styles.settings__dropdown}
|
||||
selfContained
|
||||
value={DEFAULT_CHART}
|
||||
setValue={() => undefined}
|
||||
isSm
|
||||
/>
|
||||
</div>
|
||||
|
||||
{candlesLoaded ? (
|
||||
<CandleChart candles={candles} period={periodsState.code} />
|
||||
<CandleChart
|
||||
currencyNames={currencyNames}
|
||||
candles={candles}
|
||||
period={periodsState.code}
|
||||
/>
|
||||
) : (
|
||||
<ContentPreloader style={{ height: '100%' }} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -52,4 +52,5 @@
|
|||
--dex-tooltip-border-color: #1f8feb26;
|
||||
--dex-sell-percentage: #272757;
|
||||
--dex-buy-percentage: #103262;
|
||||
}
|
||||
--action-btn-bg: #273666;
|
||||
}
|
||||
|
|
@ -52,4 +52,5 @@
|
|||
--dex-tooltip-border-color: #1f8feb33;
|
||||
--dex-sell-percentage: #fceded;
|
||||
--dex-buy-percentage: #e5f8f8;
|
||||
}
|
||||
--action-btn-bg: #e5f8f8;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue