From d02dee86f6a554af21a76d92cb473707ae510c8f Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Wed, 12 Oct 2022 19:58:52 +0100 Subject: [PATCH 1/5] Add top metrics page --- src/Routes.tsx | 5 + src/api/client.ts | 18 ++-- src/components/appHeader/ToolsButton.tsx | 9 ++ src/pages/TopStatsPage/CommonTopTable.tsx | 124 ++++++++++++++++++++++ src/pages/TopStatsPage/Transaction.tsx | 63 +++++++++++ src/pages/TopStatsPage/index.tsx | 26 +++++ src/types/api.ts | 17 +++ 7 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 src/pages/TopStatsPage/CommonTopTable.tsx create mode 100644 src/pages/TopStatsPage/Transaction.tsx create mode 100644 src/pages/TopStatsPage/index.tsx diff --git a/src/Routes.tsx b/src/Routes.tsx index 7a90529..6519617 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -17,6 +17,7 @@ import { ApprovalPage } from "./pages/ApprovalPage"; import { CheckHRC } from "./pages/tools/CheckHRC"; import {VerifyProxyContract} from "./pages/VerifyProxyContract/VerifyProxyContract"; import { ChartsPage } from "./pages/ChartsPage"; +import { TopStatsPage } from "./pages/TopStatsPage"; export function Routes() { return ( @@ -108,6 +109,10 @@ export function Routes() { + + + + diff --git a/src/api/client.ts b/src/api/client.ts index 32ac441..a7eb362 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,12 +1,12 @@ import { transport } from "./explorer"; import { - Block, - InternalTransaction, - RPCStakingTransactionHarmony, - RPCTransactionHarmony, - RelatedTransaction, - Log, LogDetailed, AddressDetails, - ShardID, MetricsType, MetricsDailyItem + Block, + InternalTransaction, + RPCStakingTransactionHarmony, + RPCTransactionHarmony, + RelatedTransaction, + Log, LogDetailed, AddressDetails, + ShardID, MetricsType, MetricsDailyItem, MetricsTopType, MetricsTopItem } from "src/types"; import { IHoldersInfo, @@ -115,6 +115,10 @@ export function getMetricsByType(type: MetricsType, offset = 0, limit = 14) { return transport("getMetricsByType", [type, offset, limit]) as Promise; } +export function getTopMetricsByType(type: MetricsTopType, period: 1 | 3 | 7, limit = 10) { + return transport("getTopMetricsByType", [type, period, limit]) as Promise; +} + export function getContractsByField(params: any[]) { return transport("getContractsByField", params) as Promise; } diff --git a/src/components/appHeader/ToolsButton.tsx b/src/components/appHeader/ToolsButton.tsx index 9b1e26c..f32b18a 100644 --- a/src/components/appHeader/ToolsButton.tsx +++ b/src/components/appHeader/ToolsButton.tsx @@ -73,6 +73,15 @@ export function ToolsButton() { (new) + { + setIsOpen(false); + history.push("/topstat"); + }} + > + Top Statistics + } style={{ diff --git a/src/pages/TopStatsPage/CommonTopTable.tsx b/src/pages/TopStatsPage/CommonTopTable.tsx new file mode 100644 index 0000000..458cbb9 --- /dev/null +++ b/src/pages/TopStatsPage/CommonTopTable.tsx @@ -0,0 +1,124 @@ +import React from 'react' +import {Box, Text} from "grommet"; +import {MetricsTopItem, MetricsTopType} from "../../types"; +import styled from "styled-components"; +import {Address} from "../../components/ui"; +import {ReactComponent as HarmonyLogo} from '../../assets/Logo.svg'; + + +export interface TopTableProps { + items: MetricsTopItem[] + title: string + columns: string[] +} + +interface TopTableRowProps { + item: MetricsTopItem +} + +const TableContainer = styled(Box)` + flex: 0 0 calc(50% - 16px); + margin-left: 8px; + margin-right: 8px; + + @media (max-width: 1024px) { + flex: 0 0 calc(50% - 16px); + margin-bottom: 0.75rem; + } + + @media (max-width: 768px) { + flex: 0 0 calc(100% - 16px); + margin-top: 0.75rem; + margin-bottom: 0.75rem; + } +` + +const LogoWrapper = styled(Box)` + svg path { + fill: #00AEE9 + } +` + +const columnsWidth = ['5%', '65%', '15%', '10%'] + +const TopTableHeader = (props: { columns: string[] }) => { + const { columns } = props + return + + {columns[0]} + + + {columns[1]} + + + {columns[2]} + + + {columns[3]} + + +} + +const TopTableRow = (props: TopTableRowProps) => { + const { item: { type, rank, address, value, share } } = props + + const isOneTransfer = [MetricsTopType.topOneSender, MetricsTopType.topOneReceiver].includes(type) + const valueFormat = isOneTransfer + ? Math.round(+value / Math.pow(10, 18)) + : value + + const valueFormatEn = Intl.NumberFormat('en-US', { + notation: "compact", + maximumFractionDigits: 2 + }).format(+valueFormat) + + const shareFormatEn = Intl.NumberFormat('en-US', { + notation: "compact", + maximumFractionDigits: 2 + }).format(+share) + + return + + {rank} + + +
+ + + {isOneTransfer && + + } + {valueFormatEn} + + + {shareFormatEn}% + + +} + +export const TopTable = (props: TopTableProps) => { + return + + + {props.title} + + + {props.items.map(item => )} + + +} diff --git a/src/pages/TopStatsPage/Transaction.tsx b/src/pages/TopStatsPage/Transaction.tsx new file mode 100644 index 0000000..ca51384 --- /dev/null +++ b/src/pages/TopStatsPage/Transaction.tsx @@ -0,0 +1,63 @@ +import React, {useEffect, useState} from 'react' +import {Box} from "grommet"; +import {TopTable} from "./CommonTopTable"; +import {getTopMetricsByType} from "../../api/client"; +import {MetricsTopItem, MetricsTopType} from "../../types"; + +enum PeriodOption { + d1 = 1, + d3 = 3, + d7 = 7 +} + +export const TransactionTopStats = () => { + const [period, setPeriod] = useState(PeriodOption.d1) + const [oneSenders, setOneSenders] = useState([]) + const [oneReceivers, setOneReceivers] = useState([]) + const [txsSenders, setTxsSenders] = useState([]) + const [txsReceivers, setTxsReceivers] = useState([]) + + useEffect(() => { + const loadData = async () => { + const rowsOneSent = await getTopMetricsByType(MetricsTopType.topOneSender, 1) + const rowsOneReceive = await getTopMetricsByType(MetricsTopType.topOneReceiver, 1) + const rowsTxsSent = await getTopMetricsByType(MetricsTopType.topTxsCountSent, 1) + const rowsTxsReceived = await getTopMetricsByType(MetricsTopType.topTxsCountReceived, 1) + + setOneSenders(rowsOneSent) + setOneReceivers(rowsOneReceive) + setTxsSenders(rowsTxsSent) + setTxsReceivers(rowsTxsReceived) + } + loadData() + }, []) + + return + + + + + +} diff --git a/src/pages/TopStatsPage/index.tsx b/src/pages/TopStatsPage/index.tsx new file mode 100644 index 0000000..9944b42 --- /dev/null +++ b/src/pages/TopStatsPage/index.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import {Box, Heading, Text} from "grommet"; +import {BaseContainer, BasePage} from "../../components/ui"; +import {TransactionTopStats} from "./Transaction"; + +export const TopStatsPage = () => { +return + + Top Statistics + + + + Transactions + + + + + + +} diff --git a/src/types/api.ts b/src/types/api.ts index 7e96bfb..1c21e4a 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -57,3 +57,20 @@ export enum MetricsType { averageFee = 'average_fee', blockSize = 'block_size' } + +export enum MetricsTopType { + topOneSender = 'top_one_sender', + topOneReceiver = 'top_one_receiver', + topTxsCountSent = 'top_txs_count_sent', + topTxsCountReceived = 'top_txs_count_received', +} + +export interface MetricsTopItem { + type: MetricsTopType + address: string + period: number + rank: number + share: number + value: string + updatedAt: string +} From 7d1b49af4e193d8ecf774bdbbc74c3be05993d96 Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Wed, 12 Oct 2022 21:58:52 +0100 Subject: [PATCH 2/5] Add top metrics filter --- src/api/client.ts | 4 +- src/pages/TopStatsPage/CommonTopTable.tsx | 10 +- src/pages/TopStatsPage/OptionsSelect.tsx | 52 ++++++++++ src/pages/TopStatsPage/Transaction.tsx | 110 +++++++++++++--------- src/types/api.ts | 6 ++ 5 files changed, 132 insertions(+), 50 deletions(-) create mode 100644 src/pages/TopStatsPage/OptionsSelect.tsx diff --git a/src/api/client.ts b/src/api/client.ts index a7eb362..de3297d 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -6,7 +6,7 @@ import { RPCTransactionHarmony, RelatedTransaction, Log, LogDetailed, AddressDetails, - ShardID, MetricsType, MetricsDailyItem, MetricsTopType, MetricsTopItem + ShardID, MetricsType, MetricsDailyItem, MetricsTopType, MetricsTopItem, MetricsTopPeriod } from "src/types"; import { IHoldersInfo, @@ -115,7 +115,7 @@ export function getMetricsByType(type: MetricsType, offset = 0, limit = 14) { return transport("getMetricsByType", [type, offset, limit]) as Promise; } -export function getTopMetricsByType(type: MetricsTopType, period: 1 | 3 | 7, limit = 10) { +export function getTopMetricsByType(type: MetricsTopType, period: MetricsTopPeriod, limit = 10) { return transport("getTopMetricsByType", [type, period, limit]) as Promise; } diff --git a/src/pages/TopStatsPage/CommonTopTable.tsx b/src/pages/TopStatsPage/CommonTopTable.tsx index 458cbb9..75731ae 100644 --- a/src/pages/TopStatsPage/CommonTopTable.tsx +++ b/src/pages/TopStatsPage/CommonTopTable.tsx @@ -10,6 +10,7 @@ export interface TopTableProps { items: MetricsTopItem[] title: string columns: string[] + isLoading?: boolean } interface TopTableRowProps { @@ -20,9 +21,10 @@ const TableContainer = styled(Box)` flex: 0 0 calc(50% - 16px); margin-left: 8px; margin-right: 8px; + min-width: 500px; @media (max-width: 1024px) { - flex: 0 0 calc(50% - 16px); + flex: 0 0 calc(100% - 16px); margin-bottom: 0.75rem; } @@ -93,7 +95,7 @@ const TopTableRow = (props: TopTableRowProps) => { {rank} -
+
{isOneTransfer && @@ -112,10 +114,12 @@ export const TopTable = (props: TopTableProps) => { border={{ size: '1px' }} round={'8px'} overflow={'hidden'} + margin={{ bottom: '16px' }} + style={{ opacity: props.isLoading ? 0.5 : 1 }} > - {props.title} + {props.title} {props.items.map(item => )} diff --git a/src/pages/TopStatsPage/OptionsSelect.tsx b/src/pages/TopStatsPage/OptionsSelect.tsx new file mode 100644 index 0000000..e480e58 --- /dev/null +++ b/src/pages/TopStatsPage/OptionsSelect.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import {Box} from "grommet"; +import {MetricsTopPeriod} from "../../types"; + +const OptionAlias = { + [MetricsTopPeriod.d1]: '1 Day', + [MetricsTopPeriod.d3]: '3 Days', + [MetricsTopPeriod.d7]: '7 Days', +} + +export const ChartOptions = Object.values(MetricsTopPeriod).filter(v => typeof v === 'number') as MetricsTopPeriod[] + +const Option = (props: { value: MetricsTopPeriod, isActive: boolean, onSelect: (value: MetricsTopPeriod) => void }) => { + const {value, isActive, onSelect} = props + return onSelect(value)} + style={{ cursor: 'pointer', fontWeight: isActive ? 'bold': 'normal' }} + > + {OptionAlias[value]} + +} + +export interface OptionsSelectProps { + activeOption: MetricsTopPeriod + onSelect: (option: MetricsTopPeriod) => void + disabled?: boolean +} + +export const OptionsSelect = (props: OptionsSelectProps) => { + const { activeOption, disabled, onSelect } = props + + return + {ChartOptions.map(option => +} diff --git a/src/pages/TopStatsPage/Transaction.tsx b/src/pages/TopStatsPage/Transaction.tsx index ca51384..0547415 100644 --- a/src/pages/TopStatsPage/Transaction.tsx +++ b/src/pages/TopStatsPage/Transaction.tsx @@ -1,17 +1,13 @@ import React, {useEffect, useState} from 'react' -import {Box} from "grommet"; +import {Box, Spinner} from "grommet"; import {TopTable} from "./CommonTopTable"; import {getTopMetricsByType} from "../../api/client"; -import {MetricsTopItem, MetricsTopType} from "../../types"; - -enum PeriodOption { - d1 = 1, - d3 = 3, - d7 = 7 -} +import {MetricsTopItem, MetricsTopPeriod, MetricsTopType} from "../../types"; +import {OptionsSelect} from "./OptionsSelect"; export const TransactionTopStats = () => { - const [period, setPeriod] = useState(PeriodOption.d1) + const [isLoading, setLoading] = useState(false) + const [period, setPeriod] = useState(MetricsTopPeriod.d1) const [oneSenders, setOneSenders] = useState([]) const [oneReceivers, setOneReceivers] = useState([]) const [txsSenders, setTxsSenders] = useState([]) @@ -19,45 +15,69 @@ export const TransactionTopStats = () => { useEffect(() => { const loadData = async () => { - const rowsOneSent = await getTopMetricsByType(MetricsTopType.topOneSender, 1) - const rowsOneReceive = await getTopMetricsByType(MetricsTopType.topOneReceiver, 1) - const rowsTxsSent = await getTopMetricsByType(MetricsTopType.topTxsCountSent, 1) - const rowsTxsReceived = await getTopMetricsByType(MetricsTopType.topTxsCountReceived, 1) + try { + setLoading(true) + const rowsOneSent = await getTopMetricsByType(MetricsTopType.topOneSender, period) + const rowsOneReceive = await getTopMetricsByType(MetricsTopType.topOneReceiver, period) + const rowsTxsSent = await getTopMetricsByType(MetricsTopType.topTxsCountSent, period) + const rowsTxsReceived = await getTopMetricsByType(MetricsTopType.topTxsCountReceived, period) + + await new Promise(resolve => setTimeout(resolve, 2000)) - setOneSenders(rowsOneSent) - setOneReceivers(rowsOneReceive) - setTxsSenders(rowsTxsSent) - setTxsReceivers(rowsTxsReceived) + setOneSenders(rowsOneSent) + setOneReceivers(rowsOneReceive) + setTxsSenders(rowsTxsSent) + setTxsReceivers(rowsTxsReceived) + } catch (e) { + console.error('Error on loading top metrics:', (e as Error).message) + } finally { + setLoading(false) + } } loadData() - }, []) + }, [period]) - return - - - - + return + + setPeriod(option)} + /> + + {isLoading && } + + + + + + + + } diff --git a/src/types/api.ts b/src/types/api.ts index 1c21e4a..1d47308 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -74,3 +74,9 @@ export interface MetricsTopItem { value: string updatedAt: string } + +export enum MetricsTopPeriod { + d1 = 1, + d3 = 3, + d7 = 7 +} From f75722e7f2667b1082a89a86bbf7e150decfcd44 Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Thu, 13 Oct 2022 10:06:27 +0100 Subject: [PATCH 3/5] Add values caching --- src/pages/TopStatsPage/OptionsSelect.tsx | 4 +- src/pages/TopStatsPage/Transaction.tsx | 71 +++++++++++++++++++----- src/pages/TopStatsPage/index.tsx | 2 +- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/pages/TopStatsPage/OptionsSelect.tsx b/src/pages/TopStatsPage/OptionsSelect.tsx index e480e58..8477744 100644 --- a/src/pages/TopStatsPage/OptionsSelect.tsx +++ b/src/pages/TopStatsPage/OptionsSelect.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Box} from "grommet"; +import {Box, Text} from "grommet"; import {MetricsTopPeriod} from "../../types"; const OptionAlias = { @@ -20,7 +20,7 @@ const Option = (props: { value: MetricsTopPeriod, isActive: boolean, onSelect: ( onClick={() => onSelect(value)} style={{ cursor: 'pointer', fontWeight: isActive ? 'bold': 'normal' }} > - {OptionAlias[value]} + {OptionAlias[value]} } diff --git a/src/pages/TopStatsPage/Transaction.tsx b/src/pages/TopStatsPage/Transaction.tsx index 0547415..8a77367 100644 --- a/src/pages/TopStatsPage/Transaction.tsx +++ b/src/pages/TopStatsPage/Transaction.tsx @@ -4,30 +4,63 @@ import {TopTable} from "./CommonTopTable"; import {getTopMetricsByType} from "../../api/client"; import {MetricsTopItem, MetricsTopPeriod, MetricsTopType} from "../../types"; import {OptionsSelect} from "./OptionsSelect"; +import dayjs from "dayjs"; + +const defaultMetricsItem = { + [MetricsTopType.topOneSender]: [] as MetricsTopItem[], + [MetricsTopType.topOneReceiver]: [] as MetricsTopItem[], + [MetricsTopType.topTxsCountSent]: [] as MetricsTopItem[], + [MetricsTopType.topTxsCountReceived]: [] as MetricsTopItem[], +} +const defaultCache = { + [MetricsTopPeriod.d1]: {...defaultMetricsItem}, + [MetricsTopPeriod.d3]: {...defaultMetricsItem}, + [MetricsTopPeriod.d7]: {...defaultMetricsItem}, +} export const TransactionTopStats = () => { const [isLoading, setLoading] = useState(false) const [period, setPeriod] = useState(MetricsTopPeriod.d1) + const [cache, setCache] = useState(defaultCache) const [oneSenders, setOneSenders] = useState([]) const [oneReceivers, setOneReceivers] = useState([]) const [txsSenders, setTxsSenders] = useState([]) const [txsReceivers, setTxsReceivers] = useState([]) useEffect(() => { + const retrieveMetrics = async (type: MetricsTopType, p: MetricsTopPeriod) => { + const cachedRows = cache[p][type] + if(cachedRows.length > 0) { + return [...cachedRows] + } + // await new Promise(resolve => setTimeout(resolve, 500)) + + return await getTopMetricsByType(type, p) + } + const loadData = async () => { try { setLoading(true) - const rowsOneSent = await getTopMetricsByType(MetricsTopType.topOneSender, period) - const rowsOneReceive = await getTopMetricsByType(MetricsTopType.topOneReceiver, period) - const rowsTxsSent = await getTopMetricsByType(MetricsTopType.topTxsCountSent, period) - const rowsTxsReceived = await getTopMetricsByType(MetricsTopType.topTxsCountReceived, period) - - await new Promise(resolve => setTimeout(resolve, 2000)) + const rowsOneSent = await retrieveMetrics(MetricsTopType.topOneSender, period) + const rowsOneReceive = await retrieveMetrics(MetricsTopType.topOneReceiver, period) + const rowsTxsSent = await retrieveMetrics(MetricsTopType.topTxsCountSent, period) + const rowsTxsReceived = await retrieveMetrics(MetricsTopType.topTxsCountReceived, period) setOneSenders(rowsOneSent) setOneReceivers(rowsOneReceive) setTxsSenders(rowsTxsSent) setTxsReceivers(rowsTxsReceived) + + const cacheUpdated = { + ...cache, + [period]: { + [MetricsTopType.topOneSender]: rowsOneSent, + [MetricsTopType.topOneReceiver]: rowsOneReceive, + [MetricsTopType.topTxsCountSent]: rowsTxsSent, + [MetricsTopType.topTxsCountReceived]: rowsTxsReceived + } + } + setCache(cacheUpdated) } catch (e) { console.error('Error on loading top metrics:', (e as Error).message) } finally { @@ -37,15 +70,25 @@ export const TransactionTopStats = () => { loadData() }, [period]) + const dateFrom = oneSenders.length > 0 ? dayjs(oneSenders[0].updatedAt).subtract(period, 'day') : '' + const dateTo = oneSenders.length > 0 ? dayjs(oneSenders[0].updatedAt): '' + return - - setPeriod(option)} - /> - - {isLoading && } + + + setPeriod(option)} + /> + + {isLoading && } + + + + {dateFrom && dateTo && + `${dateFrom.format('DD MMM')} - ${dateTo.format('DD MMM')}` + } { return - + Top Statistics From ad83e4a5a7149803532bb2958e233bb7032b5fcf Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Thu, 13 Oct 2022 11:56:38 +0100 Subject: [PATCH 4/5] Add mobile view --- src/pages/TopStatsPage/CommonTopTable.tsx | 26 +++++++++----------- src/pages/TopStatsPage/Transaction.tsx | 29 ++++++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/pages/TopStatsPage/CommonTopTable.tsx b/src/pages/TopStatsPage/CommonTopTable.tsx index 75731ae..c7eb3f9 100644 --- a/src/pages/TopStatsPage/CommonTopTable.tsx +++ b/src/pages/TopStatsPage/CommonTopTable.tsx @@ -21,23 +21,16 @@ const TableContainer = styled(Box)` flex: 0 0 calc(50% - 16px); margin-left: 8px; margin-right: 8px; - min-width: 500px; @media (max-width: 1024px) { flex: 0 0 calc(100% - 16px); margin-bottom: 0.75rem; } - - @media (max-width: 768px) { - flex: 0 0 calc(100% - 16px); - margin-top: 0.75rem; - margin-bottom: 0.75rem; - } ` const LogoWrapper = styled(Box)` svg path { - fill: #00AEE9 + fill: #00AEE9; } ` @@ -95,7 +88,7 @@ const TopTableRow = (props: TopTableRowProps) => { {rank} -
+
{isOneTransfer && @@ -113,16 +106,19 @@ export const TopTable = (props: TopTableProps) => { return - - - {props.title} + + + + {props.title} + + + {props.items.map(item => )} - - {props.items.map(item => )} } diff --git a/src/pages/TopStatsPage/Transaction.tsx b/src/pages/TopStatsPage/Transaction.tsx index 8a77367..9f6c83c 100644 --- a/src/pages/TopStatsPage/Transaction.tsx +++ b/src/pages/TopStatsPage/Transaction.tsx @@ -1,10 +1,11 @@ import React, {useEffect, useState} from 'react' -import {Box, Spinner} from "grommet"; +import {Box, Spinner, Tip, Text} from "grommet"; import {TopTable} from "./CommonTopTable"; import {getTopMetricsByType} from "../../api/client"; import {MetricsTopItem, MetricsTopPeriod, MetricsTopType} from "../../types"; import {OptionsSelect} from "./OptionsSelect"; import dayjs from "dayjs"; +import {TipContent} from "../../components/ui"; const defaultMetricsItem = { [MetricsTopType.topOneSender]: [] as MetricsTopItem[], @@ -12,6 +13,7 @@ const defaultMetricsItem = { [MetricsTopType.topTxsCountSent]: [] as MetricsTopItem[], [MetricsTopType.topTxsCountReceived]: [] as MetricsTopItem[], } + const defaultCache = { [MetricsTopPeriod.d1]: {...defaultMetricsItem}, [MetricsTopPeriod.d3]: {...defaultMetricsItem}, @@ -85,40 +87,45 @@ export const TransactionTopStats = () => { {isLoading && } - - {dateFrom && dateTo && - `${dateFrom.format('DD MMM')} - ${dateTo.format('DD MMM')}` - } - + {!isLoading && dateFrom && dateTo && + + } + > + {dateFrom.format('DD MMM')} - {dateTo.format('DD MMM')} + + + } From 69604931e5900aaaa2290e4bc140a58945954240 Mon Sep 17 00:00:00 2001 From: artemkolodko Date: Thu, 13 Oct 2022 12:54:24 +0100 Subject: [PATCH 5/5] Added new item mark --- src/components/appHeader/ToolsButton.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/appHeader/ToolsButton.tsx b/src/components/appHeader/ToolsButton.tsx index f32b18a..8f7a33d 100644 --- a/src/components/appHeader/ToolsButton.tsx +++ b/src/components/appHeader/ToolsButton.tsx @@ -80,7 +80,10 @@ export function ToolsButton() { history.push("/topstat"); }} > - Top Statistics + + Top Statistics + (new) + }