@@ -4,10 +4,12 @@ import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton";
44import {
55 type ChartConfig ,
66 ChartContainer ,
7+ ChartLegend ,
8+ ChartLegendContent ,
79 ChartTooltip ,
810 ChartTooltipContent ,
911} from "@/components/ui/chart" ;
10- import type { UserOpStats } from "@3rdweb-sdk/react/hooks/useApi" ;
12+ import type { UserOpStatsByChain } from "@3rdweb-sdk/react/hooks/useApi" ;
1113import {
1214 EmptyChartState ,
1315 LoadingChartState ,
@@ -20,44 +22,91 @@ import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon";
2022import { DocLink } from "components/shared/DocLink" ;
2123import { format } from "date-fns" ;
2224import { useMemo } from "react" ;
23- import { Bar , BarChart , CartesianGrid , LabelList , XAxis } from "recharts" ;
25+ import { Bar , BarChart , CartesianGrid , XAxis } from "recharts" ;
26+ import { useAllChainsData } from "../../../hooks/chains/allChains" ;
2427
25- type ChartData = {
28+ type ChartData = Record < string , number > & {
2629 time : string ; // human readable date
27- sponsoredUsd : number ;
2830} ;
2931
30- const chartConfig = {
31- sponsoredUsd : {
32- label : "Total Sponsored" ,
33- color : "hsl(var(--chart-1))" ,
34- } ,
35- } satisfies ChartConfig ;
36-
3732export function TotalSponsoredChartCard ( props : {
38- userOpStats : UserOpStats [ ] ;
33+ userOpStats : UserOpStatsByChain [ ] ;
3934 isPending : boolean ;
4035} ) {
4136 const { userOpStats } = props ;
42- const barChartData : ChartData [ ] = useMemo ( ( ) => {
43- const chartDataMap : Map < string , ChartData > = new Map ( ) ;
37+ const topChainsToShow = 10 ;
38+ const chainsStore = useAllChainsData ( ) ;
39+
40+ const { chartConfig, chartData } = useMemo ( ( ) => {
41+ const _chartConfig : ChartConfig = { } ;
42+ const _chartDataMap : Map < string , ChartData > = new Map ( ) ;
43+ const chainIdToVolumeMap : Map < string , number > = new Map ( ) ;
44+ // for each stat, add it in _chartDataMap
45+ for ( const stat of userOpStats ) {
46+ const chartData = _chartDataMap . get ( stat . date ) ;
47+ const { chainId } = stat ;
48+ const chain = chainsStore . idToChain . get ( Number ( chainId ) ) ;
4449
45- for ( const data of userOpStats ) {
46- const chartData = chartDataMap . get ( data . date ) ;
50+ // if no data for current day - create new entry
4751 if ( ! chartData ) {
48- chartDataMap . set ( data . date , {
49- time : format ( new Date ( data . date ) , "MMM dd" ) ,
50- sponsoredUsd : data . sponsoredUsd ,
51- } ) ;
52+ _chartDataMap . set ( stat . date , {
53+ time : format ( new Date ( stat . date ) , "MMM dd" ) ,
54+ [ chainId || "Unknown" ] : Math . round ( stat . sponsoredUsd * 100 ) / 100 ,
55+ } as ChartData ) ;
5256 } else {
53- chartData . sponsoredUsd += data . sponsoredUsd ;
57+ chartData [ chain ?. name || chainId || "Unknown" ] =
58+ ( chartData [ chain ?. name || chainId || "Unknown" ] || 0 ) +
59+ Math . round ( stat . sponsoredUsd * 100 ) / 100 ;
5460 }
61+
62+ chainIdToVolumeMap . set (
63+ chain ?. name || chainId || "Unknown" ,
64+ stat . sponsoredUsd + ( chainIdToVolumeMap . get ( chainId || "Unknown" ) || 0 ) ,
65+ ) ;
5566 }
5667
57- return Array . from ( chartDataMap . values ( ) ) ;
58- } , [ userOpStats ] ) ;
68+ const chainsSorted = Array . from ( chainIdToVolumeMap . entries ( ) )
69+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
70+ . map ( ( w ) => w [ 0 ] ) ;
71+
72+ const chainsToShow = chainsSorted . slice ( 0 , topChainsToShow ) ;
73+ const chainsToTagAsOthers = chainsSorted . slice ( topChainsToShow ) ;
74+
75+ // replace chainIdsToTagAsOther chainId with "other"
76+ for ( const data of _chartDataMap . values ( ) ) {
77+ for ( const chainId in data ) {
78+ if ( chainsToTagAsOthers . includes ( chainId ) ) {
79+ data . others = ( data . others || 0 ) + ( data [ chainId ] || 0 ) ;
80+ delete data [ chainId ] ;
81+ }
82+ }
83+ }
5984
60- const disableActions = props . isPending || barChartData . length === 0 ;
85+ chainsToShow . forEach ( ( walletType , i ) => {
86+ _chartConfig [ walletType ] = {
87+ label : chainsToShow [ i ] ,
88+ color : `hsl(var(--chart-${ ( i % 10 ) + 1 } ))` ,
89+ } ;
90+ } ) ;
91+
92+ // Add Other
93+ chainsToShow . push ( "others" ) ;
94+ _chartConfig . others = {
95+ label : "Others" ,
96+ color : "hsl(var(--muted-foreground))" ,
97+ } ;
98+
99+ return {
100+ chartData : Array . from ( _chartDataMap . values ( ) ) ,
101+ chartConfig : _chartConfig ,
102+ } ;
103+ } , [ userOpStats , chainsStore ] ) ;
104+
105+ const uniqueChainIds = Object . keys ( chartConfig ) ;
106+ const disableActions =
107+ props . isPending ||
108+ chartData . length === 0 ||
109+ chartData . every ( ( data ) => data . sponsoredUsd === 0 ) ;
61110
62111 return (
63112 < div className = "relative w-full rounded-lg border border-border bg-muted/50 p-4 md:p-6" >
@@ -70,17 +119,21 @@ export function TotalSponsoredChartCard(props: {
70119
71120 < div className = "top-6 right-6 mb-4 grid grid-cols-2 items-center gap-2 md:absolute md:mb-0 md:flex" >
72121 < ExportToCSVButton
73- disabled = { disableActions }
74122 className = "bg-background"
123+ fileName = "Connect Wallets"
124+ disabled = { disableActions }
75125 getData = { async ( ) => {
76- const header = [ "Date" , "Total Sponsored" ] ;
77- const rows = barChartData . map ( ( row ) => [
78- row . time ,
79- row . sponsoredUsd . toString ( ) ,
80- ] ) ;
126+ // Shows the number of each type of wallet connected on all dates
127+ const header = [ "Date" , ...uniqueChainIds ] ;
128+ const rows = chartData . map ( ( data ) => {
129+ const { time, ...rest } = data ;
130+ return [
131+ time ,
132+ ...uniqueChainIds . map ( ( w ) => ( rest [ w ] || 0 ) . toString ( ) ) ,
133+ ] ;
134+ } ) ;
81135 return { header, rows } ;
82136 } }
83- fileName = "Total Sponsored"
84137 />
85138 </ div >
86139
@@ -91,8 +144,8 @@ export function TotalSponsoredChartCard(props: {
91144 >
92145 { props . isPending ? (
93146 < LoadingChartState />
94- ) : barChartData . length === 0 ||
95- barChartData . every ( ( data ) => data . sponsoredUsd === 0 ) ? (
147+ ) : chartData . length === 0 ||
148+ chartData . every ( ( data ) => data . sponsoredUsd === 0 ) ? (
96149 < EmptyChartState >
97150 < div className = "flex flex-col items-center justify-center" >
98151 < span className = "mb-6 text-lg" > Sponsor gas for your users</ span >
@@ -133,7 +186,7 @@ export function TotalSponsoredChartCard(props: {
133186 ) : (
134187 < BarChart
135188 accessibilityLayer
136- data = { barChartData }
189+ data = { chartData }
137190 margin = { {
138191 top : 20 ,
139192 } }
@@ -148,21 +201,20 @@ export function TotalSponsoredChartCard(props: {
148201 />
149202
150203 < ChartTooltip cursor = { true } content = { < ChartTooltipContent /> } />
151-
152- < Bar
153- dataKey = { "sponsoredUsd" }
154- fill = { "var(--color-sponsoredUsd)" }
155- radius = { 8 }
156- >
157- { barChartData . length < 50 && (
158- < LabelList
159- position = "top"
160- offset = { 12 }
161- className = "invisible fill-foreground sm:visible"
162- fontSize = { 12 }
204+ < ChartLegend content = { < ChartLegendContent /> } />
205+ { uniqueChainIds . map ( ( chainId ) => {
206+ return (
207+ < Bar
208+ key = { chainId }
209+ dataKey = { chainId }
210+ fill = { chartConfig [ chainId ] ?. color }
211+ radius = { 4 }
212+ stackId = "a"
213+ strokeWidth = { 1.5 }
214+ className = "stroke-muted"
163215 />
164- ) }
165- </ Bar >
216+ ) ;
217+ } ) }
166218 </ BarChart >
167219 ) }
168220 </ ChartContainer >
0 commit comments