From e89f4d10c3e73b2347d10f02104fd26822fd10a0 Mon Sep 17 00:00:00 2001 From: tradermohamed <110409704+tradermohamed@users.noreply.github.com> Date: Thu, 3 Aug 2023 04:44:26 +0900 Subject: [PATCH] Merge mobile changes (#12) * updated bar charts and colors * put coin selector sort in a util file --- components/common/chartWrapper/index.tsx | 6 +- components/home/charts/cumulative-inflow.tsx | 5 +- components/home/charts/cumulative-users.tsx | 10 ++- components/home/charts/fees.tsx | 6 +- components/home/charts/funding-rate.tsx | 41 +++---------- components/home/charts/hlp.tsx | 46 ++++++++------ components/home/charts/liquidator.tsx | 64 ++++++++++---------- components/home/charts/liquidity.tsx | 55 ++++++----------- components/home/charts/open-interest.tsx | 15 +++-- components/home/charts/retail-volume.tsx | 57 +++++++++-------- components/home/charts/trader-profit.tsx | 10 ++- components/home/charts/unique-users-coin.tsx | 64 +++++++++----------- components/home/charts/volume-num-trades.tsx | 57 +++++++++-------- components/home/charts/volume-total.tsx | 46 +++++++------- constants/tokens.ts | 53 ++++++---------- helpers/utils.ts | 52 ++++++++++++++++ hooks/isMobile.ts | 13 ++++ package-lock.json | 22 +++++++ package.json | 2 + 19 files changed, 331 insertions(+), 293 deletions(-) create mode 100644 helpers/utils.ts create mode 100644 hooks/isMobile.ts diff --git a/components/common/chartWrapper/index.tsx b/components/common/chartWrapper/index.tsx index 2302d16..496a9f3 100644 --- a/components/common/chartWrapper/index.tsx +++ b/components/common/chartWrapper/index.tsx @@ -33,7 +33,7 @@ const Loader = () => ( ); function ChartWrapper(props: any) { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + let isMobile = props.isMobile; const { title, loading, controls, zIndex, coinSelectors } = props; const controlButtons = controls && @@ -92,9 +92,7 @@ function ChartWrapper(props: any) { {controlButtons} ) : ( - - {controlButtons} - + {controlButtons} )} )} diff --git a/components/home/charts/cumulative-inflow.tsx b/components/home/charts/cumulative-inflow.tsx index 2acf73d..d4fe2e2 100644 --- a/components/home/charts/cumulative-inflow.tsx +++ b/components/home/charts/cumulative-inflow.tsx @@ -22,11 +22,12 @@ import { tooltipFormatterDate, } from '../../../helpers'; import { daily_inflow, cumulative_inflow } from '../../../constants/api'; +import { useIsMobile } from '@/hooks/isMobile'; const REQUESTS = [daily_inflow, cumulative_inflow]; export default function CumulativeInflow() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); const [formattedData, setFormattedData] = useState([]); const [dataDailyInflow, loadingDailyInflow, errorDailyInflow] = useRequest( @@ -99,7 +100,7 @@ export default function CumulativeInflow() { }, [loading, errorDailyInflow]); return ( - + diff --git a/components/home/charts/cumulative-users.tsx b/components/home/charts/cumulative-users.tsx index f09b5e0..3003ffe 100644 --- a/components/home/charts/cumulative-users.tsx +++ b/components/home/charts/cumulative-users.tsx @@ -11,6 +11,7 @@ import { } from 'recharts'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; +import { useIsMobile } from '@/hooks/isMobile'; import { useMediaQuery } from '@chakra-ui/react'; import ChartWrapper from '../../common/chartWrapper'; import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN, GREEN } from '../../../constants'; @@ -29,7 +30,7 @@ import { const REQUESTS = [cumulative_new_users, daily_unique_users, daily_unique_users_by_coin]; export default function CumulativeUsers() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); const [formattedData, setFormattedData] = useState([]); @@ -74,7 +75,12 @@ export default function CumulativeUsers() { }, [loading, error]); return ( - + diff --git a/components/home/charts/fees.tsx b/components/home/charts/fees.tsx index c56e652..a893645 100644 --- a/components/home/charts/fees.tsx +++ b/components/home/charts/fees.tsx @@ -11,6 +11,7 @@ import { } from 'recharts'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; +import { useIsMobile } from '@/hooks/isMobile'; import { useMediaQuery } from '@chakra-ui/react'; import ChartWrapper from '../../common/chartWrapper'; import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN, GREEN } from '../../../constants'; @@ -25,7 +26,8 @@ import { total_accrued_fees } from '../../../constants/api'; const REQUESTS = [total_accrued_fees]; export default function Fees() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); + const [formattedData, setFormattedData] = useState([]); const [dailyFeesAccrued, loading, error] = useRequest(REQUESTS[0], [], 'chart_data'); @@ -68,7 +70,7 @@ export default function Fees() { }, [loading, error]); return ( - + diff --git a/components/home/charts/funding-rate.tsx b/components/home/charts/funding-rate.tsx index 2ca2999..190f37b 100644 --- a/components/home/charts/funding-rate.tsx +++ b/components/home/charts/funding-rate.tsx @@ -11,6 +11,7 @@ import { import { useMediaQuery } from '@chakra-ui/react'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; +import { useIsMobile } from '@/hooks/isMobile'; import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { CHART_HEIGHT } from '../../../constants'; import { @@ -19,13 +20,15 @@ import { formatterPercent, tooltipFormatterDate, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; +import { createCoinSelectors } from "../../../helpers/utils"; + +import { getTokenColor, initialTokensSelected } from '../../../constants/tokens'; import { funding_rate } from '../../../constants/api'; const REQUESTS = [funding_rate]; export default function FundingRate() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); const [coinKeys, setCoinKeys] = useState([]); const [formattedData, setFormattedData] = useState([]); @@ -34,7 +37,7 @@ export default function FundingRate() { [], 'chart_data' ); - const [coinsSelected, setCoinsSelected] = useState(['ETH', 'BTC', 'ARB']); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelected); const loading = loadingFundingRate; const error = errorFundingRate; @@ -113,34 +116,7 @@ export default function FundingRate() { } }, [loading, coinsSelected]); - const coinSelectorsSort = (a: CoinSelector, b: CoinSelector) => { - if (a.isChecked !== b.isChecked) { - return a.isChecked ? -1 : 1; - } - return a.name.localeCompare(b.name); - }; - - const coinSelectors = coinKeys - .map((coinKey: string) => { - return { - name: coinKey, - event: () => - setCoinsSelected((coinsSelected) => { - let newCoinsSelected = coinsSelected; - if (coinsSelected.includes(coinKey)) { - newCoinsSelected = coinsSelected.filter((e) => { - return e !== coinKey; - }); - } else { - newCoinsSelected.push(coinKey); - } - formatData(); - return newCoinsSelected; - }), - isChecked: coinsSelected.includes(coinKey), - }; - }) - .sort((a: CoinSelector, b: CoinSelector) => coinSelectorsSort(a, b)); + const coinSelectors = createCoinSelectors(coinKeys, coinsSelected, setCoinsSelected, formatData) return ( @@ -189,7 +166,7 @@ export default function FundingRate() { dataKey={coinName.toString()} dot={false} name={coinName.toString()} - stroke={getTokenHex(coinName.toString())} + stroke={getTokenColor(coinName.toString())} key={'funding-rate-line-' + i} /> ); diff --git a/components/home/charts/hlp.tsx b/components/home/charts/hlp.tsx index 6682717..d58eb8b 100644 --- a/components/home/charts/hlp.tsx +++ b/components/home/charts/hlp.tsx @@ -13,6 +13,8 @@ import { import { Box, Text, useMediaQuery } from '@chakra-ui/react'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; +import { useIsMobile } from '@/hooks/isMobile'; + import ChartWrapper from '../../common/chartWrapper'; import { BLUE, BRIGHT_GREEN, CHART_HEIGHT, GREEN, ORANGE, RED } from '../../../constants'; import { @@ -22,14 +24,16 @@ import { yaxisFormatter, tooltipFormatterLongShort, } from '../../../helpers'; -import { getTokenHex } from '@/constants/tokens'; + +import { getTokenColor } from '@/constants/tokens'; import { asset_ctxs, hlp_liquidator_pnl, hlp_positions } from '@/constants/api'; const REQUESTS = [hlp_positions, asset_ctxs, hlp_liquidator_pnl]; const DAY = 60 * 60 * 24 * 1000; export default function Hlp() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); + const [dataMode, setDataMode] = useState<'COINS' | 'NET' | 'PNL' | 'HEDGED'>('PNL'); const [coins, setCoins] = useState([]); const [dataHlpPositions, loadingDataHlpPositions, errorDataHlpPositions] = useRequest( @@ -82,7 +86,7 @@ export default function Hlp() { const getOraclePxs = (assetCtxs: AssetCtx[]): Map => { const map = new Map(); assetCtxs.forEach((item) => { - map.set(item.coin + item.time, item.first_oracle_px); + map.set(item.coin + item.time, item.first_oracle_px); }); return map; }; @@ -128,7 +132,7 @@ export default function Hlp() { let { time, coin, daily_ntl } = item; if (!map.has(time)) { const pnl = hlpPnL.get(time)?.pnl || 0; - hedgedCumulativePnl += pnl; + hedgedCumulativePnl += pnl; map.set(time, { time: new Date(time), daily_ntl: 0, @@ -144,12 +148,12 @@ export default function Hlp() { existingEntry.daily_ntl += daily_ntl; const oraclePx = oraclePxs.get(coin + time); - let hedgedPnl = 0; + let hedgedPnl = 0; const nextTime = getNextTime(time); let oraclePxNext = oraclePxs.get(coin + nextTime); - - let prevTimeData = prevTime ? map.get(prevTime) : null; - let prevDayNtlPosition = prevTimeData ? prevTimeData[`${coin}`] : null; + + let prevTimeData = prevTime ? map.get(prevTime) : null; + let prevDayNtlPosition = prevTimeData ? prevTimeData[`${coin}`] : null; if (oraclePxNext && oraclePx && prevDayNtlPosition) { const pxChange = 1 - oraclePx / oraclePxNext; @@ -157,9 +161,9 @@ export default function Hlp() { hedgedPnl += pnl; } - existingEntry.hedged_pnl += hedgedPnl; + existingEntry.hedged_pnl += hedgedPnl; hedgedCumulativePnl += hedgedPnl; - existingEntry.hedged_cumulative_pnl = hedgedCumulativePnl; + existingEntry.hedged_cumulative_pnl = hedgedCumulativePnl; }); map.forEach((entry) => { @@ -237,7 +241,13 @@ export default function Hlp() { }, [loading, error, hlpPnL]); return ( - + @@ -295,7 +305,7 @@ export default function Hlp() { dataKey={coin} stackId='a' name={coin.toString()} - fill={getTokenHex(coin.toString())} + fill={getTokenColor(coin.toString())} key={i} maxBarSize={20} /> @@ -356,21 +366,19 @@ export default function Hlp() { )} - {dataMode === 'PNL' && ( - PNL over time - )} + {dataMode === 'PNL' && PNL over time} {dataMode === 'HEDGED' && ( - Hedged PNL over time. Hedge the previous day's position and add to today's PNL. + + Hedged PNL over time. Hedge the previous day's position and add to today's PNL. + )} - {dataMode === 'NET' && ( - Net notional position over time - )} + {dataMode === 'NET' && Net notional position over time} ); diff --git a/components/home/charts/liquidator.tsx b/components/home/charts/liquidator.tsx index d6a88aa..b34f45a 100644 --- a/components/home/charts/liquidator.tsx +++ b/components/home/charts/liquidator.tsx @@ -12,8 +12,9 @@ import { } from 'recharts'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; +import { useIsMobile } from '@/hooks/isMobile'; import { Box, Text, useMediaQuery } from '@chakra-ui/react'; -import ChartWrapper from '../../common/chartWrapper'; +import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { CHART_HEIGHT, YAXIS_WIDTH, @@ -30,7 +31,9 @@ import { tooltipFormatterCurrency, tooltipFormatterDate, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; +import { createCoinSelectorsWithFormatArg } from "../../../helpers/utils"; + +import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens'; import { cumulative_liquidated_notional, daily_notional_liquidated_total, @@ -50,9 +53,10 @@ const REQUESTS = [ ]; export default function LiquidatorChart() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); const [dataMode, setDataMode] = useState<'COINS' | 'MARGIN' | 'PNL'>('COINS'); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther); const [formattedDataCoins, setFormattedDataCoins] = useState([]); const [formattedDataMargin, setFormattedDataMargin] = useState([]); @@ -168,6 +172,7 @@ export default function LiquidatorChart() { type FormattedCoinTradesData = any[]; const formatDailyTradesByCoins = ( + CoinsSelected: string[], dataDailyTradesByCoin: { time: string; coin: string; daily_notional_liquidated: number }[], formattedCumulativeByTime: { [key: string]: number } ): FormattedCoinTradesData[] => { @@ -180,25 +185,21 @@ export default function LiquidatorChart() { temp[data.time].all += data.daily_notional_liquidated; } - const sortAndSliceTop10 = (obj: { [coin: string]: number }) => { - const sortedEntries = Object.entries(obj).sort( - ([, aVolume], [, bVolume]) => bVolume - aVolume - ); - const top10Entries = sortedEntries.slice(0, 10); - const otherEntries = sortedEntries.slice(10); - + const selectedCoinData = (obj: { [coin: string]: number }) => { + const selectedEntries = Object.entries(obj).filter(([coin]) => CoinsSelected.includes(coin) || coin==="all"); + const otherEntries = Object.entries(obj).filter(([coin]) => (!(CoinsSelected.includes(coin))) && (coin !== "all")); const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0); return { - ...Object.fromEntries(top10Entries), + ...Object.fromEntries(selectedEntries), Other: otherVolume, }; }; const result: any[] = Object.entries(temp).map(([time, volumes]) => { - const top10Volumes = sortAndSliceTop10(volumes); + const selectedVolumes = selectedCoinData(volumes); return { time: new Date(time), - ...top10Volumes, + ...selectedVolumes, cumulative: formattedCumulativeByTime[time as any], unit: '', }; @@ -206,25 +207,17 @@ export default function LiquidatorChart() { return result; }; - const extractUniqueCoins = (formattedData: any[]): string[] => { + const extractUniqueCoins = (coinData: any[]): string[] => { const coinSet = new Set(); - for (const data of formattedData) { - Object.keys(data).forEach((coin) => { - if (coin !== 'time' && coin !== 'unit' && coin !== 'cumulative' && coin !== 'all') { - coinSet.add(coin); + for (const data of coinData) { + if (data.coin !== 'time' && data.coin !== 'unit' && data.coin !== 'cumulative' && data.coin !== 'all') { + coinSet.add(data.coin); } - }); - } - const coinsArray = Array.from(coinSet); - if (coinsArray.includes('Other')) { - const index = coinsArray.indexOf('Other'); - coinsArray.splice(index, 1); - coinsArray.push('Other'); } - return coinsArray; + return Array.from(coinSet); }; - const formatData = () => { + const formatData = (CoinsSelected: string[]) => { const formattedCumulativeLiquidatedByTime = formatCumulativeLiquidatedByTime(dataCumulativeLiquidated); const formattedVolumeByMargin = formatLiquidatedByMargin( @@ -232,6 +225,7 @@ export default function LiquidatorChart() { formattedCumulativeLiquidatedByTime ); const formattedDailyTradesByCoins = formatDailyTradesByCoins( + CoinsSelected, dataDailyLiquidatedByCoins, formattedCumulativeLiquidatedByTime ); @@ -240,7 +234,7 @@ export default function LiquidatorChart() { dataLiquidatorCumulativePnl ); setFormattedLiquidatorPnl(newFormattedLiquidatorPnl); - setCoinKeys(extractUniqueCoins(formattedDailyTradesByCoins)); + setCoinKeys(extractUniqueCoins(dataDailyLiquidatedByCoins)); setFormattedDataMargin(formattedVolumeByMargin); setFormattedDataCoins(formattedDailyTradesByCoins); console.log('dev formattedDailyTradesByCoins', formattedDailyTradesByCoins); @@ -268,7 +262,7 @@ export default function LiquidatorChart() { useEffect(() => { if (!loading && !error) { - formatData(); + formatData(coinsSelected); } }, [loading, error]); @@ -297,6 +291,10 @@ export default function LiquidatorChart() { return [-1 * Math.abs(maxCumulativePnl) * 1.1, Math.abs(maxCumulativePnl) * 1.1]; }; + + const coinSelectors = createCoinSelectorsWithFormatArg(coinKeys, coinsSelected, setCoinsSelected, formatData) + + return ( @@ -334,8 +334,8 @@ export default function LiquidatorChart() { {dataMode === 'COINS' && ( <> - {coinKeys && - coinKeys.map((coinName, i) => { + { + coinsSelected.map((coinName, i) => { return ( diff --git a/components/home/charts/liquidity.tsx b/components/home/charts/liquidity.tsx index 0bc7e92..128d18e 100644 --- a/components/home/charts/liquidity.tsx +++ b/components/home/charts/liquidity.tsx @@ -12,6 +12,7 @@ import { useEffect, useState } from 'react'; import { Box, Text, useMediaQuery } from '@chakra-ui/react'; import { useRequest } from '@/hooks/useRequest'; import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; +import { useIsMobile } from '@/hooks/isMobile'; import { CHART_HEIGHT } from '../../../constants'; import { tooltipFormatter, @@ -19,13 +20,16 @@ import { xAxisFormatter, formatterPercent, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; +import { createCoinSelectors } from "../../../helpers/utils"; + +import { getTokenColor, initialTokensSelected } from '../../../constants/tokens'; import { liquidity_by_coin } from '../../../constants/api'; const REQUESTS = [liquidity_by_coin]; export default function Liquidity() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); + const [formattedData0, setFormattedData0] = useState([]); const [formattedData1000, setFormattedData1000] = useState([]); const [formattedData3000, setFormattedData3000] = useState([]); @@ -38,7 +42,7 @@ export default function Liquidity() { const [coinKeys10000, setCoinKeys10000] = useState([]); const [dataMode, setDataMode] = useState<'0' | '1000' | '3000' | '10000'>('0'); - const [coinsSelected, setCoinsSelected] = useState(['ETH', 'BTC', 'ARB']); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelected); const [dataLiqudity, loadingLiqudity, errorLiqudity] = useRequest(REQUESTS[0], [], 'chart_data'); const loading = loadingLiqudity; @@ -87,12 +91,12 @@ export default function Liquidity() { }; const extractCoins = (data: InputData): string[] => { - let coins = []; + let coins = []; for (let coin of Object.keys(data)) { - coins.push(coin); + coins.push(coin); } - return coins; - } + return coins; + }; const transformData = (data: InputData): OutputData => { // Filter data for each category by top 10 coins @@ -171,8 +175,8 @@ export default function Liquidity() { }; const formatData = () => { - const extractedCoinKeys = extractCoins(dataLiqudity); - setCoinKeys(extractedCoinKeys); + const extractedCoinKeys = extractCoins(dataLiqudity); + setCoinKeys(extractedCoinKeys); const formattedData = transformData(dataLiqudity); setFormattedData0(formattedData.median_slippage_0); setFormattedData1000(formattedData.median_slippage_1000); @@ -182,7 +186,7 @@ export default function Liquidity() { const formattedUniqueCoinKeys1000 = extractUniqueCoins(formattedData.median_slippage_1000); const formattedUniqueCoinKeys3000 = extractUniqueCoins(formattedData.median_slippage_3000); const formattedUniqueCoinKeys10000 = extractUniqueCoins(formattedData.median_slippage_10000); - + setCoinKeys0(formattedUniqueCoinKeys0); setCoinKeys1000(formattedUniqueCoinKeys1000); setCoinKeys3000(formattedUniqueCoinKeys3000); @@ -213,34 +217,8 @@ export default function Liquidity() { ? coinKeys3000 : coinKeys10000; - const coinSelectorsSort = (a: CoinSelector, b: CoinSelector) => { - if (a.isChecked !== b.isChecked) { - return a.isChecked ? -1 : 1; - } - return a.name.localeCompare(b.name); - }; + const coinSelectors = createCoinSelectors(coinKeys, coinsSelected, setCoinsSelected, formatData); - const coinSelectors = coinKeys - .map((coinKey: string) => { - return { - name: coinKey, - event: () => - setCoinsSelected((coinsSelected) => { - let newCoinsSelected = coinsSelected; - if (coinsSelected.includes(coinKey)) { - newCoinsSelected = coinsSelected.filter((e) => { - return e !== coinKey; - }); - } else { - newCoinsSelected.push(coinKey); - } - formatData(); - return newCoinsSelected; - }), - isChecked: coinsSelected.includes(coinKey), - }; - }) - .sort((a: CoinSelector, b: CoinSelector) => coinSelectorsSort(a, b)); return ( @@ -289,7 +268,7 @@ export default function Liquidity() { type='monotone' dataKey={`${coinName}`} name={coinName.toString()} - stroke={getTokenHex(coinName.toString())} + stroke={getTokenColor(coinName.toString())} key={i} dot={false} /> diff --git a/components/home/charts/open-interest.tsx b/components/home/charts/open-interest.tsx index 585ad34..f00bd61 100644 --- a/components/home/charts/open-interest.tsx +++ b/components/home/charts/open-interest.tsx @@ -11,7 +11,7 @@ import { import { useEffect, useState } from 'react'; import { Box, Text, useMediaQuery } from '@chakra-ui/react'; import { useRequest } from '@/hooks/useRequest'; -import ChartWrapper from '../../common/chartWrapper'; +import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { BRIGHT_GREEN, CHART_HEIGHT, GREEN, YAXIS_WIDTH } from '../../../constants'; import { xAxisFormatter, @@ -20,15 +20,18 @@ import { tooltipFormatterCurrency, tooltipFormatterDate, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; + +import { getTokenColor } from '../../../constants/tokens'; import { open_interest } from '../../../constants/api'; +import { useIsMobile } from '@/hooks/isMobile'; const REQUESTS = [open_interest]; export default function VolumeChart() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); - + const [isMobile] = useIsMobile(); + const [hasSetCoinsSelected, setHasSetCoinsSelected] = useState(false); const [coinKeys, setCoinKeys] = useState([]); + const [formattedData, setFormattedData] = useState([]); const [dataOpenInterest, loadingOpenInterest, errorOpenInterest] = useRequest( REQUESTS[0], @@ -125,7 +128,7 @@ export default function VolumeChart() { }, [loading]); return ( - + @@ -168,7 +171,7 @@ export default function VolumeChart() { dataKey={coinName} dot={false} name={coinName.toString()} - stroke={getTokenHex(coinName.toString())} + stroke={getTokenColor(coinName.toString())} key={'open-i-rate-line-' + i} /> ); diff --git a/components/home/charts/retail-volume.tsx b/components/home/charts/retail-volume.tsx index ce0dc3e..69ae99f 100644 --- a/components/home/charts/retail-volume.tsx +++ b/components/home/charts/retail-volume.tsx @@ -12,7 +12,7 @@ import { import { useEffect, useState } from 'react'; import { Box, Text, useMediaQuery } from '@chakra-ui/react'; import { useRequest } from '@/hooks/useRequest'; -import ChartWrapper from '../../common/chartWrapper'; +import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { CHART_HEIGHT, YAXIS_WIDTH, @@ -26,7 +26,9 @@ import { yaxisFormatter, xAxisFormatter, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; +import { createCoinSelectorsWithFormatArg } from "../../../helpers/utils"; + +import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens'; import { cumulative_usd_volume, daily_usd_volume, @@ -34,6 +36,7 @@ import { daily_usd_volume_by_crossed, daily_usd_volume_by_user, } from '../../../constants/api'; +import { useIsMobile } from '@/hooks/isMobile'; const REQUESTS = [ cumulative_usd_volume, @@ -44,10 +47,12 @@ const REQUESTS = [ ]; export default function RetailVolumeChart() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); + const [dataMode, setDataMode] = useState<'COINS' | 'MARGIN'>('COINS'); const [formattedDataCoins, setFormattedDataCoins] = useState([]); const [formattedDataMargin, setFormattedDataMargin] = useState([]); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther); const [coinKeys, setCoinKeys] = useState([]); const [dataCumulativeUsdVolume, loadingCumulativeUsdVolume, errorCumulativeUsdVolume] = useRequest(REQUESTS[0], [], 'chart_data'); @@ -107,6 +112,7 @@ export default function RetailVolumeChart() { type FormattedVolumeData = any[]; //{ time: string, all: number, [coin: string]: number }; const formatVolumeByCoins = ( + CoinsSelected: string[], dataDailyUsdVolumeByCoin: VolumeData[], formattedCumulativeUsdVolume: { [key: string]: number }, formattedDailyVolumeByTime: { [key: string]: number } @@ -120,25 +126,21 @@ export default function RetailVolumeChart() { temp[data.time].all += data.daily_usd_volume; } - const sortAndSliceTop10 = (obj: { [coin: string]: number }) => { - const sortedEntries = Object.entries(obj).sort( - ([, aVolume], [, bVolume]) => bVolume - aVolume - ); - const top10Entries = sortedEntries.slice(0, 10); - const otherEntries = sortedEntries.slice(10); - + const selectedCoinData = (obj: { [coin: string]: number }) => { + const selectedEntries = Object.entries(obj).filter(([coin]) => CoinsSelected.includes(coin) && coin !== "all"); + const otherEntries = Object.entries(obj).filter(([coin]) => (!(CoinsSelected.includes(coin))) && (coin !== "all")); const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0); return { - ...Object.fromEntries(top10Entries), + ...Object.fromEntries(selectedEntries), Other: otherVolume, }; }; const result: any[] = Object.entries(temp).map(([time, volumes]) => { - const top10Volumes = sortAndSliceTop10(volumes); + const selectedVolumes = selectedCoinData(volumes); return { time: new Date(time), - ...top10Volumes, + ...selectedVolumes, cumulative: formattedCumulativeUsdVolume[time as any], all: formattedDailyVolumeByTime[time as any], unit: '$', @@ -148,20 +150,10 @@ export default function RetailVolumeChart() { return result; }; - const extractUniqueCoins = (formattedVolumeData: FormattedVolumeData[]): string[] => { + const extractUniqueCoins = (formattedVolumeData: VolumeData[]): string[] => { const coinSet = new Set(); for (const data of formattedVolumeData) { - Object.keys(data).forEach((coin) => { - if ( - coin !== 'all' && - coin !== 'cumulative' && - coin !== 'time' && - coin !== 'other' && - coin !== 'unit' - ) { - coinSet.add(coin); - } - }); + coinSet.add(data.coin); } const coinsArray = Array.from(coinSet); if (coinsArray.includes('Other')) { @@ -209,10 +201,11 @@ export default function RetailVolumeChart() { return result; }; - const formatData = () => { + const formatData = (CoinsSelected: string[]) => { const formattedCumulativeVolumeByTime = formatCumulativeVolumeByTime(dataCumulativeUsdVolume); const formattedDailyVolumeByTime = formatDailyVolumeByTime(dataDailyUsdVolume); const formattedVolumeByCoins = formatVolumeByCoins( + CoinsSelected, dataDailyUsdVolumeByCoin, formattedCumulativeVolumeByTime, formattedDailyVolumeByTime @@ -222,7 +215,7 @@ export default function RetailVolumeChart() { formattedCumulativeVolumeByTime, formattedDailyVolumeByTime ); - setCoinKeys(extractUniqueCoins(formattedVolumeByCoins)); + setCoinKeys(extractUniqueCoins(dataDailyUsdVolumeByCoin)); setFormattedDataCoins(formattedVolumeByCoins); setFormattedDataMargin(formattedVolumeByCrossed); }; @@ -244,10 +237,12 @@ export default function RetailVolumeChart() { useEffect(() => { if (!loading || error) { - formatData(); + formatData(coinsSelected); } }, [loading, error]); + const coinSelectors = createCoinSelectorsWithFormatArg(coinKeys, coinsSelected, setCoinsSelected, formatData); + return ( {dataMode === 'COINS' && ( <> - {coinKeys.map((coinName, i) => { + {coinsSelected.map((coinName, i) => { return ( diff --git a/components/home/charts/trader-profit.tsx b/components/home/charts/trader-profit.tsx index 8f6c60e..11d6687 100644 --- a/components/home/charts/trader-profit.tsx +++ b/components/home/charts/trader-profit.tsx @@ -23,11 +23,12 @@ import { tooltipFormatterCurrency, tooltipFormatterDate, } from '../../../helpers'; +import { useIsMobile } from '@/hooks/isMobile'; const REQUESTS = [cumulative_user_pnl, user_pnl]; export default function TradersProfitLossChart() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); const [data, setData] = useState(null); const [dataCumulativeUserPNL, loadingCumulativeUserPNL, errorCumulativeUserPNL] = useRequest( @@ -101,7 +102,12 @@ export default function TradersProfitLossChart() { }, [loading, error]); return ( - + diff --git a/components/home/charts/unique-users-coin.tsx b/components/home/charts/unique-users-coin.tsx index 4ef7e44..b8f4768 100644 --- a/components/home/charts/unique-users-coin.tsx +++ b/components/home/charts/unique-users-coin.tsx @@ -12,7 +12,9 @@ import { import { useEffect, useState } from 'react'; import { Box, Text, useMediaQuery } from '@chakra-ui/react'; import { useRequest } from '@/hooks/useRequest'; -import ChartWrapper from '../../common/chartWrapper'; +import { useIsMobile } from '@/hooks/isMobile'; + +import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN } from '../../../constants'; import { tooltipFormatter, @@ -21,7 +23,9 @@ import { yaxisFormatterNumber, yaxisFormatterPercent, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; +import { createCoinSelectorsWithFormatArg } from "../../../helpers/utils"; + +import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens'; import { cumulative_new_users, daily_unique_users, @@ -68,7 +72,8 @@ type TempGroupedTradeData = { const REQUESTS = [cumulative_new_users, daily_unique_users, daily_unique_users_by_coin]; export default function UniqueUsers() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther); const [formattedData, setFormattedData] = useState([]); const [coinKeys, setCoinKeys] = useState([]); @@ -91,6 +96,7 @@ export default function UniqueUsers() { const error = errorCumulativeNewUsers || errorDailyUniqueUsers || errorDailyUniqueUsersByCoin; const formatTradesByCoinAndTime = ( + CoinsSelected: string[], dataDailyUniqueUsersByCoin: DailyUniqueUsersByCoin[], uniqueUserTradeData: UniqueUserTradeData[], dataCumulativeNewUsers: CumulativeNewUsersData[] @@ -123,15 +129,12 @@ export default function UniqueUsers() { } ); - const sortAndSliceTop10 = (obj: { [coin: string]: number }) => { - const sortedEntries = Object.entries(obj).sort( - ([, aVolume], [, bVolume]) => bVolume - aVolume - ); - const top10Entries = sortedEntries.slice(0, 10); - const otherEntries = sortedEntries.slice(10); + const selectedCoinData = (obj: { [coin: string]: number }) => { + const selectedEntries = Object.entries(obj).filter(([coin]) => CoinsSelected.includes(coin) || coin==="all"); + const otherEntries = Object.entries(obj).filter(([coin]) => (!(CoinsSelected.includes(coin))) && (coin !== "all")); const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0); return { - ...Object.fromEntries(top10Entries), + ...Object.fromEntries(selectedEntries), Other: otherVolume, }; }; @@ -139,63 +142,50 @@ export default function UniqueUsers() { return Object.values(temp).map(({ time, coins, ...rest }) => { return { time: new Date(time), - ...sortAndSliceTop10(coins), + ...selectedCoinData(coins), ...rest, unit: '%', }; }); }; - const extractUniqueCoins = (formattedVolumeData: GroupedTradeData[]): string[] => { + const extractUniqueCoins = (CoinData: any): string[] => { const coinSet = new Set(); - for (const data of formattedVolumeData) { - Object.keys(data).forEach((coin) => { - if ( - coin !== 'all' && - coin !== 'cumulative' && - coin !== 'time' && - coin !== 'other' && - coin !== 'unit' && - coin !== 'daily_unique_users' && - coin !== 'cumulative_unique_users' && - !coin.includes('daily_unique_users') - ) { - coinSet.add(coin); - } - }); + for (const data of CoinData) { + coinSet.add(data.coin); } const coinsArray = Array.from(coinSet); - if (coinsArray.includes('Other')) { - const index = coinsArray.indexOf('Other'); - coinsArray.splice(index, 1); - coinsArray.push('Other'); - } return coinsArray; }; - const formatData = () => { + const formatData = (CoinsSelector: string[]) => { const formattedData = formatTradesByCoinAndTime( + CoinsSelector, dataDailyUniqueUsersByCoin, dataDailyUniqueUsers, dataCumulativeNewUsers ); - const formattedUniqueCoinKeys = extractUniqueCoins(formattedData); + const formattedUniqueCoinKeys = extractUniqueCoins(dataDailyUniqueUsersByCoin); setFormattedData(formattedData); setCoinKeys(formattedUniqueCoinKeys); }; useEffect(() => { if (!loading && !error) { - formatData(); + formatData(coinsSelected); } }, [loading]); + const coinSelectors = createCoinSelectorsWithFormatArg(coinKeys, coinsSelected, setCoinsSelected, formatData); + return ( @@ -239,7 +229,7 @@ export default function UniqueUsers() { }} /> - {coinKeys.map((coinName, i) => { + {coinsSelected.map((coinName, i) => { return ( diff --git a/components/home/charts/volume-num-trades.tsx b/components/home/charts/volume-num-trades.tsx index 3f76a84..28e990b 100644 --- a/components/home/charts/volume-num-trades.tsx +++ b/components/home/charts/volume-num-trades.tsx @@ -13,7 +13,9 @@ import { import { Box, Text, useMediaQuery } from '@chakra-ui/react'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; -import ChartWrapper from '../../common/chartWrapper'; +import { useIsMobile } from '@/hooks/isMobile'; + +import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { CHART_HEIGHT, YAXIS_WIDTH, @@ -27,7 +29,9 @@ import { yaxisFormatterNumber, xAxisFormatter, } from '../../../helpers'; -import { getTokenHex } from '../../../constants/tokens'; +import { createCoinSelectorsWithFormatArg } from "../../../helpers/utils"; + +import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens'; import { cumulative_trades, daily_trades, @@ -45,7 +49,8 @@ const REQUESTS = [ ]; export default function VolumeChart() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther); const [dataMode, setDataMode] = useState<'COINS' | 'MARGIN' | 'USER'>('COINS'); const [formattedDataCoins, setFormattedDataCoins] = useState([]); @@ -108,6 +113,7 @@ export default function VolumeChart() { type FormattedCoinTradesData = any[]; //{ time: string, all: number, [coin: string]: number }; const formatDailyTradesByCoins = ( + CoinsSelected: string[], dataDailyTradesByCoin: { coin: string; daily_trades: number; time: string }[], formattedCumulativeTradesByTime: { [key: string]: number } ): FormattedCoinTradesData[] => { @@ -120,22 +126,18 @@ export default function VolumeChart() { temp[data.time].all += data.daily_trades; } - const sortAndSliceTop10 = (obj: { [coin: string]: number }) => { - const sortedEntries = Object.entries(obj).sort( - ([, aVolume], [, bVolume]) => bVolume - aVolume - ); - const top10Entries = sortedEntries.slice(0, 10); - const otherEntries = sortedEntries.slice(10); - + const selectedCoinData = (obj: { [coin: string]: number }) => { + const selectedEntries = Object.entries(obj).filter(([coin]) => CoinsSelected.includes(coin) || coin==="all"); + const otherEntries = Object.entries(obj).filter(([coin]) => (!(CoinsSelected.includes(coin))) && (coin !== "all")); const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0); return { - ...Object.fromEntries(top10Entries), + ...Object.fromEntries(selectedEntries), Other: otherVolume, }; }; const result: any[] = Object.entries(temp).map(([time, volumes]) => { - const top10Volumes = sortAndSliceTop10(volumes); + const top10Volumes = selectedCoinData(volumes); return { time: new Date(time), ...top10Volumes, @@ -146,21 +148,12 @@ export default function VolumeChart() { return result; }; - const extractUniqueCoins = (formattedCoinTradesData: FormattedCoinTradesData[]): string[] => { + const extractUniqueCoins = (CoinData: any): string[] => { const coinSet = new Set(); - for (const data of formattedCoinTradesData) { - Object.keys(data).forEach((coin) => { - if (coin !== 'all' && coin !== 'cumulative' && coin !== 'time' && coin !== 'unit') { - coinSet.add(coin); - } - }); + for (const data of CoinData) { + coinSet.add(data.coin); } const coinsArray = Array.from(coinSet); - if (coinsArray.includes('Other')) { - const index = coinsArray.indexOf('Other'); - coinsArray.splice(index, 1); - coinsArray.push('Other'); - } return coinsArray; }; @@ -204,9 +197,10 @@ export default function VolumeChart() { return Object.values(groupedByTime); }; - const formatData = () => { + const formatData = (CoinsSelected: string[]) => { const formattedCumulativeTradesByTime = formatTradesByTime(dataCumulativeTrades); const formattedTradesByCoins = formatDailyTradesByCoins( + CoinsSelected, dataDailyTradesByCoin, formattedCumulativeTradesByTime ); @@ -215,7 +209,7 @@ export default function VolumeChart() { formattedCumulativeTradesByTime ); setMaxAllValueUser(maxAllValueUser); - setCoinKeys(extractUniqueCoins(formattedTradesByCoins)); + setCoinKeys(extractUniqueCoins(dataDailyTradesByCoin)); setFormattedDataCoins(formattedTradesByCoins); setFormattedDataMarin(formattedTradesByMargin); }; @@ -237,10 +231,13 @@ export default function VolumeChart() { useEffect(() => { if (!loading && !error) { - formatData(); + formatData(coinsSelected); } }, [loading, dataMode]); + + const coinSelectors = createCoinSelectorsWithFormatArg(coinKeys, coinsSelected, setCoinsSelected, formatData); + return ( - {coinKeys.map((coinName, i) => { + {coinsSelected.map((coinName, i) => { return ( diff --git a/components/home/charts/volume-total.tsx b/components/home/charts/volume-total.tsx index ad286c2..52dd4b0 100644 --- a/components/home/charts/volume-total.tsx +++ b/components/home/charts/volume-total.tsx @@ -11,8 +11,10 @@ import { } from 'recharts'; import { useEffect, useState } from 'react'; import { useRequest } from '@/hooks/useRequest'; +import { useIsMobile } from '@/hooks/isMobile'; + import { Box, Text, useMediaQuery } from '@chakra-ui/react'; -import ChartWrapper from '../../common/chartWrapper'; +import ChartWrapper, { CoinSelector } from '../../common/chartWrapper'; import { BRIGHT_GREEN, CHART_HEIGHT, YAXIS_WIDTH } from '../../../constants'; import { yaxisFormatter, @@ -20,14 +22,17 @@ import { tooltipFormatterCurrency, tooltipLabelFormatter, } from '../../../helpers'; +import { createCoinSelectorsWithFormatArg } from "../../../helpers/utils"; + import { total_volume } from '../../../constants/api'; -import { getTokenHex } from '@/constants/tokens'; +import { getTokenColor, initialTokensSelectedWithOther } from '@/constants/tokens'; const REQUESTS = [total_volume]; export default function TotalVolumeChart() { - const [isMobile] = useMediaQuery('(max-width: 700px)'); + const [isMobile] = useIsMobile(); const [formattedData, setFormattedData] = useState([]); + const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther); const [coins, setCoins] = useState([]); const [dataTotalVolume, loading, error] = useRequest(REQUESTS[0], [], 'chart_data'); @@ -46,9 +51,9 @@ export default function TotalVolumeChart() { Other: number; } - const makeFormattedData = (dataTotalVolume: TotalVolume[]): [MergedData[], string[]] => { + const makeFormattedData = (CoinsSelected: string[], dataTotalVolume: TotalVolume[]): [MergedData[], string[]] => { const map = new Map(); - const uniqueTopCoins = new Set(); + const uniqueCoins = new Set(); let cumulative = 0; dataTotalVolume.forEach((item: TotalVolume) => { @@ -78,43 +83,40 @@ export default function TotalVolumeChart() { key !== 'total' && key !== 'cumulative' && key !== 'other' && - key !== 'unit' + key !== 'unit' && + key !== 'Other' ); - const sortedCoinEntries = coinEntries.sort( - (a, b) => Math.abs(Number(b[1])) - Math.abs(Number(a[1])) - ); - const topCoins = sortedCoinEntries.slice(0, 10).map(([coin]) => coin); - const otherCoins = sortedCoinEntries.slice(10); + const otherCoins = coinEntries.filter(([coin]) => (!(CoinsSelected.includes(coin))) && (coin !== "all")); - topCoins.forEach((coin) => uniqueTopCoins.add(coin)); + coinEntries.forEach(([coin]) => uniqueCoins.add(coin)); let otherTotal = 0; - otherCoins.forEach(([coin, value]) => { + otherCoins.forEach(([_, value]) => { otherTotal += value; - delete entry[coin]; }); entry.Other = otherTotal; }); const result = Array.from(map.values()); - uniqueTopCoins.add('Other'); - return [result, Array.from(uniqueTopCoins)]; + return [result, Array.from(uniqueCoins)]; }; - const formatData = () => { - const [newFormattedData, coins] = makeFormattedData(dataTotalVolume); + const formatData = (CoinsSelected: string[]) => { + const [newFormattedData, coins] = makeFormattedData(CoinsSelected, dataTotalVolume); setCoins(coins); setFormattedData(newFormattedData); }; useEffect(() => { if (!loading && !error) { - formatData(); + formatData(coinsSelected); } }, [loading, error]); + const coinSelectors = createCoinSelectorsWithFormatArg(coins, coinsSelected, setCoinsSelected, formatData); + return ( - + @@ -142,7 +144,7 @@ export default function TotalVolumeChart() { tick={{ fill: '#f9f9f9', fontSize: isMobile ? 14 : 15 }} /> - {coins.map((coin, i) => { + {coinsSelected.map((coin, i) => { return ( diff --git a/constants/tokens.ts b/constants/tokens.ts index d42bcc4..8da4f1a 100644 --- a/constants/tokens.ts +++ b/constants/tokens.ts @@ -1,37 +1,18 @@ -const TOKEN_COLORS: any = { - BNB: '#ebc509', - BTC: '#F2A900', - DOGE: '#cb9800', - ETH: '#8A94B1', - INJ: '#0386FA', - KPEPE: '#509844', - MATIC: '#8C35D5', - Other: '#BBBAC6', - SOL: '#C867F0', - AVAX: '#e74242', - LTC: '#CCCCCC', - ARB: '#FCA100', - LINK: '#81D2FD', - APE: '#4087BE', - ATOM: '#FCA100', - CFX: '#F3654E', - CRV: '#850087', - DYDX: '#BE586C', - FTM: '#568EC0', - GMX: '#59C782', - LDO: '#DB6ED7', - OP: '#7F0182', - RNDR: '#FFA300', - SNX: '#498548', - STX: '#578374', - SUI: '#6A807A', -}; +import * as CryptoJS from 'crypto-js'; -export const getTokenHex = (token: string) => { - const symbol = token.toUpperCase(); - if (TOKEN_COLORS[symbol]) { - return TOKEN_COLORS[symbol]; - } else { - return 'pink'; - } -}; + +export const initialTokensSelected = ['ETH', 'BTC', 'ARB', 'APE', 'ATOM', 'AVAX', 'BNB', 'COMP', 'CRV', 'DOGE']; +export const initialTokensSelectedWithOther = ['ETH', 'BTC', 'ARB', 'APE', 'ATOM', 'AVAX', 'BNB', 'COMP', 'CRV', 'DOGE','Other']; + +export function getTokenColor(inputString: string): string { + if (inputString == "Other") { + return "pink"; + } + // Use the CryptoJS library to get the MD5 hash of the string + let hash = CryptoJS.MD5("col"+inputString); + + // Convert the hash into a hex string + let color = hash.toString(CryptoJS.enc.Hex).substr(0, 6); + + return "#" + color; +} diff --git a/helpers/utils.ts b/helpers/utils.ts new file mode 100644 index 0000000..5228178 --- /dev/null +++ b/helpers/utils.ts @@ -0,0 +1,52 @@ +import { SetStateAction } from 'react'; +import { CoinSelector } from '../components/common/chartWrapper'; + +const coinSelectorsSort = (a: CoinSelector, b: CoinSelector) => { + if (a.isChecked !== b.isChecked) { + return a.isChecked ? -1 : 1; + } + return a.name.localeCompare(b.name); + }; + + +export const createCoinSelectors = (coinKeys: string[], coinsSelected: string[], setCoinsSelected: (arg: string[]) => any, formatData: () => any) => { + return coinKeys.map((coinKey: string) => { + return { + name: coinKey, + event: () => { + let newCoinsSelected = coinsSelected; + if (coinsSelected.includes(coinKey)) { + newCoinsSelected = coinsSelected.filter((e) => { + return e !== coinKey; + }); + } else { + newCoinsSelected.push(coinKey); + } + formatData(); + setCoinsSelected(newCoinsSelected); + }, + isChecked: coinsSelected.includes(coinKey), + }; + }).sort((a: CoinSelector, b: CoinSelector) => coinSelectorsSort(a, b)); +} + +export const createCoinSelectorsWithFormatArg = (coinKeys: string[], coinsSelected: string[], setCoinsSelected: (arg: string[]) => any, formatData: (arg: string[]) => any) => { + return coinKeys.map((coinKey: string) => { + return { + name: coinKey, + event: () => { + let newCoinsSelected = coinsSelected; + if (coinsSelected.includes(coinKey)) { + newCoinsSelected = coinsSelected.filter((e) => { + return e !== coinKey; + }); + } else { + newCoinsSelected.push(coinKey); + } + formatData(newCoinsSelected); + setCoinsSelected(newCoinsSelected); + }, + isChecked: coinsSelected.includes(coinKey), + }; + }).sort((a: CoinSelector, b: CoinSelector) => coinSelectorsSort(a, b)); +} \ No newline at end of file diff --git a/hooks/isMobile.ts b/hooks/isMobile.ts new file mode 100644 index 0000000..f9cdffa --- /dev/null +++ b/hooks/isMobile.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from 'react'; + +export function useIsMobile() { + if (typeof window === "undefined") { + return [false]; + } + let [isMobile, setIsMobile] = useState(window.innerWidth < 700); + useEffect(() => { + setIsMobile(window.innerWidth < 700); + }, [window.innerWidth]); + + return [isMobile]; +} diff --git a/package-lock.json b/package-lock.json index ed29c88..48aec2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,13 @@ "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@svgr/webpack": "^8.0.1", + "@types/crypto-js": "^4.1.1", "@types/node": "20.2.5", "@types/react": "18.2.7", "@types/react-dom": "18.2.4", "@types/strftime": "^0.9.4", "axios": "^1.4.0", + "crypto-js": "^4.1.1", "eslint": "8.41.0", "eslint-config-next": "13.4.4", "ethers": "^5.7.2", @@ -4400,6 +4402,11 @@ "integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==", "dev": true }, + "node_modules/@types/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==" + }, "node_modules/@types/d3-array": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz", @@ -5246,6 +5253,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "node_modules/css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -12389,6 +12401,11 @@ "integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==", "dev": true }, + "@types/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==" + }, "@types/d3-array": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz", @@ -13028,6 +13045,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", diff --git a/package.json b/package.json index e056236..5f9ddb2 100644 --- a/package.json +++ b/package.json @@ -63,11 +63,13 @@ "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@svgr/webpack": "^8.0.1", + "@types/crypto-js": "^4.1.1", "@types/node": "20.2.5", "@types/react": "18.2.7", "@types/react-dom": "18.2.4", "@types/strftime": "^0.9.4", "axios": "^1.4.0", + "crypto-js": "^4.1.1", "eslint": "8.41.0", "eslint-config-next": "13.4.4", "ethers": "^5.7.2",