diff --git a/package.json b/package.json index 96e8e0e..76de404 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/react-fontawesome": "^0.2.0", "@reduxjs/toolkit": "^1.8.6", + "@types/lodash": "^4.14.191", "apexcharts": "^3.36.0", "autoprefixer": "^10.4.13", "axios": "^1.1.3", "bootstrap": "^5.2.2", "cookies-next": "^2.1.1", "dayjs": "^1.11.6", + "lodash": "^4.17.21", "next": "12.3.1", "nextjs-google-analytics": "^2.2.1", "react": "17.0.2", diff --git a/pages/index.tsx b/pages/index.tsx index 080d86a..0cb32c2 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -7,8 +7,11 @@ import { TvlUnit } from 'store/reducers/tvl-evolution.slice' import { formatCurrency } from 'utils/helpers/format' import Loader from 'components/Loader' import DataEvolution from 'components/DataEvolution' -import { getTokensValue } from 'utils/helpers/tokens' +import { getTokensValue, tokens } from 'utils/helpers/tokens' import { TokenPricesState } from 'store/reducers/tokens-prices.slice' +import { AreaChart } from 'components/Charts/AreaChart' +import { periodOptions } from 'types' +import { ChainComparisonTVL } from 'components/Tables/ChainComparisonTVL' const Home = () => { const tvlUnits = useSelector(state => state.tvlEvolution.data) @@ -18,6 +21,9 @@ const Home = () => { const fetchingData = useSelector(state => state.tvlEvolution.loading) const loading = fetchingData || tvlUnits.length === 0 || tvlUnitsInDollars.length === 0 + + const formattedData = useMemo(() => tokens.map((token) => ({ data: tvlUnitsInDollars.map(unit => [unit.day.getTime(), unit[token.toLowerCase() as 'eth']]), name: token })), [tvlUnitsInDollars]) + const content = ( <>
@@ -29,52 +35,12 @@ const Home = () => {
-
-
-
TVL Evolution (USD)
-
-
- {/* serie[dataPointIndex]).reduce((a: number, b: number) => a + b) - output = output + ' (Total: ' + formatCurrency(total) + ')' - } - return output - } - } - }, - fill: { - type: 'none' - } - }} - series={formattedData} - formatter={(value) => formatCurrency(value, 0)} - /> */} -
+
+ formatCurrency(value, 0)} dateOptions={periodOptions} displayDateSelector={true} /> + formatCurrency(value, 0)} dateOptions={periodOptions} displayDateSelector={true} />
-

TVL Ranking

+ +

