@@ -4,10 +4,12 @@ import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton";
4
4
import {
5
5
type ChartConfig ,
6
6
ChartContainer ,
7
+ ChartLegend ,
8
+ ChartLegendContent ,
7
9
ChartTooltip ,
8
10
ChartTooltipContent ,
9
11
} from "@/components/ui/chart" ;
10
- import type { UserOpStats } from "@3rdweb-sdk/react/hooks/useApi" ;
12
+ import type { UserOpStatsByChain } from "@3rdweb-sdk/react/hooks/useApi" ;
11
13
import {
12
14
EmptyChartState ,
13
15
LoadingChartState ,
@@ -20,44 +22,91 @@ import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon";
20
22
import { DocLink } from "components/shared/DocLink" ;
21
23
import { format } from "date-fns" ;
22
24
import { 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" ;
24
27
25
- type ChartData = {
28
+ type ChartData = Record < string , number > & {
26
29
time : string ; // human readable date
27
- sponsoredUsd : number ;
28
30
} ;
29
31
30
- const chartConfig = {
31
- sponsoredUsd : {
32
- label : "Total Sponsored" ,
33
- color : "hsl(var(--chart-1))" ,
34
- } ,
35
- } satisfies ChartConfig ;
36
-
37
32
export function TotalSponsoredChartCard ( props : {
38
- userOpStats : UserOpStats [ ] ;
33
+ userOpStats : UserOpStatsByChain [ ] ;
39
34
isPending : boolean ;
40
35
} ) {
41
36
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 ) ) ;
44
49
45
- for ( const data of userOpStats ) {
46
- const chartData = chartDataMap . get ( data . date ) ;
50
+ // if no data for current day - create new entry
47
51
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 ) ;
52
56
} 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 ;
54
60
}
61
+
62
+ chainIdToVolumeMap . set (
63
+ chain ?. name || chainId || "Unknown" ,
64
+ stat . sponsoredUsd + ( chainIdToVolumeMap . get ( chainId || "Unknown" ) || 0 ) ,
65
+ ) ;
55
66
}
56
67
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
+ }
59
84
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 ) ;
61
110
62
111
return (
63
112
< 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: {
70
119
71
120
< div className = "top-6 right-6 mb-4 grid grid-cols-2 items-center gap-2 md:absolute md:mb-0 md:flex" >
72
121
< ExportToCSVButton
73
- disabled = { disableActions }
74
122
className = "bg-background"
123
+ fileName = "Connect Wallets"
124
+ disabled = { disableActions }
75
125
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
+ } ) ;
81
135
return { header, rows } ;
82
136
} }
83
- fileName = "Total Sponsored"
84
137
/>
85
138
</ div >
86
139
@@ -91,8 +144,8 @@ export function TotalSponsoredChartCard(props: {
91
144
>
92
145
{ props . isPending ? (
93
146
< LoadingChartState />
94
- ) : barChartData . length === 0 ||
95
- barChartData . every ( ( data ) => data . sponsoredUsd === 0 ) ? (
147
+ ) : chartData . length === 0 ||
148
+ chartData . every ( ( data ) => data . sponsoredUsd === 0 ) ? (
96
149
< EmptyChartState >
97
150
< div className = "flex flex-col items-center justify-center" >
98
151
< span className = "mb-6 text-lg" > Sponsor gas for your users</ span >
@@ -133,7 +186,7 @@ export function TotalSponsoredChartCard(props: {
133
186
) : (
134
187
< BarChart
135
188
accessibilityLayer
136
- data = { barChartData }
189
+ data = { chartData }
137
190
margin = { {
138
191
top : 20 ,
139
192
} }
@@ -148,21 +201,20 @@ export function TotalSponsoredChartCard(props: {
148
201
/>
149
202
150
203
< 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"
163
215
/>
164
- ) }
165
- </ Bar >
216
+ ) ;
217
+ } ) }
166
218
</ BarChart >
167
219
) }
168
220
</ ChartContainer >
0 commit comments