From e40989d942cd4334a892066c5f265fc3040d2848 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 9 Mar 2023 17:18:41 +0100 Subject: [PATCH 01/18] Stats added to chain dashboard --- src/components/StatsLayout.tsx | 62 +++++++++++++++++++++++++ src/components/stats/StatsGraphs.tsx | 17 +++++++ src/components/stats/StatsInfoTable.tsx | 27 +++++++++++ src/hooks/useStats.ts | 12 +++++ src/model/stats.ts | 15 ++++++ src/screens/chainDashboard.tsx | 22 +++++++++ src/services/statsService.ts | 44 ++++++++++++++++++ 7 files changed, 199 insertions(+) create mode 100644 src/components/StatsLayout.tsx create mode 100644 src/components/stats/StatsGraphs.tsx create mode 100644 src/components/stats/StatsInfoTable.tsx create mode 100644 src/hooks/useStats.ts create mode 100644 src/model/stats.ts create mode 100644 src/services/statsService.ts diff --git a/src/components/StatsLayout.tsx b/src/components/StatsLayout.tsx new file mode 100644 index 00000000..1059427a --- /dev/null +++ b/src/components/StatsLayout.tsx @@ -0,0 +1,62 @@ +/** @jsxImportSource @emotion/react */ +import styled from "@emotion/styled"; + +export const StatsLayout = styled.div` + display: grid; + width: 100%; + height: auto; + + gap: 10px; + + grid-template-columns: repeat(4, auto); + + @media (max-width: 1300px) { + grid-template-columns: repeat(2, auto); + } + +`; + +const Stat = styled.div` + min-width: 100px; + width: 100%; + height: 64px; + display: flex; +`; + +const StatIcon = styled.img` + width: 64px; + height: 64px; + margin-right: 10px; +`; + +const StatTitle = styled.div` + font-weight: 500; + margin-top: auto; +`; + +const StatValue = styled.div` + height: 32px; + font-weight: 900; +`; + +export type StatItemProps = { + title: string; + value?: string | number; +}; + +export function StatItem (props: StatItemProps) { + const { + title, + value, + } = props; + + return ( + + +
+ {title} + {value} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/stats/StatsGraphs.tsx b/src/components/stats/StatsGraphs.tsx new file mode 100644 index 00000000..6ef7c584 --- /dev/null +++ b/src/components/stats/StatsGraphs.tsx @@ -0,0 +1,17 @@ +import { Resource } from "../../model/resource"; +import { Stats } from "../../model/stats"; + +export type StatsGraphProps = { + stats: Resource; +} + +export const StatsGraph = (props: StatsGraphProps) => { + const {stats} = props; + + // This is temporary + return ( +
+ Graph +
+ ); +}; diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx new file mode 100644 index 00000000..54652152 --- /dev/null +++ b/src/components/stats/StatsInfoTable.tsx @@ -0,0 +1,27 @@ +import { Resource } from "../../model/resource"; +import { Stats } from "../../model/stats"; +import { StatItem, StatsLayout } from "../StatsLayout"; + +export type StatsInfoTableProps = { + stats: Resource; +} + +export const StatsInfoTable = (props: StatsInfoTableProps) => { + const {stats} = props; + + + const stakedValuePercentage = stats.data?.totalIssuance ? (stats.data?.stakedValueTotal / stats.data?.totalIssuance * 100).toFixed(1) : 0; + + return ( + + + + + + + + + + + ); +}; diff --git a/src/hooks/useStats.ts b/src/hooks/useStats.ts new file mode 100644 index 00000000..930c248b --- /dev/null +++ b/src/hooks/useStats.ts @@ -0,0 +1,12 @@ +import { FetchOptions } from "../model/fetchOptions"; +import { getStats, StatsFilter, StatsOrder } from "../services/statsService"; +import { useResource } from "./useResource"; + +export function useStats( + network: string, + filter: StatsFilter|undefined, + order?: StatsOrder|undefined, + options?: FetchOptions +) { + return useResource(getStats, [network, filter, order], options); +} diff --git a/src/model/stats.ts b/src/model/stats.ts new file mode 100644 index 00000000..8c1889b7 --- /dev/null +++ b/src/model/stats.ts @@ -0,0 +1,15 @@ +export type Stats = { + finalizedBlocks: number; + holders: number; + nominationPoolsCountMembers: number; + nominationPoolsCountPools: number; + nominationPoolsTotalStake: number; + signedExtrinsics: number; + stakedValueNominator: number; + stakedValueTotal: number; + stakedValueValidator: number; + totalIssuance: number; + transfersCount: number; + validatorsCount: number; + validatorsIdealCount: number; +} diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index f05f85a7..f84e1068 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -16,6 +16,19 @@ import { useBlocks } from "../hooks/useBlocks"; import BlocksTable from "../components/blocks/BlocksTable"; import { useBalances } from "../hooks/useBalances"; import BalancesTable from "../components/balances/BalancesTable"; +import { useStats } from "../hooks/useStats"; +import { StatsInfoTable } from "../components/stats/StatsInfoTable"; +import { StatsGraph } from "../components/stats/StatsGraphs"; +import styled from "@emotion/styled"; + +const ChainDashboardLayout = styled.div` + display: flex; + flex-direction: row; + + @media (max-width: 800px) { + flex-direction: column; + } +`; type ChainDashboardPageParams = { network: string; @@ -29,6 +42,10 @@ function ChainDashboardPage() { const transfers = useTransfers(network, undefined, "id_DESC"); const topHolders = useBalances(network, undefined, "free_DESC"); + const stats = useStats(network, undefined); + + console.log(stats.data); + useDOMEventTrigger("data-loaded", !extrinsics.loading); const networks = useNetworks(); @@ -47,6 +64,11 @@ function ChainDashboardPage() { {networkData?.displayName} dashboard + + + + + diff --git a/src/services/statsService.ts b/src/services/statsService.ts new file mode 100644 index 00000000..3f4caf0f --- /dev/null +++ b/src/services/statsService.ts @@ -0,0 +1,44 @@ +import { Stats } from "../model/stats"; +import { fetchStatsSquid} from "./fetchService"; +import { hasSupport } from "./networksService"; + +export type StatsFilter = any + +export type StatsOrder = string | string[]; + +export async function getStats( + network: string, + filter: StatsFilter|undefined, + order: StatsOrder = "id_DESC", +) { + if (hasSupport(network, "stats-squid")) { + const response = await fetchStatsSquid<{totals: Stats[]}>( + network, + `query ($filter: TotalsWhereInput, $order: [TotalsOrderByInput!]!) { + totals(limit: 1, where: $filter, orderBy: $order) { + finalizedBlocks + holders + nominationPoolsCountMembers + nominationPoolsCountPools + nominationPoolsTotalStake + signedExtrinsics + stakedValueNominator + stakedValueTotal + stakedValueValidator + totalIssuance + transfersCount + validatorsCount + validatorsIdealCount + } + }`, + { + filter, + order, + } + ); + + return response.totals[0]; + } + + return undefined; +} From 70bc3cf977fadf7bcb2f0767e2b9ac48d81cd954 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 30 Mar 2023 22:58:10 +0200 Subject: [PATCH 02/18] Stats chart --- src/components/stats/StatsChart.tsx | 215 +++++++++++++++++++++++++++ src/components/stats/StatsGraphs.tsx | 17 --- src/screens/chainDashboard.tsx | 83 ++++++----- 3 files changed, 259 insertions(+), 56 deletions(-) create mode 100644 src/components/stats/StatsChart.tsx delete mode 100644 src/components/stats/StatsGraphs.tsx diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx new file mode 100644 index 00000000..cf887397 --- /dev/null +++ b/src/components/stats/StatsChart.tsx @@ -0,0 +1,215 @@ +/** @jsxImportSource @emotion/react */ +import { HTMLAttributes, useMemo } from "react"; +import { Resource } from "../../model/resource"; +import { Stats } from "../../model/stats"; +import { PieChart, PieChartOptions } from "../PieChart"; +import { css, Theme, useTheme } from "@emotion/react"; +import { useMediaQuery } from "@mui/material"; +import { CallbackDataParams } from "echarts/types/dist/shared"; +import { getNetwork } from "../../services/networksService"; +import Loading from "../Loading"; +import NotFound from "../NotFound"; +import { ErrorMessage } from "../ErrorMessage"; + +const valuesStyle = (theme: Theme) => css` + position: relative; + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: space-around; + + ${theme.breakpoints.down("sm")} { + display: block; + } +`; + +const valueStyle = (theme: Theme) => css` + min-width: 70px; + + ${theme.breakpoints.down("sm")} { + display: flex; + max-width: 230px; + margin: 0 auto; + } +`; + +const valueTypeStyle = (theme: Theme) => css` + margin-bottom: 8px; + font-weight: 700; + + ${theme.breakpoints.down("sm")} { + flex: 1 1 auto; + } +`; + +const separatorStyle = css` + display: block; + width: 1px; + flex: 0 0 auto; + background-color: rgba(0, 0, 0, .125); +`; + +const notFoundStyle = css` + margin: 0 auto; + max-width: 300px; +`; + +const chartStyle = (theme: Theme) => css` + width: 400px; + height: 230px; + + ${theme.breakpoints.down("sm")} { + width: 230px; + height: 270px; + } + + .ECharts-tooltip { + [data-class=title] { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + font-weight: 600; + font-size: 15px; + } + + [data-class=icon] { + height: 24px; + width: 24px; + object-fit: contain; + } + + [data-class=value] { + font-size: 15px; + } + + [data-class=usd-value] { + font-size: 14px; + opacity: .75; + } + } +`; + +export type StatsChartProps = HTMLAttributes & { + stats: Resource; + networkName: string; +} + +export const StatsChart = (props: StatsChartProps) => { + const { stats, networkName, ...divProps } = props; + + const network = getNetwork(networkName); + + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm")); + + const totalData = useMemo(() => { + if (!stats.data) { + return []; + } + + return [ + { + name: "Circulating", + value: (stats.data?.totalIssuance - stats.data?.stakedValueTotal), + itemStyle: { + color: network?.color || theme.palette.primary.main, + } + }, + { + name: "Staked", + value: (stats.data?.stakedValueTotal), + itemStyle: { + color: "gray" + }, + } + ]; + }, [stats]); + + + const options = useMemo(() => { + if (totalData.length === 0) { + return {}; + } + + const thing: PieChartOptions = { + tooltip: { + formatter: (params) => { + const { name, value } = params as CallbackDataParams; + + console.log("PARAMS: ", params); + + return ` +
${name}
+
${value}
+ `; + } + }, + legend: { + textStyle: { + width: 85, + overflow: "truncate" + }, + orient: isSmallScreen ? "horizontal" : "vertical", + top: isSmallScreen ? "auto" : "center", + left: isSmallScreen ? "center" : 275, + bottom: isSmallScreen ? 0 : "auto", + height: isSmallScreen ? "auto" : 140 + }, + series: { + radius: [60, 100], + center: isSmallScreen + ? ["center", 116] + : [116, "center"], + data: [ + ...totalData, + ] + }, + }; + return thing; + }, [totalData, isSmallScreen]); + + if (stats.loading) { + return ; + } + + if (stats.notFound) { + return No positive balances with conversion rate to USD found; + } + + if (stats.error) { + return ( + + ); + } + + if (!stats.data) { + return null; + } + + return (<> +
+
+
Circulating
+
{stats.data?.totalIssuance - stats.data?.stakedValueTotal}
+
+
+
+
Staked
+
{stats.data?.stakedValueTotal}
+
+
+
+ + + ); +}; diff --git a/src/components/stats/StatsGraphs.tsx b/src/components/stats/StatsGraphs.tsx deleted file mode 100644 index 6ef7c584..00000000 --- a/src/components/stats/StatsGraphs.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Resource } from "../../model/resource"; -import { Stats } from "../../model/stats"; - -export type StatsGraphProps = { - stats: Resource; -} - -export const StatsGraph = (props: StatsGraphProps) => { - const {stats} = props; - - // This is temporary - return ( -
- Graph -
- ); -}; diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index ad52e61f..383ae203 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -4,7 +4,7 @@ import { useParams } from "react-router-dom"; import ExtrinsicsTable from "../components/extrinsics/ExtrinsicsTable"; -import { Card, CardHeader } from "../components/Card"; +import { Card, CardHeader, CardRow } from "../components/Card"; import { useExtrinsicsWithoutTotalCount } from "../hooks/useExtrinsicsWithoutTotalCount"; import { useDOMEventTrigger } from "../hooks/useDOMEventTrigger"; import { TabbedContent, TabPane } from "../components/TabbedContent"; @@ -18,20 +18,19 @@ import { useBalances } from "../hooks/useBalances"; import BalancesTable from "../components/balances/BalancesTable"; import { useStats } from "../hooks/useStats"; import { StatsInfoTable } from "../components/stats/StatsInfoTable"; -import { StatsGraph } from "../components/stats/StatsGraphs"; -import styled from "@emotion/styled"; +import { StatsChart } from "../components/stats/StatsChart"; import { useUsdRates } from "../hooks/useUsdRates"; - -const ChainDashboardLayout = styled.div` - display: flex; - flex-direction: row; - - @media (max-width: 800px) { - flex-direction: column; - } +import { css, Tooltip } from "@mui/material"; +import { InfoOutlined } from "@mui/icons-material"; + +const portfolioInfoIconStyle = css` + height: 30px; + font-size: 20px; + opacity: .5; + vertical-align: text-bottom; + margin-left: 4px; `; - type ChainDashboardPageParams = { network: string; }; @@ -62,16 +61,22 @@ function ChainDashboardPage() { return ( <> - - - {networkData?.displayName} dashboard - - - + + + + {networkData?.displayName} dashboard + - - - + + + + {networkData?.displayName} Statistics + + + + + + - - + + + } {hasSupport(network, "balances-squid") && - - - + + + } From 5bf137decfb567ebf776bbaa3176aac9c118f13a Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 30 Mar 2023 22:59:50 +0200 Subject: [PATCH 03/18] updated `notFound` message --- src/components/stats/StatsChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index cf887397..52d2e6ec 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -174,7 +174,7 @@ export const StatsChart = (props: StatsChartProps) => { } if (stats.notFound) { - return No positive balances with conversion rate to USD found; + return Stats are currently unavailable for {networkName}.; } if (stats.error) { From fdae40d83a187b2e15c3cb2dbff7c233803e7cb1 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 30 Mar 2023 23:07:02 +0200 Subject: [PATCH 04/18] StatsInfoTable loading, error and notFound added --- src/components/stats/StatsInfoTable.tsx | 34 ++++++++++++++++++++++++- src/screens/chainDashboard.tsx | 10 -------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 54652152..025b1bdb 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -1,17 +1,49 @@ +/** @jsxImportSource @emotion/react */ +import { css } from "@emotion/react"; import { Resource } from "../../model/resource"; import { Stats } from "../../model/stats"; +import { ErrorMessage } from "../ErrorMessage"; +import Loading from "../Loading"; +import NotFound from "../NotFound"; import { StatItem, StatsLayout } from "../StatsLayout"; +const notFoundStyle = css` + margin: 0 auto; + max-width: 300px; +`; + export type StatsInfoTableProps = { stats: Resource; } export const StatsInfoTable = (props: StatsInfoTableProps) => { - const {stats} = props; + const { stats } = props; const stakedValuePercentage = stats.data?.totalIssuance ? (stats.data?.stakedValueTotal / stats.data?.totalIssuance * 100).toFixed(1) : 0; + if (stats.loading) { + return ; + } + + if (stats.notFound) { + return Stats are currently unavailable.; + } + + if (stats.error) { + return ( + + ); + } + + if (!stats.data) { + return null; + } + return ( diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index 383ae203..2082529f 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -20,16 +20,6 @@ import { useStats } from "../hooks/useStats"; import { StatsInfoTable } from "../components/stats/StatsInfoTable"; import { StatsChart } from "../components/stats/StatsChart"; import { useUsdRates } from "../hooks/useUsdRates"; -import { css, Tooltip } from "@mui/material"; -import { InfoOutlined } from "@mui/icons-material"; - -const portfolioInfoIconStyle = css` - height: 30px; - font-size: 20px; - opacity: .5; - vertical-align: text-bottom; - margin-left: 4px; -`; type ChainDashboardPageParams = { network: string; From f042dc7e0d968989f434e1ea41da35046265a203 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 30 Mar 2023 23:16:36 +0200 Subject: [PATCH 05/18] chart styles unified --- src/components/ChartStyles.tsx | 74 +++++++++++++++++++ src/components/account/AccountPortfolio.tsx | 78 +------------------- src/components/stats/StatsChart.tsx | 81 +-------------------- src/components/stats/StatsInfoTable.tsx | 7 +- src/screens/chainDashboard.tsx | 2 - 5 files changed, 78 insertions(+), 164 deletions(-) create mode 100644 src/components/ChartStyles.tsx diff --git a/src/components/ChartStyles.tsx b/src/components/ChartStyles.tsx new file mode 100644 index 00000000..616d29a0 --- /dev/null +++ b/src/components/ChartStyles.tsx @@ -0,0 +1,74 @@ +import { css, Theme } from "@emotion/react"; + +export const switchStyle = (theme: Theme) => css` + position: absolute; + top: 48px; + right: 48px; + margin: 4px 0; + + .MuiToggleButton-root { + padding: 0 10px; + font-size: 14px; + } + + ${theme.breakpoints.down("md")} { + top: 24px; + right: 24px; + } + + ${theme.breakpoints.down("sm")} { + position: relative; + top: -28px; + right: auto; + display: flex; + justify-content: center; + } +`; + +export const valuesStyle = (theme: Theme) => css` + position: relative; + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: space-around; + + ${theme.breakpoints.down("sm")} { + display: block; + } +`; + +export const valueStyle = (theme: Theme) => css` + min-width: 70px; + + ${theme.breakpoints.down("sm")} { + display: flex; + max-width: 230px; + margin: 0 auto; + } +`; + +export const valueTypeStyle = (theme: Theme) => css` + margin-bottom: 8px; + font-weight: 700; + + ${theme.breakpoints.down("sm")} { + flex: 1 1 auto; + } +`; + +export const separatorStyle = css` + display: block; + width: 1px; + flex: 0 0 auto; + background-color: rgba(0, 0, 0, .125); +`; + +export const chartStyle = css` + margin: 0 auto; + margin-top: 32px; +`; + +export const notFoundStyle = css` + margin: 0 auto; + max-width: 300px; +`; diff --git a/src/components/account/AccountPortfolio.tsx b/src/components/account/AccountPortfolio.tsx index 13691332..68c09487 100644 --- a/src/components/account/AccountPortfolio.tsx +++ b/src/components/account/AccountPortfolio.tsx @@ -1,92 +1,16 @@ /** @jsxImportSource @emotion/react */ import { HTMLAttributes, useMemo, useState } from "react"; import { ToggleButton, ToggleButtonGroup } from "@mui/material"; -import { css, Theme } from "@emotion/react"; - import { AccountBalance } from "../../model/accountBalance"; import { Resource } from "../../model/resource"; import { UsdRates } from "../../model/usdRates"; import { usdBalanceSum } from "../../utils/balance"; import { formatCurrency } from "../../utils/number"; - import { ErrorMessage } from "../ErrorMessage"; import Loading from "../Loading"; import NotFound from "../NotFound"; - import { AccountPortfolioChartMode, AccountPortfolioChart } from "./AccountPortfolioChart"; - -const switchStyle = (theme: Theme) => css` - position: absolute; - top: 48px; - right: 48px; - margin: 4px 0; - - .MuiToggleButton-root { - padding: 0 10px; - font-size: 14px; - } - - ${theme.breakpoints.down("md")} { - top: 24px; - right: 24px; - } - - ${theme.breakpoints.down("sm")} { - position: relative; - top: -28px; - right: auto; - display: flex; - justify-content: center; - } -`; - -const valuesStyle = (theme: Theme) => css` - position: relative; - display: flex; - flex-direction: row; - align-items: stretch; - justify-content: space-around; - - ${theme.breakpoints.down("sm")} { - display: block; - } -`; - -const valueStyle = (theme: Theme) => css` - min-width: 70px; - - ${theme.breakpoints.down("sm")} { - display: flex; - max-width: 230px; - margin: 0 auto; - } -`; - -const valueTypeStyle = (theme: Theme) => css` - margin-bottom: 8px; - font-weight: 700; - - ${theme.breakpoints.down("sm")} { - flex: 1 1 auto; - } -`; - -const separatorStyle = css` - display: block; - width: 1px; - flex: 0 0 auto; - background-color: rgba(0, 0, 0, .125); -`; - -const chartStyle = css` - margin: 0 auto; - margin-top: 32px; -`; - -const notFoundStyle = css` - margin: 0 auto; - max-width: 300px; -`; +import { chartStyle, notFoundStyle, separatorStyle, switchStyle, valuesStyle, valueStyle, valueTypeStyle } from "../ChartStyles"; export type AccountPortfolioProps = HTMLAttributes & { balances: Resource; diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index 52d2e6ec..d99f244c 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -3,92 +3,15 @@ import { HTMLAttributes, useMemo } from "react"; import { Resource } from "../../model/resource"; import { Stats } from "../../model/stats"; import { PieChart, PieChartOptions } from "../PieChart"; -import { css, Theme, useTheme } from "@emotion/react"; +import { useTheme } from "@emotion/react"; import { useMediaQuery } from "@mui/material"; import { CallbackDataParams } from "echarts/types/dist/shared"; import { getNetwork } from "../../services/networksService"; import Loading from "../Loading"; import NotFound from "../NotFound"; import { ErrorMessage } from "../ErrorMessage"; +import { chartStyle, notFoundStyle, separatorStyle, valuesStyle, valueStyle, valueTypeStyle } from "../ChartStyles"; -const valuesStyle = (theme: Theme) => css` - position: relative; - display: flex; - flex-direction: row; - align-items: stretch; - justify-content: space-around; - - ${theme.breakpoints.down("sm")} { - display: block; - } -`; - -const valueStyle = (theme: Theme) => css` - min-width: 70px; - - ${theme.breakpoints.down("sm")} { - display: flex; - max-width: 230px; - margin: 0 auto; - } -`; - -const valueTypeStyle = (theme: Theme) => css` - margin-bottom: 8px; - font-weight: 700; - - ${theme.breakpoints.down("sm")} { - flex: 1 1 auto; - } -`; - -const separatorStyle = css` - display: block; - width: 1px; - flex: 0 0 auto; - background-color: rgba(0, 0, 0, .125); -`; - -const notFoundStyle = css` - margin: 0 auto; - max-width: 300px; -`; - -const chartStyle = (theme: Theme) => css` - width: 400px; - height: 230px; - - ${theme.breakpoints.down("sm")} { - width: 230px; - height: 270px; - } - - .ECharts-tooltip { - [data-class=title] { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; - font-weight: 600; - font-size: 15px; - } - - [data-class=icon] { - height: 24px; - width: 24px; - object-fit: contain; - } - - [data-class=value] { - font-size: 15px; - } - - [data-class=usd-value] { - font-size: 14px; - opacity: .75; - } - } -`; export type StatsChartProps = HTMLAttributes & { stats: Resource; diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 025b1bdb..62eb3e6e 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -1,17 +1,12 @@ /** @jsxImportSource @emotion/react */ -import { css } from "@emotion/react"; import { Resource } from "../../model/resource"; import { Stats } from "../../model/stats"; +import { notFoundStyle } from "../ChartStyles"; import { ErrorMessage } from "../ErrorMessage"; import Loading from "../Loading"; import NotFound from "../NotFound"; import { StatItem, StatsLayout } from "../StatsLayout"; -const notFoundStyle = css` - margin: 0 auto; - max-width: 300px; -`; - export type StatsInfoTableProps = { stats: Resource; } diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index 2082529f..20c1b687 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -1,9 +1,7 @@ /** @jsxImportSource @emotion/react */ import { useEffect } from "react"; import { useParams } from "react-router-dom"; - import ExtrinsicsTable from "../components/extrinsics/ExtrinsicsTable"; - import { Card, CardHeader, CardRow } from "../components/Card"; import { useExtrinsicsWithoutTotalCount } from "../hooks/useExtrinsicsWithoutTotalCount"; import { useDOMEventTrigger } from "../hooks/useDOMEventTrigger"; From e970578bcbcf7fe7c65ab92435f5e5f4f16dd559 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 30 Mar 2023 23:21:10 +0200 Subject: [PATCH 06/18] Has support check for stats-squid --- src/components/stats/StatsChart.tsx | 2 +- src/components/stats/StatsInfoTable.tsx | 2 +- src/screens/chainDashboard.tsx | 18 +++++++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index d99f244c..6ed52f4a 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -97,7 +97,7 @@ export const StatsChart = (props: StatsChartProps) => { } if (stats.notFound) { - return Stats are currently unavailable for {networkName}.; + return Stats not found.; } if (stats.error) { diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 62eb3e6e..9659a5d3 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -22,7 +22,7 @@ export const StatsInfoTable = (props: StatsInfoTableProps) => { } if (stats.notFound) { - return Stats are currently unavailable.; + return Stats not found.; } if (stats.error) { diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index 20c1b687..671a7609 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -54,14 +54,18 @@ function ChainDashboardPage() { {networkData?.displayName} dashboard - - - - - {networkData?.displayName} Statistics - - + {hasSupport(network, "stats-squid") && + + } + {hasSupport(network, "stats-squid") && + + + {networkData?.displayName} Statistics + + + + } From 3bad9871fd3a5e38019987d031db4086b6529b5e Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Thu, 30 Mar 2023 23:42:25 +0200 Subject: [PATCH 07/18] bug fixed with a style --- src/components/ChartStyles.tsx | 38 ++++++++++++++++++++- src/components/account/AccountPortfolio.tsx | 4 +-- src/components/stats/StatsChart.tsx | 25 ++++++-------- src/screens/chainDashboard.tsx | 2 +- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/components/ChartStyles.tsx b/src/components/ChartStyles.tsx index 616d29a0..705adf11 100644 --- a/src/components/ChartStyles.tsx +++ b/src/components/ChartStyles.tsx @@ -63,7 +63,7 @@ export const separatorStyle = css` background-color: rgba(0, 0, 0, .125); `; -export const chartStyle = css` +export const chartMarginStyle = css` margin: 0 auto; margin-top: 32px; `; @@ -72,3 +72,39 @@ export const notFoundStyle = css` margin: 0 auto; max-width: 300px; `; + +export const chartStyle = (theme: Theme) => css` + width: 400px; + height: 230px; + + ${theme.breakpoints.down("sm")} { + width: 230px; + height: 270px; + } + + .ECharts-tooltip { + [data-class=title] { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + font-weight: 600; + font-size: 15px; + } + + [data-class=icon] { + height: 24px; + width: 24px; + object-fit: contain; + } + + [data-class=value] { + font-size: 15px; + } + + [data-class=usd-value] { + font-size: 14px; + opacity: .75; + } + } +`; diff --git a/src/components/account/AccountPortfolio.tsx b/src/components/account/AccountPortfolio.tsx index 68c09487..f51736b1 100644 --- a/src/components/account/AccountPortfolio.tsx +++ b/src/components/account/AccountPortfolio.tsx @@ -10,7 +10,7 @@ import { ErrorMessage } from "../ErrorMessage"; import Loading from "../Loading"; import NotFound from "../NotFound"; import { AccountPortfolioChartMode, AccountPortfolioChart } from "./AccountPortfolioChart"; -import { chartStyle, notFoundStyle, separatorStyle, switchStyle, valuesStyle, valueStyle, valueTypeStyle } from "../ChartStyles"; +import { chartMarginStyle, notFoundStyle, separatorStyle, switchStyle, valuesStyle, valueStyle, valueTypeStyle } from "../ChartStyles"; export type AccountPortfolioProps = HTMLAttributes & { balances: Resource; @@ -76,7 +76,7 @@ export const AccountPortfolio = (props: AccountPortfolioProps) => {
& { stats: Resource; @@ -51,11 +50,7 @@ export const StatsChart = (props: StatsChartProps) => { const options = useMemo(() => { - if (totalData.length === 0) { - return {}; - } - - const thing: PieChartOptions = { + return { tooltip: { formatter: (params) => { const { name, value } = params as CallbackDataParams; @@ -89,7 +84,7 @@ export const StatsChart = (props: StatsChartProps) => { ] }, }; - return thing; + }, [totalData, isSmallScreen]); if (stats.loading) { @@ -127,12 +122,14 @@ export const StatsChart = (props: StatsChartProps) => {
- +
+ +
); }; diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index 671a7609..687bcef1 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -61,7 +61,7 @@ function ChainDashboardPage() { {hasSupport(network, "stats-squid") && - {networkData?.displayName} Statistics + {networkData?.displayName} statistics From f4e7f6bc25039c49a52de95989e514ff31a81326 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Sun, 30 Apr 2023 23:38:26 +0200 Subject: [PATCH 08/18] Chain dashboard ready for new stats-squid --- src/assets/block.svg | 1 + src/assets/holder.svg | 1 + src/assets/nominator.svg | 1 + src/assets/signed.svg | 1 + src/assets/stake.svg | 1 + src/assets/token.svg | 1 + src/assets/transfer.svg | 1 + src/assets/validator.svg | 1 + src/components/StatsLayout.tsx | 46 ++++++++------------- src/components/stats/StatsChart.tsx | 6 +-- src/components/stats/StatsInfoTable.tsx | 55 ++++++++++++++++++------- src/model/stats.ts | 2 + src/services/balancesService.ts | 1 - src/services/statsService.ts | 25 +++++++++-- 14 files changed, 92 insertions(+), 51 deletions(-) create mode 100644 src/assets/block.svg create mode 100644 src/assets/holder.svg create mode 100644 src/assets/nominator.svg create mode 100644 src/assets/signed.svg create mode 100644 src/assets/stake.svg create mode 100644 src/assets/token.svg create mode 100644 src/assets/transfer.svg create mode 100644 src/assets/validator.svg diff --git a/src/assets/block.svg b/src/assets/block.svg new file mode 100644 index 00000000..6f257c23 --- /dev/null +++ b/src/assets/block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/holder.svg b/src/assets/holder.svg new file mode 100644 index 00000000..e9d68c14 --- /dev/null +++ b/src/assets/holder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/nominator.svg b/src/assets/nominator.svg new file mode 100644 index 00000000..5a1ea36f --- /dev/null +++ b/src/assets/nominator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/signed.svg b/src/assets/signed.svg new file mode 100644 index 00000000..ca3bb5af --- /dev/null +++ b/src/assets/signed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/stake.svg b/src/assets/stake.svg new file mode 100644 index 00000000..d31b0833 --- /dev/null +++ b/src/assets/stake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/token.svg b/src/assets/token.svg new file mode 100644 index 00000000..946dad70 --- /dev/null +++ b/src/assets/token.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/transfer.svg b/src/assets/transfer.svg new file mode 100644 index 00000000..72a05aa9 --- /dev/null +++ b/src/assets/transfer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/validator.svg b/src/assets/validator.svg new file mode 100644 index 00000000..7592f739 --- /dev/null +++ b/src/assets/validator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/StatsLayout.tsx b/src/components/StatsLayout.tsx index 1059427a..7ba0fa35 100644 --- a/src/components/StatsLayout.tsx +++ b/src/components/StatsLayout.tsx @@ -1,46 +1,33 @@ /** @jsxImportSource @emotion/react */ -import styled from "@emotion/styled"; +import { css } from "@emotion/react"; -export const StatsLayout = styled.div` - display: grid; - width: 100%; - height: auto; - - gap: 10px; - - grid-template-columns: repeat(4, auto); - - @media (max-width: 1300px) { - grid-template-columns: repeat(2, auto); - } - -`; - -const Stat = styled.div` +const StatStyle = css` min-width: 100px; width: 100%; height: 64px; display: flex; `; -const StatIcon = styled.img` - width: 64px; - height: 64px; +const StatIconStyle = css` + width: 44px; + height: 44px; margin-right: 10px; + padding: 10px; `; -const StatTitle = styled.div` - font-weight: 500; +const StatTitleStyle = css` + font-weight: 900; margin-top: auto; `; -const StatValue = styled.div` +const StatValueStyle = css` + font-weight: 500; height: 32px; - font-weight: 900; `; export type StatItemProps = { title: string; + icon?: string; value?: string | number; }; @@ -48,15 +35,16 @@ export function StatItem (props: StatItemProps) { const { title, value, + icon, } = props; return ( - - +
+
- {title} - {value} +
{title}
+
{value}
- +
); } \ No newline at end of file diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index eec5d893..e17463ff 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -33,7 +33,7 @@ export const StatsChart = (props: StatsChartProps) => { return [ { name: "Circulating", - value: (stats.data?.totalIssuance - stats.data?.stakedValueTotal), + value: (stats.data.circulatingValueTotal), itemStyle: { color: network?.color || theme.palette.primary.main, } @@ -113,12 +113,12 @@ export const StatsChart = (props: StatsChartProps) => {
Circulating
-
{stats.data?.totalIssuance - stats.data?.stakedValueTotal}
+
{stats.data.circulatingValueTotal}
Staked
-
{stats.data?.stakedValueTotal}
+
{stats.data?.stakedValueTotal.toString()}
diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 9659a5d3..4fc05e87 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -5,7 +5,31 @@ import { notFoundStyle } from "../ChartStyles"; import { ErrorMessage } from "../ErrorMessage"; import Loading from "../Loading"; import NotFound from "../NotFound"; -import { StatItem, StatsLayout } from "../StatsLayout"; +import { StatItem } from "../StatsLayout"; + +import Block from "../../assets/block.svg"; +import Signed from "../../assets/signed.svg"; +import Transfer from "../../assets/transfer.svg"; +import Holder from "../../assets/holder.svg"; +import Nominator from "../../assets/nominator.svg"; +import Stake from "../../assets/stake.svg"; +import Validator from "../../assets/validator.svg"; +import Token from "../../assets/token.svg"; +import { css } from "@emotion/react"; + +const StatsLayoutStyle = css` + display: grid; + width: 100%; + height: auto; + + gap: 10px; + + grid-template-columns: repeat(4, auto); + + @media (max-width: 1500px) { + grid-template-columns: repeat(2, auto); + } +`; export type StatsInfoTableProps = { stats: Resource; @@ -14,9 +38,6 @@ export type StatsInfoTableProps = { export const StatsInfoTable = (props: StatsInfoTableProps) => { const { stats } = props; - - const stakedValuePercentage = stats.data?.totalIssuance ? (stats.data?.stakedValueTotal / stats.data?.totalIssuance * 100).toFixed(1) : 0; - if (stats.loading) { return ; } @@ -40,15 +61,21 @@ export const StatsInfoTable = (props: StatsInfoTableProps) => { } return ( - - - - - - - - - - +
+ + + + + + + + +
); }; diff --git a/src/model/stats.ts b/src/model/stats.ts index 8c1889b7..afca6763 100644 --- a/src/model/stats.ts +++ b/src/model/stats.ts @@ -12,4 +12,6 @@ export type Stats = { transfersCount: number; validatorsCount: number; validatorsIdealCount: number; + stakedValuePercentage: number; + circulatingValueTotal: number; } diff --git a/src/services/balancesService.ts b/src/services/balancesService.ts index 4ea5920d..7837d3e9 100644 --- a/src/services/balancesService.ts +++ b/src/services/balancesService.ts @@ -1,5 +1,4 @@ import Decimal from "decimal.js"; - import { AccountBalance } from "../model/accountBalance"; import { Balance } from "../model/balance"; import { BalancesSquidBalance } from "../model/explorer-squid/explorerSquidAccountBalance"; diff --git a/src/services/statsService.ts b/src/services/statsService.ts index 3f4caf0f..fed78ecf 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -1,6 +1,7 @@ import { Stats } from "../model/stats"; +import { rawAmountToDecimal } from "../utils/number"; import { fetchStatsSquid} from "./fetchService"; -import { hasSupport } from "./networksService"; +import { getNetwork, hasSupport } from "./networksService"; export type StatsFilter = any @@ -12,7 +13,7 @@ export async function getStats( order: StatsOrder = "id_DESC", ) { if (hasSupport(network, "stats-squid")) { - const response = await fetchStatsSquid<{totals: Stats[]}>( + const response = await fetchStatsSquid<{totals: Omit[]}>( network, `query ($filter: TotalsWhereInput, $order: [TotalsOrderByInput!]!) { totals(limit: 1, where: $filter, orderBy: $order) { @@ -36,9 +37,25 @@ export async function getStats( order, } ); - - return response.totals[0]; + + if(response.totals[0]) { + return unifyStats(response.totals[0], network); + } } return undefined; } + +/*** PRIVATE ***/ + +function unifyStats(stats: Omit, networkName: string): Stats { + const network = getNetwork(networkName)!; + + return { + ...stats, + totalIssuance: rawAmountToDecimal(network, stats.totalIssuance.toString()).toNumber(), + stakedValueTotal: rawAmountToDecimal(network, stats.stakedValueTotal.toString()).toNumber(), + circulatingValueTotal: 0, + stakedValuePercentage: stats.stakedValueTotal / stats.totalIssuance * 100, + }; +} \ No newline at end of file From 211cb3381f34027403adeb7e7132d1ecaba4dbab Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Sun, 30 Apr 2023 23:40:29 +0200 Subject: [PATCH 09/18] .toFixed() in StatsChart --- src/components/stats/StatsChart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index e17463ff..48726f22 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -113,12 +113,12 @@ export const StatsChart = (props: StatsChartProps) => {
Circulating
-
{stats.data.circulatingValueTotal}
+
{stats.data.circulatingValueTotal.toFixed(1)}
Staked
-
{stats.data?.stakedValueTotal.toString()}
+
{stats.data?.stakedValueTotal.toFixed(1)}
From e80d91f3f177ab42fa69bfc2c7b3f2ce2ae50c47 Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Fri, 5 May 2023 22:52:40 +0200 Subject: [PATCH 10/18] new gs-stats squid --- src/components/stats/StatsChart.tsx | 4 +-- src/components/stats/StatsInfoTable.tsx | 12 ++++---- src/model/stats.ts | 24 +++++++-------- src/networks.json | 5 ++-- src/services/statsService.ts | 40 ++++++++++++------------- 5 files changed, 40 insertions(+), 45 deletions(-) diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index 48726f22..ce5c1200 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -40,7 +40,7 @@ export const StatsChart = (props: StatsChartProps) => { }, { name: "Staked", - value: (stats.data?.stakedValueTotal), + value: (stats.data?.stakingTotalStake), itemStyle: { color: "gray" }, @@ -118,7 +118,7 @@ export const StatsChart = (props: StatsChartProps) => {
Staked
-
{stats.data?.stakedValueTotal.toFixed(1)}
+
{stats.data?.stakingTotalStake.toFixed(1)}
diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 4fc05e87..7d118010 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -62,18 +62,18 @@ export const StatsInfoTable = (props: StatsInfoTableProps) => { return (
- - - + + + - +
diff --git a/src/model/stats.ts b/src/model/stats.ts index afca6763..022178b5 100644 --- a/src/model/stats.ts +++ b/src/model/stats.ts @@ -1,17 +1,15 @@ export type Stats = { - finalizedBlocks: number; - holders: number; - nominationPoolsCountMembers: number; - nominationPoolsCountPools: number; - nominationPoolsTotalStake: number; - signedExtrinsics: number; - stakedValueNominator: number; - stakedValueTotal: number; - stakedValueValidator: number; - totalIssuance: number; - transfersCount: number; - validatorsCount: number; - validatorsIdealCount: number; + chainFinalizedBlocks: number; + nominationPoolsMembersAmount: number; + nominationPoolsPoolsActiveTotalStake: number; + chainSignedExtrinsics: number; + stakingTotalStake: number; + balancesTotalIssuance: number; + balancesTransfersAmount: number; + stakingValidatorsAmount: number; + stakingValidatorsIdealAmount: number; stakedValuePercentage: number; circulatingValueTotal: number; + nominationPoolsCountPools: number; + holders: number; } diff --git a/src/networks.json b/src/networks.json index 2fdbbeb9..0ad8e297 100644 --- a/src/networks.json +++ b/src/networks.json @@ -310,8 +310,7 @@ "archive": "https://kusama.explorer.subsquid.io/graphql", "balances": "https://squid.subsquid.io/kusama-balances/graphql", "explorer": "https://squid.subsquid.io/gs-explorer-kusama/graphql", - "main": "https://squid.subsquid.io/gs-main-kusama/graphql", - "stats": "https://squid.subsquid.io/chain-analytics-squid/v/kusama-multi-parallel-2-0/graphql" + "main": "https://squid.subsquid.io/gs-main-kusama/graphql" }, "coinGeckoId": "kusama" }, @@ -458,7 +457,7 @@ "balances": "https://squid.subsquid.io/polkadot-balances/graphql", "explorer": "https://squid.subsquid.io/gs-explorer-polkadot/graphql", "main": "https://squid.subsquid.io/gs-main-polkadot/graphql", - "stats": "https://squid.subsquid.io/chain-analytics-squid/v/polkadot-multi-parallel-2-0/graphql" + "stats": "https://squid.subsquid.io/gs-stats-polkadot/v/v1122/graphql" }, "coinGeckoId": "polkadot" }, diff --git a/src/services/statsService.ts b/src/services/statsService.ts index fed78ecf..3326991e 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -13,23 +13,19 @@ export async function getStats( order: StatsOrder = "id_DESC", ) { if (hasSupport(network, "stats-squid")) { - const response = await fetchStatsSquid<{totals: Omit[]}>( + const response = await fetchStatsSquid<{currents: Omit[]}>( network, - `query ($filter: TotalsWhereInput, $order: [TotalsOrderByInput!]!) { - totals(limit: 1, where: $filter, orderBy: $order) { - finalizedBlocks - holders - nominationPoolsCountMembers - nominationPoolsCountPools - nominationPoolsTotalStake - signedExtrinsics - stakedValueNominator - stakedValueTotal - stakedValueValidator - totalIssuance - transfersCount - validatorsCount - validatorsIdealCount + `query ($filter: CurrentWhereInput, $order: [CurrentOrderByInput!]!) { + currents(limit: 1, where: $filter, orderBy: $order) { + chainFinalizedBlocks + nominationPoolsMembersAmount + nominationPoolsPoolsActiveTotalStake + chainSignedExtrinsics + stakingTotalStake + balancesTotalIssuance + balancesTransfersAmount + stakingValidatorsAmount + stakingValidatorsIdealAmount } }`, { @@ -38,8 +34,8 @@ export async function getStats( } ); - if(response.totals[0]) { - return unifyStats(response.totals[0], network); + if(response.currents[0]) { + return unifyStats(response.currents[0], network); } } @@ -53,9 +49,11 @@ function unifyStats(stats: Omit Date: Tue, 16 May 2023 14:49:06 +0200 Subject: [PATCH 11/18] Missing "key" prop for element in array fixed --- src/components/network-select/NetworkSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/network-select/NetworkSelect.tsx b/src/components/network-select/NetworkSelect.tsx index 161860ac..21babd47 100644 --- a/src/components/network-select/NetworkSelect.tsx +++ b/src/components/network-select/NetworkSelect.tsx @@ -69,7 +69,7 @@ const NetworkSelect = (props: NetworkSelectProps) => { > {networkGroups.map((group, index) => [ index > 0 && , - + {group.relayChainNetwork?.displayName || "Other"} {group.relayChainNetwork && and parachains} , From 442552f7f348d11e04f72b60d18b0ccfffb6179e Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Tue, 16 May 2023 19:21:19 +0200 Subject: [PATCH 12/18] Polkadot dashboard changes --- src/assets/holder.svg | 1 - src/assets/inflation.svg | 1 + src/assets/staking-reward.svg | 1 + src/assets/transfer.svg | 1 - src/components/StatsLayout.tsx | 5 +-- src/components/stats/StatsInfoTable.tsx | 40 ++++++++++++--------- src/components/transfers/TransfersTable.tsx | 2 -- src/model/stats.ts | 5 +-- src/screens/chainDashboard.tsx | 6 ++-- src/services/statsService.ts | 5 +-- 10 files changed, 37 insertions(+), 30 deletions(-) delete mode 100644 src/assets/holder.svg create mode 100644 src/assets/inflation.svg create mode 100644 src/assets/staking-reward.svg delete mode 100644 src/assets/transfer.svg diff --git a/src/assets/holder.svg b/src/assets/holder.svg deleted file mode 100644 index e9d68c14..00000000 --- a/src/assets/holder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/inflation.svg b/src/assets/inflation.svg new file mode 100644 index 00000000..bd427c56 --- /dev/null +++ b/src/assets/inflation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/staking-reward.svg b/src/assets/staking-reward.svg new file mode 100644 index 00000000..7c4a7434 --- /dev/null +++ b/src/assets/staking-reward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/transfer.svg b/src/assets/transfer.svg deleted file mode 100644 index 72a05aa9..00000000 --- a/src/assets/transfer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/StatsLayout.tsx b/src/components/StatsLayout.tsx index 7ba0fa35..613748ea 100644 --- a/src/components/StatsLayout.tsx +++ b/src/components/StatsLayout.tsx @@ -4,8 +4,9 @@ import { css } from "@emotion/react"; const StatStyle = css` min-width: 100px; width: 100%; - height: 64px; + display: flex; + align-items: center; `; const StatIconStyle = css` @@ -41,7 +42,7 @@ export function StatItem (props: StatItemProps) { return (
-
+
{title}
{value}
diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 7d118010..c892e007 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -9,13 +9,16 @@ import { StatItem } from "../StatsLayout"; import Block from "../../assets/block.svg"; import Signed from "../../assets/signed.svg"; -import Transfer from "../../assets/transfer.svg"; -import Holder from "../../assets/holder.svg"; import Nominator from "../../assets/nominator.svg"; import Stake from "../../assets/stake.svg"; import Validator from "../../assets/validator.svg"; import Token from "../../assets/token.svg"; +import Inflation from "../../assets/inflation.svg"; +import StakingReward from "../../assets/staking-reward.svg"; + import { css } from "@emotion/react"; +import { formatCurrency } from "../../utils/number"; +import { getNetwork } from "../../services/networksService"; const StatsLayoutStyle = css` display: grid; @@ -24,19 +27,22 @@ const StatsLayoutStyle = css` gap: 10px; - grid-template-columns: repeat(4, auto); + grid-template-columns: repeat(2, auto); + - @media (max-width: 1500px) { - grid-template-columns: repeat(2, auto); - } `; export type StatsInfoTableProps = { stats: Resource; + networkName: string; } -export const StatsInfoTable = (props: StatsInfoTableProps) => { - const { stats } = props; +export const StatsInfoTable = ( + props: StatsInfoTableProps, +) => { + const { stats, networkName } = props; + + const network = getNetwork(networkName); if (stats.loading) { return ; @@ -46,7 +52,7 @@ export const StatsInfoTable = (props: StatsInfoTableProps) => { return Stats not found.; } - if (stats.error) { + if (stats.error || !network) { return ( { if (!stats.data) { return null; } - + return (
- - - - - + + + + + - +
); }; diff --git a/src/components/transfers/TransfersTable.tsx b/src/components/transfers/TransfersTable.tsx index e855c129..18dbf81a 100644 --- a/src/components/transfers/TransfersTable.tsx +++ b/src/components/transfers/TransfersTable.tsx @@ -1,11 +1,9 @@ -import { Extrinsic } from "../../model/extrinsic"; import { PaginatedResource } from "../../model/paginatedResource"; import { Transfer } from "../../model/transfer"; import { Chip } from "@mui/material"; import CrossIcon from "../../assets/cross-icon.png"; import CheckIcon from "../../assets/check-icon.png"; import { AccountAddress } from "../AccountAddress"; -import { ButtonLink } from "../ButtonLink"; import { ItemsTable, ItemsTableAttribute } from "../ItemsTable"; import { Link } from "../Link"; import { Time } from "../Time"; diff --git a/src/model/stats.ts b/src/model/stats.ts index 022178b5..7b57ef0b 100644 --- a/src/model/stats.ts +++ b/src/model/stats.ts @@ -10,6 +10,7 @@ export type Stats = { stakingValidatorsIdealAmount: number; stakedValuePercentage: number; circulatingValueTotal: number; - nominationPoolsCountPools: number; - holders: number; + stakingInflationRatio: number; + stakingRewardsRatio: number; + nominationPoolsPoolsActiveAmount: number; } diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index 7655a52b..ae8a0eef 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -44,16 +44,16 @@ export const ChainDashboardPage = () => { - {network.displayName} dashboard + {network.displayName} {hasSupport(network.name, "stats-squid") && - + } {hasSupport(network.name, "stats-squid") && - {network.displayName} statistics + Token distribution diff --git a/src/services/statsService.ts b/src/services/statsService.ts index 3326991e..69ee19fc 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -26,6 +26,9 @@ export async function getStats( balancesTransfersAmount stakingValidatorsAmount stakingValidatorsIdealAmount + stakingInflationRatio + stakingRewardsRatio + nominationPoolsPoolsActiveAmount } }`, { @@ -53,7 +56,5 @@ function unifyStats(stats: Omit Date: Tue, 16 May 2023 19:46:37 +0200 Subject: [PATCH 13/18] Token distribution update --- src/components/stats/StatsChart.tsx | 25 +++++++++++++++---------- src/components/stats/StatsInfoTable.tsx | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index ce5c1200..0d0a9a8b 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -11,6 +11,7 @@ import Loading from "../Loading"; import NotFound from "../NotFound"; import { ErrorMessage } from "../ErrorMessage"; import { notFoundStyle, separatorStyle, valuesStyle, valueStyle, valueTypeStyle, chartStyle, chartMarginStyle } from "../ChartStyles"; +import { formatCurrency } from "../../utils/number"; export type StatsChartProps = HTMLAttributes & { stats: Resource; @@ -44,6 +45,13 @@ export const StatsChart = (props: StatsChartProps) => { itemStyle: { color: "gray" }, + }, + { + name: "Other", + value: (stats.data?.balancesTotalIssuance - stats.data.circulatingValueTotal - stats.data?.stakingTotalStake), + itemStyle: { + color: "lightgray" + }, } ]; }, [stats]); @@ -59,7 +67,10 @@ export const StatsChart = (props: StatsChartProps) => { return `
${name}
-
${value}
+
${ + typeof value === "number" ? + formatCurrency(value, network ? network.symbol : "", { decimalPlaces: "optimal" }) + : value}
`; } }, @@ -105,22 +116,16 @@ export const StatsChart = (props: StatsChartProps) => { ); } - if (!stats.data) { + if (!stats.data || !network) { return null; } return (<>
-
Circulating
-
{stats.data.circulatingValueTotal.toFixed(1)}
-
-
-
-
Staked
-
{stats.data?.stakingTotalStake.toFixed(1)}
+
Total issuance
+
{formatCurrency(stats.data.balancesTotalIssuance, network.symbol, { decimalPlaces: "optimal" })}
-
Stats not found.; } - if (stats.error || !network) { + if (stats.error) { return ( Date: Tue, 16 May 2023 19:54:21 +0200 Subject: [PATCH 14/18] stakingActiveValidatorsAmount --- src/components/stats/StatsInfoTable.tsx | 2 +- src/model/stats.ts | 2 +- src/services/statsService.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index dc3f2af8..9cc3736a 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -79,7 +79,7 @@ export const StatsInfoTable = (
diff --git a/src/model/stats.ts b/src/model/stats.ts index 7b57ef0b..58be53f3 100644 --- a/src/model/stats.ts +++ b/src/model/stats.ts @@ -6,7 +6,7 @@ export type Stats = { stakingTotalStake: number; balancesTotalIssuance: number; balancesTransfersAmount: number; - stakingValidatorsAmount: number; + stakingActiveValidatorsAmount: number; stakingValidatorsIdealAmount: number; stakedValuePercentage: number; circulatingValueTotal: number; diff --git a/src/services/statsService.ts b/src/services/statsService.ts index 69ee19fc..d2874200 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -24,7 +24,7 @@ export async function getStats( stakingTotalStake balancesTotalIssuance balancesTransfersAmount - stakingValidatorsAmount + stakingActiveValidatorsAmount stakingValidatorsIdealAmount stakingInflationRatio stakingRewardsRatio From 18c958e44981f206e9042618bbc2162eb690a35d Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Tue, 16 May 2023 20:04:16 +0200 Subject: [PATCH 15/18] chain icon --- src/components/ChainIcon.tsx | 31 +++++++++++++++++++++++++++++++ src/screens/chainDashboard.tsx | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 src/components/ChainIcon.tsx diff --git a/src/components/ChainIcon.tsx b/src/components/ChainIcon.tsx new file mode 100644 index 00000000..0bf1af9a --- /dev/null +++ b/src/components/ChainIcon.tsx @@ -0,0 +1,31 @@ +/** @jsxImportSource @emotion/react */ +import { css } from "@emotion/react"; +import { getNetwork } from "../services/networksService"; + +const iconStyle = css` + cursor: default !important; + vertical-align: text-bottom; + margin-right: 8px; + height: 32px; +`; + +export type ChainIconProps = { + networkName: string; +} + +export const ChainIcon = (props: ChainIconProps) => { + const {networkName} = props; + + const network = getNetwork(networkName); + + if (!network) { + return null; + } + + return ( + + ); +}; diff --git a/src/screens/chainDashboard.tsx b/src/screens/chainDashboard.tsx index ae8a0eef..23f17110 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/chainDashboard.tsx @@ -17,6 +17,7 @@ import { StatsInfoTable } from "../components/stats/StatsInfoTable"; import { StatsChart } from "../components/stats/StatsChart"; import { useUsdRates } from "../hooks/useUsdRates"; import { useRootLoaderData } from "../hooks/useRootLoaderData"; +import { ChainIcon } from "../components/ChainIcon"; export const ChainDashboardPage = () => { const { network } = useRootLoaderData(); @@ -44,6 +45,7 @@ export const ChainDashboardPage = () => { + {network.displayName} {hasSupport(network.name, "stats-squid") && From 24fef7fb0524b1d88b09a21cdfe808e9f172778f Mon Sep 17 00:00:00 2001 From: rostislavlitovkin Date: Wed, 17 May 2023 10:11:48 +0200 Subject: [PATCH 16/18] issuance and staked value deleted optimised for mobile --- src/assets/stake.svg | 1 - src/assets/token.svg | 1 - src/components/stats/StatsChart.tsx | 2 +- src/components/stats/StatsInfoTable.tsx | 17 +++++------------ 4 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 src/assets/stake.svg delete mode 100644 src/assets/token.svg diff --git a/src/assets/stake.svg b/src/assets/stake.svg deleted file mode 100644 index d31b0833..00000000 --- a/src/assets/stake.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/token.svg b/src/assets/token.svg deleted file mode 100644 index 946dad70..00000000 --- a/src/assets/token.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx index 0d0a9a8b..45f1dd63 100644 --- a/src/components/stats/StatsChart.tsx +++ b/src/components/stats/StatsChart.tsx @@ -10,7 +10,7 @@ import { getNetwork } from "../../services/networksService"; import Loading from "../Loading"; import NotFound from "../NotFound"; import { ErrorMessage } from "../ErrorMessage"; -import { notFoundStyle, separatorStyle, valuesStyle, valueStyle, valueTypeStyle, chartStyle, chartMarginStyle } from "../ChartStyles"; +import { notFoundStyle, valuesStyle, valueStyle, valueTypeStyle, chartStyle, chartMarginStyle } from "../ChartStyles"; import { formatCurrency } from "../../utils/number"; export type StatsChartProps = HTMLAttributes & { diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx index 9cc3736a..9e73b202 100644 --- a/src/components/stats/StatsInfoTable.tsx +++ b/src/components/stats/StatsInfoTable.tsx @@ -6,18 +6,14 @@ import { ErrorMessage } from "../ErrorMessage"; import Loading from "../Loading"; import NotFound from "../NotFound"; import { StatItem } from "../StatsLayout"; - import Block from "../../assets/block.svg"; import Signed from "../../assets/signed.svg"; import Nominator from "../../assets/nominator.svg"; -import Stake from "../../assets/stake.svg"; import Validator from "../../assets/validator.svg"; -import Token from "../../assets/token.svg"; import Inflation from "../../assets/inflation.svg"; import StakingReward from "../../assets/staking-reward.svg"; import { css } from "@emotion/react"; -import { formatCurrency } from "../../utils/number"; import { getNetwork } from "../../services/networksService"; const StatsLayoutStyle = css` @@ -29,7 +25,9 @@ const StatsLayoutStyle = css` grid-template-columns: repeat(2, auto); - + @media (max-width: 530px) { + grid-template-columns: repeat(1, auto); + } `; export type StatsInfoTableProps = { @@ -68,15 +66,10 @@ export const StatsInfoTable = ( return (
- - - - + + Date: Thu, 18 May 2023 17:28:26 +0200 Subject: [PATCH 17/18] refactoring & network token chart enhancements --- scripts/update-networks/config/squids.ts | 5 +- src/components/ChainIcon.tsx | 31 ---- src/components/ChartStyles.tsx | 110 ------------- .../{network-select => }/NetworkSelect.tsx | 4 +- src/components/SearchInput.tsx | 2 +- src/components/StatsLayout.tsx | 51 ------ src/components/account/AccountPortfolio.tsx | 82 +++++++++- src/components/network/NetworkStats.tsx | 128 +++++++++++++++ .../network/NetworkTokenDistribution.tsx | 91 +++++++++++ .../network/NetworkTokenDistributionChart.tsx | 147 ++++++++++++++++++ src/components/stats/StatsChart.tsx | 140 ----------------- src/components/stats/StatsInfoTable.tsx | 80 ---------- src/router.tsx | 4 +- .../{chainDashboard.tsx => network.tsx} | 35 ++++- src/services/statsService.ts | 14 +- src/utils/number.ts | 33 ++-- 16 files changed, 503 insertions(+), 454 deletions(-) delete mode 100644 src/components/ChainIcon.tsx delete mode 100644 src/components/ChartStyles.tsx rename src/components/{network-select => }/NetworkSelect.tsx (94%) delete mode 100644 src/components/StatsLayout.tsx create mode 100644 src/components/network/NetworkStats.tsx create mode 100644 src/components/network/NetworkTokenDistribution.tsx create mode 100644 src/components/network/NetworkTokenDistributionChart.tsx delete mode 100644 src/components/stats/StatsChart.tsx delete mode 100644 src/components/stats/StatsInfoTable.tsx rename src/screens/{chainDashboard.tsx => network.tsx} (82%) diff --git a/scripts/update-networks/config/squids.ts b/scripts/update-networks/config/squids.ts index deb0ae76..a9cc876d 100644 --- a/scripts/update-networks/config/squids.ts +++ b/scripts/update-networks/config/squids.ts @@ -8,10 +8,7 @@ export const squidUrlTemplates: Record string> = { }; export const forceSquidUrl: Record> = { - "kusama": { - "stats": "https://squid.subsquid.io/chain-analytics-squid/v/kusama-multi-parallel-2-0/graphql" - }, "polkadot": { - "stats": "https://squid.subsquid.io/chain-analytics-squid/v/polkadot-multi-parallel-2-0/graphql" + "stats": "https://squid.subsquid.io/gs-stats-polkadot/v/v1122/graphql" } }; diff --git a/src/components/ChainIcon.tsx b/src/components/ChainIcon.tsx deleted file mode 100644 index 0bf1af9a..00000000 --- a/src/components/ChainIcon.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { css } from "@emotion/react"; -import { getNetwork } from "../services/networksService"; - -const iconStyle = css` - cursor: default !important; - vertical-align: text-bottom; - margin-right: 8px; - height: 32px; -`; - -export type ChainIconProps = { - networkName: string; -} - -export const ChainIcon = (props: ChainIconProps) => { - const {networkName} = props; - - const network = getNetwork(networkName); - - if (!network) { - return null; - } - - return ( - - ); -}; diff --git a/src/components/ChartStyles.tsx b/src/components/ChartStyles.tsx deleted file mode 100644 index 705adf11..00000000 --- a/src/components/ChartStyles.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { css, Theme } from "@emotion/react"; - -export const switchStyle = (theme: Theme) => css` - position: absolute; - top: 48px; - right: 48px; - margin: 4px 0; - - .MuiToggleButton-root { - padding: 0 10px; - font-size: 14px; - } - - ${theme.breakpoints.down("md")} { - top: 24px; - right: 24px; - } - - ${theme.breakpoints.down("sm")} { - position: relative; - top: -28px; - right: auto; - display: flex; - justify-content: center; - } -`; - -export const valuesStyle = (theme: Theme) => css` - position: relative; - display: flex; - flex-direction: row; - align-items: stretch; - justify-content: space-around; - - ${theme.breakpoints.down("sm")} { - display: block; - } -`; - -export const valueStyle = (theme: Theme) => css` - min-width: 70px; - - ${theme.breakpoints.down("sm")} { - display: flex; - max-width: 230px; - margin: 0 auto; - } -`; - -export const valueTypeStyle = (theme: Theme) => css` - margin-bottom: 8px; - font-weight: 700; - - ${theme.breakpoints.down("sm")} { - flex: 1 1 auto; - } -`; - -export const separatorStyle = css` - display: block; - width: 1px; - flex: 0 0 auto; - background-color: rgba(0, 0, 0, .125); -`; - -export const chartMarginStyle = css` - margin: 0 auto; - margin-top: 32px; -`; - -export const notFoundStyle = css` - margin: 0 auto; - max-width: 300px; -`; - -export const chartStyle = (theme: Theme) => css` - width: 400px; - height: 230px; - - ${theme.breakpoints.down("sm")} { - width: 230px; - height: 270px; - } - - .ECharts-tooltip { - [data-class=title] { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; - font-weight: 600; - font-size: 15px; - } - - [data-class=icon] { - height: 24px; - width: 24px; - object-fit: contain; - } - - [data-class=value] { - font-size: 15px; - } - - [data-class=usd-value] { - font-size: 14px; - opacity: .75; - } - } -`; diff --git a/src/components/network-select/NetworkSelect.tsx b/src/components/NetworkSelect.tsx similarity index 94% rename from src/components/network-select/NetworkSelect.tsx rename to src/components/NetworkSelect.tsx index 21babd47..15411c67 100644 --- a/src/components/network-select/NetworkSelect.tsx +++ b/src/components/NetworkSelect.tsx @@ -3,8 +3,8 @@ import { useCallback, useEffect } from "react"; import { Divider, ListItemIcon, ListItemText, ListSubheader, MenuItem, Select, SelectProps } from "@mui/material"; import { css } from "@emotion/react"; -import { useNetworks } from "../../hooks/useNetworks"; -import { useNetworkGroups } from "../../hooks/useNetworkGroups"; +import { useNetworks } from "../hooks/useNetworks"; +import { useNetworkGroups } from "../hooks/useNetworkGroups"; const selectStyle = css` &.MuiInputBase-root { diff --git a/src/components/SearchInput.tsx b/src/components/SearchInput.tsx index 43319363..40a2ec51 100644 --- a/src/components/SearchInput.tsx +++ b/src/components/SearchInput.tsx @@ -5,7 +5,7 @@ import { Button, FormGroup, TextField } from "@mui/material"; import SearchIcon from "@mui/icons-material/Search"; import { css, Theme } from "@emotion/react"; -import NetworkSelect from "./network-select/NetworkSelect"; +import NetworkSelect from "./NetworkSelect"; const formGroupStyle = css` flex-direction: row; diff --git a/src/components/StatsLayout.tsx b/src/components/StatsLayout.tsx deleted file mode 100644 index 613748ea..00000000 --- a/src/components/StatsLayout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { css } from "@emotion/react"; - -const StatStyle = css` - min-width: 100px; - width: 100%; - - display: flex; - align-items: center; -`; - -const StatIconStyle = css` - width: 44px; - height: 44px; - margin-right: 10px; - padding: 10px; -`; - -const StatTitleStyle = css` - font-weight: 900; - margin-top: auto; -`; - -const StatValueStyle = css` - font-weight: 500; - height: 32px; -`; - -export type StatItemProps = { - title: string; - icon?: string; - value?: string | number; -}; - -export function StatItem (props: StatItemProps) { - const { - title, - value, - icon, - } = props; - - return ( -
- -
-
{title}
-
{value}
-
-
- ); -} \ No newline at end of file diff --git a/src/components/account/AccountPortfolio.tsx b/src/components/account/AccountPortfolio.tsx index f51736b1..1a3d450b 100644 --- a/src/components/account/AccountPortfolio.tsx +++ b/src/components/account/AccountPortfolio.tsx @@ -1,16 +1,93 @@ /** @jsxImportSource @emotion/react */ import { HTMLAttributes, useMemo, useState } from "react"; import { ToggleButton, ToggleButtonGroup } from "@mui/material"; +import { Theme, css } from "@emotion/react"; + import { AccountBalance } from "../../model/accountBalance"; import { Resource } from "../../model/resource"; import { UsdRates } from "../../model/usdRates"; import { usdBalanceSum } from "../../utils/balance"; import { formatCurrency } from "../../utils/number"; + import { ErrorMessage } from "../ErrorMessage"; import Loading from "../Loading"; import NotFound from "../NotFound"; + import { AccountPortfolioChartMode, AccountPortfolioChart } from "./AccountPortfolioChart"; -import { chartMarginStyle, notFoundStyle, separatorStyle, switchStyle, valuesStyle, valueStyle, valueTypeStyle } from "../ChartStyles"; + +export const chartStyle = css` + margin: 0 auto; + margin-top: 32px; +`; + +export const valuesStyle = (theme: Theme) => css` + position: relative; + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: space-around; + + ${theme.breakpoints.down("sm")} { + display: block; + } +`; + +export const valueStyle = (theme: Theme) => css` + min-width: 70px; + + ${theme.breakpoints.down("sm")} { + display: flex; + max-width: 230px; + margin: 0 auto; + } +`; + +export const valueTypeStyle = (theme: Theme) => css` + margin-bottom: 8px; + font-weight: 700; + + ${theme.breakpoints.down("sm")} { + flex: 1 1 auto; + } +`; + +export const separatorStyle = css` + display: block; + width: 1px; + flex: 0 0 auto; + background-color: rgba(0, 0, 0, .125); +`; + + +export const notFoundStyle = css` + margin: 0 auto; + max-width: 300px; +`; + +export const switchStyle = (theme: Theme) => css` + position: absolute; + top: 48px; + right: 48px; + margin: 4px 0; + + .MuiToggleButton-root { + padding: 0 10px; + font-size: 14px; + } + + ${theme.breakpoints.down("md")} { + top: 24px; + right: 24px; + } + + ${theme.breakpoints.down("sm")} { + position: relative; + top: -28px; + right: auto; + display: flex; + justify-content: center; + } +`; export type AccountPortfolioProps = HTMLAttributes & { balances: Resource; @@ -73,10 +150,9 @@ export const AccountPortfolio = (props: AccountPortfolioProps) => {
Reserved
{formatCurrency(stats.reserved, "USD", {decimalPlaces: "optimal"})}
-
{ + const { + title, + value, + icon, + } = props; + + return ( +
+ +
+
{title}
+
{value}
+
+
+ ); +}; + +export type NetworkInfoTableProps = { + stats: Resource; + networkName: string; +} + +export const NetworkStats = (props: NetworkInfoTableProps) => { + const { stats, networkName } = props; + + const network = getNetwork(networkName); + + if (stats.loading) { + return ; + } + + if (stats.notFound) { + return Stats not found.; + } + + if (stats.error) { + return ( + + ); + } + + if (!stats.data || !network) { + return null; + } + + return ( +
+ + + + + + +
+ ); +}; diff --git a/src/components/network/NetworkTokenDistribution.tsx b/src/components/network/NetworkTokenDistribution.tsx new file mode 100644 index 00000000..c5add34a --- /dev/null +++ b/src/components/network/NetworkTokenDistribution.tsx @@ -0,0 +1,91 @@ +/** @jsxImportSource @emotion/react */ +import { HTMLAttributes } from "react"; +import { css } from "@emotion/react"; + +import { Resource } from "../../model/resource"; +import { Stats } from "../../model/stats"; +import { Network } from "../../model/network"; +import { formatNumber } from "../../utils/number"; + +import { ErrorMessage } from "../ErrorMessage"; +import Loading from "../Loading"; +import NotFound from "../NotFound"; + +import { NetworkTokenDistributionChart } from "./NetworkTokenDistributionChart"; + +export const valueStyle = css` + min-width: 70px; + display: flex; + max-width: 230px; + margin: 0 auto; +`; + +export const valueTypeStyle = css` + margin-bottom: 8px; + font-weight: 700; + flex: 1 1 auto; +`; + +export const separatorStyle = css` + display: block; + width: 1px; + flex: 0 0 auto; + background-color: rgba(0, 0, 0, .125); +`; + +export const notFoundStyle = css` + margin: 0 auto; + margin-top: 32px; +`; + +export const chartStyle = css` + margin: 0 auto; + margin-top: 32px; +`; + +export type NetworkTokenDistributionProps = HTMLAttributes & { + stats: Resource; + network: Network; +}; + +export const NetworkTokenDistribution = (props: NetworkTokenDistributionProps) => { + const {stats, network} = props; + + if (stats.loading) { + return ; + } + + if (stats.notFound) { + return No stats data found; + } + + if (stats.error) { + return ( + + ); + } + + if (!stats.data) { + return null; + } + + return ( +
+
+
+
Total issuance
+
{formatNumber(stats.data.balancesTotalIssuance, {compact: true})}
+
+
+ +
+ ); +}; diff --git a/src/components/network/NetworkTokenDistributionChart.tsx b/src/components/network/NetworkTokenDistributionChart.tsx new file mode 100644 index 00000000..cd254ed8 --- /dev/null +++ b/src/components/network/NetworkTokenDistributionChart.tsx @@ -0,0 +1,147 @@ +/** @jsxImportSource @emotion/react */ +import { HTMLAttributes, useMemo } from "react"; +import { lighten, useMediaQuery } from "@mui/material"; +import { css, useTheme } from "@emotion/react"; +import { CallbackDataParams } from "echarts/types/dist/shared"; + +import { formatNumber } from "../../utils/number"; +import { Network } from "../../model/network"; +import { Stats } from "../../model/stats"; + +import { PieChart, PieChartOptions } from "../PieChart"; + +export const chartStyle = css` + .ECharts-tooltip { + [data-class=title] { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + font-weight: 600; + font-size: 15px; + } + + [data-class=value] { + font-size: 15px; + } + } +`; + +export type NetworkTokenDistributionChartProps = HTMLAttributes & { + stats: Stats; + network: Network; +} + +export const NetworkTokenDistributionChart = (props: NetworkTokenDistributionChartProps) => { + const { stats, network, ...divProps } = props; + + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm")); + + const totalData = useMemo(() => { + if (!stats) { + return []; + } + + return [ + { + name: "Circulating", + value: stats.circulatingValueTotal, + itemStyle: { + color: network.color || theme.palette.primary.main, + } + }, + { + name: "Staked", + value: stats.stakingTotalStake, + itemStyle: { + color: lighten(network.color || theme.palette.primary.main, .5), + }, + }, + { + name: "Other", + value: stats.balancesTotalIssuance - stats.circulatingValueTotal - stats.stakingTotalStake, + itemStyle: { + color: "lightgray" + }, + } + ].filter(it => it.value > 0); + }, [stats]); + + + const options = useMemo(() => { + return { + title: { + text: network.symbol, + left: 110, + top: 98, + textAlign: "center", + textStyle: { + fontWeight: 400, + fontSize: 22, + fontFamily: "Open Sans" + } + }, + tooltip: { + formatter: (params) => { + const { name, value, percent } = params as CallbackDataParams; + + console.log("PARAMS: ", params); + + return ` +
${name}
+
${formatNumber(value as number)} (${percent}%)
+ `; + } + }, + legend: { + formatter: (name) => { + const item = totalData.find(it => it.name === name); + const value = item!.value; + const percent = (value * 100 / stats.balancesTotalIssuance).toFixed(2); + return `${name}\n${formatNumber(value, {compact: true})} (${percent}%)`; + }, + textStyle: { + width: 115,//85, + overflow: "truncate", + lineHeight: 16 + }, + itemHeight: 32, + itemWidth: 14, + orient: "vertical", + top: isSmallScreen ? "auto" : "center", + left: isSmallScreen ? "center" : 265, + bottom: isSmallScreen ? 0 : "auto", + height: isSmallScreen ? "auto" : 140, + }, + series: { + radius: [60, 100], + center: isSmallScreen + ? ["center", 116] + : [116, "center"], + data: [ + ...totalData, + ] + }, + }; + + }, [totalData, isSmallScreen]); + + return ( + + ); +}; diff --git a/src/components/stats/StatsChart.tsx b/src/components/stats/StatsChart.tsx deleted file mode 100644 index 45f1dd63..00000000 --- a/src/components/stats/StatsChart.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { HTMLAttributes, useMemo } from "react"; -import { Resource } from "../../model/resource"; -import { Stats } from "../../model/stats"; -import { PieChart, PieChartOptions } from "../PieChart"; -import { useTheme } from "@emotion/react"; -import { useMediaQuery } from "@mui/material"; -import { CallbackDataParams } from "echarts/types/dist/shared"; -import { getNetwork } from "../../services/networksService"; -import Loading from "../Loading"; -import NotFound from "../NotFound"; -import { ErrorMessage } from "../ErrorMessage"; -import { notFoundStyle, valuesStyle, valueStyle, valueTypeStyle, chartStyle, chartMarginStyle } from "../ChartStyles"; -import { formatCurrency } from "../../utils/number"; - -export type StatsChartProps = HTMLAttributes & { - stats: Resource; - networkName: string; -} - -export const StatsChart = (props: StatsChartProps) => { - const { stats, networkName, ...divProps } = props; - - const network = getNetwork(networkName); - - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm")); - - const totalData = useMemo(() => { - if (!stats.data) { - return []; - } - - return [ - { - name: "Circulating", - value: (stats.data.circulatingValueTotal), - itemStyle: { - color: network?.color || theme.palette.primary.main, - } - }, - { - name: "Staked", - value: (stats.data?.stakingTotalStake), - itemStyle: { - color: "gray" - }, - }, - { - name: "Other", - value: (stats.data?.balancesTotalIssuance - stats.data.circulatingValueTotal - stats.data?.stakingTotalStake), - itemStyle: { - color: "lightgray" - }, - } - ]; - }, [stats]); - - - const options = useMemo(() => { - return { - tooltip: { - formatter: (params) => { - const { name, value } = params as CallbackDataParams; - - console.log("PARAMS: ", params); - - return ` -
${name}
-
${ - typeof value === "number" ? - formatCurrency(value, network ? network.symbol : "", { decimalPlaces: "optimal" }) - : value}
- `; - } - }, - legend: { - textStyle: { - width: 85, - overflow: "truncate" - }, - orient: isSmallScreen ? "horizontal" : "vertical", - top: isSmallScreen ? "auto" : "center", - left: isSmallScreen ? "center" : 275, - bottom: isSmallScreen ? 0 : "auto", - height: isSmallScreen ? "auto" : 140 - }, - series: { - radius: [60, 100], - center: isSmallScreen - ? ["center", 116] - : [116, "center"], - data: [ - ...totalData, - ] - }, - }; - - }, [totalData, isSmallScreen]); - - if (stats.loading) { - return ; - } - - if (stats.notFound) { - return Stats not found.; - } - - if (stats.error) { - return ( - - ); - } - - if (!stats.data || !network) { - return null; - } - - return (<> -
-
-
Total issuance
-
{formatCurrency(stats.data.balancesTotalIssuance, network.symbol, { decimalPlaces: "optimal" })}
-
-
-
- -
- - ); -}; diff --git a/src/components/stats/StatsInfoTable.tsx b/src/components/stats/StatsInfoTable.tsx deleted file mode 100644 index 9e73b202..00000000 --- a/src/components/stats/StatsInfoTable.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { Resource } from "../../model/resource"; -import { Stats } from "../../model/stats"; -import { notFoundStyle } from "../ChartStyles"; -import { ErrorMessage } from "../ErrorMessage"; -import Loading from "../Loading"; -import NotFound from "../NotFound"; -import { StatItem } from "../StatsLayout"; -import Block from "../../assets/block.svg"; -import Signed from "../../assets/signed.svg"; -import Nominator from "../../assets/nominator.svg"; -import Validator from "../../assets/validator.svg"; -import Inflation from "../../assets/inflation.svg"; -import StakingReward from "../../assets/staking-reward.svg"; - -import { css } from "@emotion/react"; -import { getNetwork } from "../../services/networksService"; - -const StatsLayoutStyle = css` - display: grid; - width: 100%; - height: auto; - - gap: 10px; - - grid-template-columns: repeat(2, auto); - - @media (max-width: 530px) { - grid-template-columns: repeat(1, auto); - } -`; - -export type StatsInfoTableProps = { - stats: Resource; - networkName: string; -} - -export const StatsInfoTable = ( - props: StatsInfoTableProps, -) => { - const { stats, networkName } = props; - - const network = getNetwork(networkName); - - if (stats.loading) { - return ; - } - - if (stats.notFound) { - return Stats not found.; - } - - if (stats.error) { - return ( - - ); - } - - if (!stats.data || !network) { - return null; - } - - return ( -
- - - - - - -
- ); -}; diff --git a/src/router.tsx b/src/router.tsx index 2f2b8266..a8826ffe 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -7,7 +7,7 @@ import { encodeAddress } from "./utils/formatAddress"; import { AccountPage } from "./screens/account"; import { BlockPage } from "./screens/block"; import { CallPage } from "./screens/call"; -import { ChainDashboardPage } from "./screens/chainDashboard"; +import { NetworkPage } from "./screens/network"; import { EventPage } from "./screens/event"; import { ExtrinsicPage } from "./screens/extrinsic"; import { HomePage } from "./screens/home"; @@ -34,7 +34,7 @@ export const router = createBrowserRouter([ children: [ { index: true, - element: , + element: , }, { path: "extrinsic/:id", diff --git a/src/screens/chainDashboard.tsx b/src/screens/network.tsx similarity index 82% rename from src/screens/chainDashboard.tsx rename to src/screens/network.tsx index 23f17110..c9dd242a 100644 --- a/src/screens/chainDashboard.tsx +++ b/src/screens/network.tsx @@ -1,5 +1,7 @@ /** @jsxImportSource @emotion/react */ import { useEffect } from "react"; +import { Theme, css } from "@emotion/react"; + import ExtrinsicsTable from "../components/extrinsics/ExtrinsicsTable"; import { Card, CardHeader, CardRow } from "../components/Card"; import { useExtrinsicsWithoutTotalCount } from "../hooks/useExtrinsicsWithoutTotalCount"; @@ -13,13 +15,27 @@ import BlocksTable from "../components/blocks/BlocksTable"; import { useBalances } from "../hooks/useBalances"; import BalancesTable from "../components/balances/BalancesTable"; import { useStats } from "../hooks/useStats"; -import { StatsInfoTable } from "../components/stats/StatsInfoTable"; -import { StatsChart } from "../components/stats/StatsChart"; +import { NetworkStats } from "../components/network/NetworkStats"; import { useUsdRates } from "../hooks/useUsdRates"; import { useRootLoaderData } from "../hooks/useRootLoaderData"; -import { ChainIcon } from "../components/ChainIcon"; +import { NetworkTokenDistribution } from "../components/network/NetworkTokenDistribution"; + +const networkIconStyle = css` + vertical-align: text-bottom; + margin-right: 8px; + height: 32px; +`; + +const tokenDistributionStyle = (theme: Theme) => css` + flex: 0 0 auto; + width: 400px; + + ${theme.breakpoints.down("lg")} { + width: auto; + } +`; -export const ChainDashboardPage = () => { +export const NetworkPage = () => { const { network } = useRootLoaderData(); const extrinsics = useExtrinsicsWithoutTotalCount(network.name, undefined, "id_DESC"); @@ -45,19 +61,22 @@ export const ChainDashboardPage = () => { - + {network.displayName} {hasSupport(network.name, "stats-squid") && - + } {hasSupport(network.name, "stats-squid") && - + Token distribution - + } diff --git a/src/services/statsService.ts b/src/services/statsService.ts index d2874200..8c34b395 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -3,7 +3,7 @@ import { rawAmountToDecimal } from "../utils/number"; import { fetchStatsSquid} from "./fetchService"; import { getNetwork, hasSupport } from "./networksService"; -export type StatsFilter = any +export type StatsFilter = any; export type StatsOrder = string | string[]; @@ -16,7 +16,7 @@ export async function getStats( const response = await fetchStatsSquid<{currents: Omit[]}>( network, `query ($filter: CurrentWhereInput, $order: [CurrentOrderByInput!]!) { - currents(limit: 1, where: $filter, orderBy: $order) { + currents(limit: 1, where: $filter, orderBy: $order) { chainFinalizedBlocks nominationPoolsMembersAmount nominationPoolsPoolsActiveTotalStake @@ -25,12 +25,12 @@ export async function getStats( balancesTotalIssuance balancesTransfersAmount stakingActiveValidatorsAmount - stakingValidatorsIdealAmount + stakingValidatorsIdealAmount stakingInflationRatio - stakingRewardsRatio + stakingRewardsRatio nominationPoolsPoolsActiveAmount - } - }`, + } + }`, { filter, order, @@ -57,4 +57,4 @@ function unifyStats(stats: Omit Date: Thu, 18 May 2023 17:33:48 +0200 Subject: [PATCH 18/18] clean --- src/components/account/AccountPortfolio.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/account/AccountPortfolio.tsx b/src/components/account/AccountPortfolio.tsx index 1a3d450b..68c139c5 100644 --- a/src/components/account/AccountPortfolio.tsx +++ b/src/components/account/AccountPortfolio.tsx @@ -15,12 +15,12 @@ import NotFound from "../NotFound"; import { AccountPortfolioChartMode, AccountPortfolioChart } from "./AccountPortfolioChart"; -export const chartStyle = css` +const chartStyle = css` margin: 0 auto; margin-top: 32px; `; -export const valuesStyle = (theme: Theme) => css` +const valuesStyle = (theme: Theme) => css` position: relative; display: flex; flex-direction: row; @@ -32,7 +32,7 @@ export const valuesStyle = (theme: Theme) => css` } `; -export const valueStyle = (theme: Theme) => css` +const valueStyle = (theme: Theme) => css` min-width: 70px; ${theme.breakpoints.down("sm")} { @@ -42,7 +42,7 @@ export const valueStyle = (theme: Theme) => css` } `; -export const valueTypeStyle = (theme: Theme) => css` +const valueTypeStyle = (theme: Theme) => css` margin-bottom: 8px; font-weight: 700; @@ -51,7 +51,7 @@ export const valueTypeStyle = (theme: Theme) => css` } `; -export const separatorStyle = css` +const separatorStyle = css` display: block; width: 1px; flex: 0 0 auto; @@ -59,12 +59,12 @@ export const separatorStyle = css` `; -export const notFoundStyle = css` +const notFoundStyle = css` margin: 0 auto; max-width: 300px; `; -export const switchStyle = (theme: Theme) => css` +const switchStyle = (theme: Theme) => css` position: absolute; top: 48px; right: 48px;