Assets ranking

) diff --git a/src/components/Button/DateButton.tsx b/src/components/Button/DateButton.tsx new file mode 100644 index 0000000..083c2b9 --- /dev/null +++ b/src/components/Button/DateButton.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react' + +export interface DateButtonProps { + value: { + value: string; + originalValue: string; + }; + isSelected: boolean; + onClick: (value: string) => void; +} + +export const DateButton: FC = ({ value, isSelected, onClick }) => { + return ( +
onClick(value.originalValue)} + className={`flex flex-row justify-center items-center px-2.5 py-1 cursor-pointer rounded ${ + isSelected ? 'bg-slate-50 rounded' : 'data-button-not-selected' + }`} + > +
{value.value}
+
+ ) +} diff --git a/src/components/Charts/AreaChart.tsx b/src/components/Charts/AreaChart.tsx new file mode 100644 index 0000000..94bfab9 --- /dev/null +++ b/src/components/Charts/AreaChart.tsx @@ -0,0 +1,108 @@ +import React, { FC, useCallback, useState } from 'react' +import Chart, { Series } from '.' +import { ApexOptions } from 'apexcharts' +import { DateButton } from 'components/Button/DateButton' +import { Period } from 'types' + +interface AreaChartProps { + title: string; + series: Series[]; + formatter: (value: number) => string; + displayDateSelector?: boolean; + dateOptions?: { + value: string; + originalValue: string; + }[]; +} + +export const AreaChart: FC = ({ title, series, formatter, displayDateSelector, dateOptions }) => { + const [period, setPeriod] = useState(Period.SEMESTER) + + const options = { + colors: ['#5B61D5'], + xaxis: { + show: false, + labels: { + show: false + }, + axisBorder: { + show: false + }, + axisTicks: { + show: false + } + }, + yaxis: { + show: true, + labels: { + show: true, + align: 'right', + minWidth: 0, + maxWidth: 0, + style: { + colors: ['white'], + fontWeight: 400, + fontSize: 15, + fontFamily: 'DM Sans, sans-serif' + }, + offsetX: 60, + formatter + } + }, + grid: { + show: false, + padding: { + bottom: -30, + right: -30, + left: -10 + } + }, + stroke: { + show: true, + curve: 'straight', + width: 1.5 + }, + fill: { + colors: ['#2B355B'], + type: 'gradient', + gradient: { + type: 'vertical', + gradientToColors: ['#5A5FCC', '#2B355B', '#191C34'], + opacityFrom: 0.9, + opacityTo: 0 + }, + pattern: { + style: 'verticalLines', + width: 6, + height: 6, + strokeWidth: 2 + } + } + } + + const handleOnChangeDate = useCallback( + (value: string) => { + setPeriod(value) + }, + [setPeriod] + ) + + return ( +
+
+
{title}
+
+ + {displayDateSelector && dateOptions && ( +
+ {dateOptions.map(option => { + const isSelected = option.originalValue === period + return ( + + ) + })} +
+ )} +
+ ) +} diff --git a/src/components/Charts/PieChart.tsx b/src/components/Charts/PieChart.tsx new file mode 100644 index 0000000..513a30c --- /dev/null +++ b/src/components/Charts/PieChart.tsx @@ -0,0 +1,62 @@ +import { ApexOptions } from 'apexcharts' +import React, { FC } from 'react' +import { Chain } from 'types/chain' +import { getColorForChain } from 'utils/helpers/getColorForChain' +import Chart, { DonutSeries } from '.' + +interface PieChartProps { + series: DonutSeries; + formatter: (value: number) => string; + customOptions?: ApexOptions, +} + +export const PieChart: FC = ({ series, formatter, customOptions }) => { + const baseOptions: ApexOptions = { + chart: { + type: 'donut' + }, + legend: { + show: false + }, + fill: { + type: 'solid', + opacity: 1 + }, + stroke: { + show: false + }, + tooltip: { + enabled: true, + custom: ({ series, seriesIndex, _dataPointIndex, w }) => { + const { labels } = w.config + const chain = labels[seriesIndex] + const textColor = chain === Chain.ZkSync ? 'black' : 'white' + const value = formatter + ? formatter(series[seriesIndex]) + : series[seriesIndex] + return '
' + + '' + `${chain}: ${value}` + '' + + '
' + + '' + } + }, + ...customOptions + } + return ( + + ) +} diff --git a/src/components/Charts/index.tsx b/src/components/Charts/index.tsx index 90976ee..8e983cf 100644 --- a/src/components/Charts/index.tsx +++ b/src/components/Charts/index.tsx @@ -3,15 +3,22 @@ import dynamic from 'next/dynamic' import { ApexOptions } from 'apexcharts' import { baseChartOptions } from 'utils/shared' -interface Props { - series: { - name: string, - data: number[][] - }[], +export interface Series { + name: string; + data: number[][]; +} + +export type DonutSeries = number[]; + +interface ChartProps { + series: Series[] | DonutSeries, customOptions?: ApexOptions, formatter: (value: number) => string, + chartType?: 'bar' | 'area' | 'donut', + height?: number, + width?: number, } -const Chart: React.FC = ({ series, formatter, customOptions }: Props) => { +const Chart: React.FC = ({ series, formatter, customOptions, chartType = 'bar', height = 400, width }) => { const ApexCharts = dynamic(() => import('react-apexcharts'), { ssr: false }) const options = { @@ -33,8 +40,9 @@ const Chart: React.FC = ({ series, formatter, customOptions }: Props) => options={options.options as ApexOptions} // eslint-disable-next-line @typescript-eslint/no-explicit-any series={options.series as any} - type="bar" - height="400" + type={chartType} + height={height} + width={width} /> ) } diff --git a/src/components/DataBlock.tsx b/src/components/DataBlock.tsx index 47e3985..4b57bee 100644 --- a/src/components/DataBlock.tsx +++ b/src/components/DataBlock.tsx @@ -9,7 +9,7 @@ interface Props { } const DataBlock: React.FC = ({ color, classes = '', title, data, mobileTitle }: Props) => { return ( -
+

{title}

{mobileTitle || title}

{data}

diff --git a/src/components/Selector/Selector.tsx b/src/components/Selector/Selector.tsx new file mode 100644 index 0000000..0c081d2 --- /dev/null +++ b/src/components/Selector/Selector.tsx @@ -0,0 +1,37 @@ +import React, { FC, ReactNode } from 'react' + +interface SelectorProps { + options: { + value: string; + originalValue: string; + }[]; + currentOption: string; + onChange: (option: string) => void; + icon?: ReactNode; +} + +export const Selector: FC = ({ options, currentOption, onChange, icon }) => { + return ( +
+ {options.map(option => { + const isSelected = option.originalValue === currentOption + return ( +
onChange(option.originalValue)} + className={`flex flex-row justify-center items-center px-2.5 py-2 cursor-pointer ${ + isSelected ? 'purple-gradient rounded' : 'hover:bg-white hover:bg-opacity-10' + }`} + > +
{option.value}
+
+ ) + })} + {icon && ( +
+ {icon} +
+ )} +
+ ) +} diff --git a/src/components/Tables/ChainComparisonTVL.tsx b/src/components/Tables/ChainComparisonTVL.tsx new file mode 100644 index 0000000..2cf0e57 --- /dev/null +++ b/src/components/Tables/ChainComparisonTVL.tsx @@ -0,0 +1,73 @@ +import { ApexOptions } from 'apexcharts' +import { PieChart } from 'components/Charts/PieChart' +import { orderBy, sumBy } from 'lodash' +import React, { FC } from 'react' +import { Chain } from 'types/chain' +import { formatCurrency } from 'utils/helpers/format' +import { getColorForChain } from 'utils/helpers/getColorForChain' +import * as chainData from '../../mock/chainComparisonData.json' + +export const ChainComparisonTVL: FC = () => { + const totalTVL = sumBy(chainData, 'TvlInUsd') + const tvlPerChain = chainData.map(chain => { + const tvlPercent = (chain.TvlInUsd / totalTVL) * 100 + return { + ...chain, + percentShare: tvlPercent.toFixed(0) + } + }) + + const orderedTvlPerChain = orderBy(tvlPerChain, 'TvlInUsd', 'desc') + const chainDataChart = tvlPerChain.map(chain => chain.TvlInUsd) + + const pieChartOptions: ApexOptions = { + labels: [ + ...orderedTvlPerChain.map(chain => chain.chain) + ], + colors: [ + ...orderedTvlPerChain.map(chain => getColorForChain(chain.chain as Chain)) + ] + } + + return ( +
+
+
+
+
{"StarkNet's TVL vs other L2"}
+
+
+ + + + + + + + + {tvlPerChain.map(chain => ( + + + + + + ))} + +
+
+ {chain.chain} + {chain.chain} +
+
{chain.percentShare}%{formatCurrency(chain.TvlInUsd, 0)}
+
+ +
+
+
+ formatCurrency(value, 0)} /> +
+
+
+ + ) +} diff --git a/src/components/Tables/TVL.tsx b/src/components/Tables/TVL.tsx index c7df995..18fb123 100644 --- a/src/components/Tables/TVL.tsx +++ b/src/components/Tables/TVL.tsx @@ -1,12 +1,26 @@ +import { faCalendarDays } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Selector } from 'components/Selector/Selector' +import { sum } from 'lodash' import Image from 'next/image' -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useSelector } from 'react-redux' import { TokenPricesState } from 'store/reducers/tokens-prices.slice' import { TvlUnit } from 'store/reducers/tvl-evolution.slice' import { RootState } from 'store/store' -import { formatCurrency, formatValue } from 'utils/helpers/format' +import { AppType, appTypeOptions, Period, periodOptions } from 'types' +import { formatCurrency } from 'utils/helpers/format' import { getTokensValue } from 'utils/helpers/tokens' +interface Token { + name: string + amount: number + change: number + currentTvl: number + previousTvl: number + tvlPercentage: number +} + const tokensTypes = new Map() tokensTypes.set('eth', 'Layer 1') tokensTypes.set('dai', 'Stablecoin') @@ -18,7 +32,12 @@ tokensTypes.set('strk', 'Layer 2') const TvlTable = () => { const tvlUnits = useSelector(state => state.tvlEvolution.data) const prices = useSelector(state => state.tokensPrices) - const [orderedTokens, setOrderedTokens] = useState<{ currentTvl: number, previousTvl: number, change: number, name: string, amount: number }[]>([]) + const [orderedTokens, setOrderedTokens] = useState([]) + const totalTVL = useMemo(() => { + return sum(tvlUnits.map(tvlUnit => tvlUnit.total)) + }, [tvlUnits]) + const [appType, setAppType] = useState(AppType.ALL) + const [period, setPeriod] = useState(Period.SEMESTER) useEffect(() => { if (tvlUnits.length > 2) { @@ -32,48 +51,67 @@ const TvlTable = () => { previousTvl, name: token, amount: tvlUnits[tvlUnits.length - 1][token as 'eth'], - change: ((currentTvl - previousTvl) / previousTvl) * 100 + change: ((currentTvl - previousTvl) / previousTvl) * 100, + tvlPercentage: ((currentTvl / totalTVL) * 100) * 100 }) } setOrderedTokens(orderedTokens.sort((a, b) => (b.currentTvl - a.currentTvl))) } }, [tvlUnits, prices]) + + const handleAppTypeChange = useCallback( + (option: string) => { + setAppType(option) + }, + [setAppType] + ) + + const handlePeriodChange = useCallback( + (option: string) => { + setPeriod(option) + }, + [setPeriod] + ) + return ( -
-
- +
+
+ + } /> +
+
+
- - - + + - - + { orderedTokens.map((token, index) => ( - - - + - - + )) } diff --git a/src/mock/chainComparisonData.json b/src/mock/chainComparisonData.json new file mode 100644 index 0000000..704be1a --- /dev/null +++ b/src/mock/chainComparisonData.json @@ -0,0 +1,32 @@ +[ + { + "chain": "Polygon", + "logoURL": "https://www.creativefabrica.com/wp-content/uploads/2021/06/16/Cryptocurrency-Polygon-Logo-Graphics-13459548-1.jpg", + "TvlInUsd": 320000000 + }, + { + "chain": "Arbitrum", + "logoURL": "https://assets.coingecko.com/nft_contracts/images/1822/small/https-fanbase-1-s3-amazonaws-com-quixotic-collection-profile-046qixwt_400x400-jpeg.jpg?1664347649", + "TvlInUsd": 280000000 + }, + { + "chain": "ZkSync", + "logoURL": "https://styles.redditmedia.com/t5_30djbl/styles/communityIcon_8u7bhy7ruyk71.png?width=256&s=e78984e5d79e199527131d760c9c9620a6b26e80", + "TvlInUsd": 230000000 + }, + { + "chain": "Optimistic", + "logoURL": "https://cdn-images-1.medium.com/max/1200/1*6zQEwahVQVEPk4QJpKGShw.png", + "TvlInUsd": 190000000 + }, + { + "chain": "Starknet", + "logoURL": "https://starkware.co/wp-content/uploads/2021/05/StarkNet-Icon.png", + "TvlInUsd": 180000000 + }, + { + "chain": "Other", + "logoURL": "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/c0840e59-db43-4681-ae7b-31a04dc4bc55/d7eqdvw-4e97ac92-e4b9-4498-9655-e4d612eb478b.png/v1/fill/w_1600,h_900,strp/random_logo_by_criticl_d7eqdvw-fullview.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9OTAwIiwicGF0aCI6IlwvZlwvYzA4NDBlNTktZGI0My00NjgxLWFlN2ItMzFhMDRkYzRiYzU1XC9kN2VxZHZ3LTRlOTdhYzkyLWU0YjktNDQ5OC05NjU1LWU0ZDYxMmViNDc4Yi5wbmciLCJ3aWR0aCI6Ijw9MTYwMCJ9XV0sImF1ZCI6WyJ1cm46c2VydmljZTppbWFnZS5vcGVyYXRpb25zIl19.X991O1jF5lTNZbbEoHEfoo6nlHEihBMHMIm5-uBCXcU", + "TvlInUsd": 32000000 + } +] \ No newline at end of file diff --git a/src/store/store.ts b/src/store/store.ts index a8ad382..0eb3865 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -17,7 +17,7 @@ export interface AppState { tvlEvolution: typeof tvlEvolutionSlice; tokensPrices: typeof tokensPricesSlice; metrics: typeof metricsSlice; - volue: typeof volumeSlice; + volume: typeof volumeSlice; topUsers: typeof topUsersSlice; ecosystem: typeof ecosystemSlice; } diff --git a/src/types/app.ts b/src/types/app.ts new file mode 100644 index 0000000..abda135 --- /dev/null +++ b/src/types/app.ts @@ -0,0 +1,16 @@ +export enum AppType { + ALL = 'ALL', + DAPP = 'DAPP', + LAYER2 = 'LAYER2' +} + +export const appTypeMappingToName: Record = { + [AppType.ALL]: 'All', + [AppType.DAPP]: 'Dapps', + [AppType.LAYER2]: 'Layer 2' +} + +export const appTypeOptions = Object.keys(AppType).map(key => ({ + value: appTypeMappingToName[key as AppType], + originalValue: key as AppType +})) diff --git a/src/types/chain.ts b/src/types/chain.ts new file mode 100644 index 0000000..7388c72 --- /dev/null +++ b/src/types/chain.ts @@ -0,0 +1,17 @@ +export enum Chain { + Polygon = 'Polygon', + Starknet = 'Starknet', + Arbitrum = 'Arbitrum', + Optimistic = 'Optimistic', + ZkSync = 'ZkSync', + Other = 'Other' +} + +export const chainColorsMapping: Record = { + [Chain.Polygon]: '#8247E5', + [Chain.Starknet]: '#29296E', + [Chain.Arbitrum]: '#2C374B', + [Chain.Optimistic]: '#FF0420', + [Chain.ZkSync]: '#F3F6FF', + [Chain.Other]: '#3A3939' +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..bf1262a --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './period' +export * from './app' diff --git a/src/types/period.ts b/src/types/period.ts new file mode 100644 index 0000000..54676bd --- /dev/null +++ b/src/types/period.ts @@ -0,0 +1,20 @@ +export enum Period { + WEEKLY = 'WEEKLY', + MONTHLY = 'MONTHLY', + TRIMESTER = 'TRIMESTER', + SEMESTER = 'SEMESTER', + YEARLY = 'YEARLY' +} + +export const periodMappingToDay: Record = { + [Period.WEEKLY]: '7D', + [Period.MONTHLY]: '30D', + [Period.TRIMESTER]: '90D', + [Period.SEMESTER]: '180D', + [Period.YEARLY]: '365D' +} + +export const periodOptions = Object.keys(Period).map(key => ({ + value: periodMappingToDay[key as Period], + originalValue: key as Period +})) diff --git a/src/utils/charts/formatter.ts b/src/utils/charts/formatter.ts new file mode 100644 index 0000000..5aa9ab3 --- /dev/null +++ b/src/utils/charts/formatter.ts @@ -0,0 +1,15 @@ +import { formatCurrency } from 'utils/helpers/format' + +interface FormatterOptions { + series: number[][]; + dataPointIndex: number; +} + +export const formatter = (value: number, { series, dataPointIndex } : FormatterOptions) => { + let output = new Intl.DateTimeFormat('en', { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(value)) + if (series) { + const total = series.map((serie: number[]) => serie[dataPointIndex]).reduce((a: number, b: number) => a + b) + output = output + ' (Total: ' + formatCurrency(total) + ')' + } + return output +} diff --git a/src/utils/helpers/getColorForChain.ts b/src/utils/helpers/getColorForChain.ts new file mode 100644 index 0000000..149738d --- /dev/null +++ b/src/utils/helpers/getColorForChain.ts @@ -0,0 +1,5 @@ +import { Chain, chainColorsMapping } from 'types/chain' + +export const getColorForChain = (chain: Chain): string => { + return chainColorsMapping[chain] +} diff --git a/styles/styles.css b/styles/styles.css index 15f0b51..e811610 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -265,6 +265,10 @@ ul { } +.purple-gradient { + background: linear-gradient(180deg, #8382BF, #6D72F4) +} + .navigation { list-style-type: none; padding: 20px; @@ -324,7 +328,7 @@ ul { .page-title { color: white; font-size: 30px; - font-weight: 400; + font-weight: 700; font-family: 'DM Sans', sans-serif; } @@ -380,15 +384,16 @@ ul { } .table-container { - background-color: #423E52 !important; + background-color: #323D68 !important; + border-radius: 10px !important; } .data-table thead tr { - border-bottom: 1px solid #423E52 !important; + border-bottom: 1px solid #323D68 !important; } .data-table tbody tr:not(:last-child) { - border-bottom: 1px solid #423E52 !important; + border-bottom: 1px solid #323D68 !important; } .data-table tbody td { @@ -686,7 +691,6 @@ ul { background: -webkit-linear-gradient(#02C1FE, #023693); -webkit-text-fill-color: transparent; -webkit-background-clip: text; - } .ecosystem-filters .text-input { @@ -702,6 +706,54 @@ ul { .tvl-table th { padding: 20px 20px !important; + font-weight: 700; + font-family: 'DM Sans', sans-serif; + font-size: 16px; +} + +.selector-border { + border: 1px solid #323D68; +} + +.selector-text { + color: #B7B7B9; + font-size: 12px; font-weight: 400; font-family: 'DM Sans', sans-serif; -} \ No newline at end of file +} + +.data-button-not-selected { + background: radial-gradient(88.99% 94.44% at 92.31% 100%, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0) 100%); + backdrop-filter: blur(5.21px); +} + +.chain-comparison-container { + background: linear-gradient(180deg, rgba(91, 98, 212, 0.1) 0%, rgba(26, 29, 53, 1) 100%); + border-top: 1.07652px solid rgba(255, 255, 255, 0.08); + border-left: 1.07652px solid rgba(255, 255, 255, 0.08); + box-shadow: 0px 4.30608px 4.30608px rgba(0, 0, 0, 0.25); +} + +.chain-comparison-circle { + position: absolute; + background: radial-gradient(100% 100% at 0% 0%, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0) 100%); + border-radius: 50%; + backdrop-filter: blur(63.4312px); + height: 700px; + width: 700px; + top: -33%; +} + +.chain-comparison-logo { + width: 30px; + height: 30px; + border-radius: 100%; +} + +.chain-comparison-table td { + font-weight: 700; + font-family: 'DM Sans', sans-serif; + font-size: 16px; + border: none !important; +} + diff --git a/yarn.lock b/yarn.lock index c0d2958..14a7945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -227,6 +227,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.191": + version "4.14.191" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== + "@types/node@18.11.4": version "18.11.4" resolved "https://registry.npmjs.org/@types/node/-/node-18.11.4.tgz" @@ -1319,9 +1324,9 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: @@ -1754,6 +1759,11 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -1876,6 +1886,13 @@ next@12.3.1: "@next/swc-win32-ia32-msvc" "12.3.1" "@next/swc-win32-x64-msvc" "12.3.1" +nextjs-google-analytics@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/nextjs-google-analytics/-/nextjs-google-analytics-2.2.1.tgz#ebad0414aec2dca9879281cd446b02340347c6f8" + integrity sha512-b5a7V+iWdrrcB2kQxcW9KYHYwtJJoY50a4E+b68TUba3YqTmwufs0u2HfPNxd1MauPmkxTBSx2IyaSUdgxLJ7Q== + optionalDependencies: + fsevents "^2.3.2" + node-releases@^2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" @@ -1886,13 +1903,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nextjs-google-analytics@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/nextjs-google-analytics/-/nextjs-google-analytics-2.2.1.tgz#ebad0414aec2dca9879281cd446b02340347c6f8" - integrity sha512-b5a7V+iWdrrcB2kQxcW9KYHYwtJJoY50a4E+b68TUba3YqTmwufs0u2HfPNxd1MauPmkxTBSx2IyaSUdgxLJ7Q== - optionalDependencies: - fsevents "^2.3.2" - normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
AssetsNameCategoryAssetsPercentage 24h ChangeTVLAmountTVL
+
{index + 1}
-
+
{`${token.name} + + {token.name.toUpperCase()} +
{token.name.toUpperCase()}{tokensTypes.get(token.name)}{`${token.tvlPercentage.toFixed(0)} %`} 0 ? '#03D9A5' : '#DE365E' }}> - {isNaN(token.change) ? '0' : token.change.toFixed(2)}% + {isNaN(token.change) ? '0' : token.change.toFixed(1)}% {formatCurrency(token.currentTvl)}{formatValue(token.amount)}{formatCurrency(token.currentTvl, 0)}