@@ -21,15 +21,14 @@ import type {
21
21
} from "types/analytics" ;
22
22
import { AnalyticsHeader } from "../../../../components/Analytics/AnalyticsHeader" ;
23
23
import { CombinedBarChartCard } from "../../../../components/Analytics/CombinedBarChartCard" ;
24
- import { EmptyState } from "../../../../components/Analytics/EmptyState" ;
25
24
import { PieChartCard } from "../../../../components/Analytics/PieChartCard" ;
26
25
27
26
import { getTeamBySlug } from "@/api/team" ;
28
- import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage" ;
29
27
import {
30
28
EmptyStateCard ,
31
29
EmptyStateContent ,
32
30
} from "app/(app)/team/components/Analytics/EmptyStateCard" ;
31
+ import { LoadingChartState } from "components/analytics/empty-chart-state" ;
33
32
import { Suspense } from "react" ;
34
33
import { TotalSponsoredChartCardUI } from "../../_components/TotalSponsoredCard" ;
35
34
import { TransactionsChartCardUI } from "../../_components/TransactionsCard" ;
@@ -76,159 +75,221 @@ export default async function TeamOverviewPage(props: {
76
75
/>
77
76
</ div >
78
77
< div className = "flex grow flex-col justify-between gap-10 md:container md:pt-8 md:pb-16" >
79
- < Suspense fallback = { < GenericLoadingPage /> } >
80
- < OverviewPageContent
81
- teamId = { team . id }
82
- range = { range }
83
- interval = { interval }
84
- searchParams = { searchParams }
85
- />
86
- </ Suspense >
78
+ < div className = "flex grow flex-col gap-6" >
79
+ < Suspense
80
+ fallback = { < LoadingChartState className = "h-[458px] border" /> }
81
+ >
82
+ < AsyncAppHighlightsCard
83
+ teamId = { team . id }
84
+ range = { range }
85
+ interval = { interval }
86
+ searchParams = { searchParams }
87
+ />
88
+ </ Suspense >
89
+
90
+ < div className = "grid gap-6 max-md:px-6 md:grid-cols-2" >
91
+ < Suspense
92
+ fallback = { < LoadingChartState className = "h-[431px] border" /> }
93
+ >
94
+ < AsyncWalletDistributionCard teamId = { team . id } range = { range } />
95
+ </ Suspense >
96
+
97
+ < Suspense
98
+ fallback = { < LoadingChartState className = "h-[431px] border" /> }
99
+ >
100
+ < AsyncAuthMethodDistributionCard teamId = { team . id } range = { range } />
101
+ </ Suspense >
102
+ </ div >
103
+
104
+ < Suspense
105
+ fallback = { < LoadingChartState className = "h-[458px] border" /> }
106
+ >
107
+ < AsyncTransactionsChartCard
108
+ teamId = { team . id }
109
+ range = { range }
110
+ interval = { interval }
111
+ searchParams = { searchParams }
112
+ />
113
+ </ Suspense >
114
+
115
+ < Suspense
116
+ fallback = { < LoadingChartState className = "h-[458px] border" /> }
117
+ >
118
+ < AsyncTotalSponsoredCard
119
+ teamId = { team . id }
120
+ range = { range }
121
+ interval = { interval }
122
+ searchParams = { searchParams }
123
+ />
124
+ </ Suspense >
125
+ </ div >
87
126
</ div >
88
127
</ div >
89
128
) ;
90
129
}
91
130
92
- async function OverviewPageContent ( props : {
131
+ async function AsyncAppHighlightsCard ( props : {
93
132
teamId : string ;
94
133
range : Range ;
95
134
interval : "day" | "week" ;
96
135
searchParams : SearchParams ;
97
136
} ) {
98
- const { teamId, range, interval, searchParams } = props ;
99
-
100
- const [
101
- walletConnections ,
102
- walletUserStatsTimeSeries ,
103
- inAppWalletUsage ,
104
- userOpUsageTimeSeries ,
105
- userOpUsage ,
106
- clientTransactionsTimeSeries ,
107
- clientTransactions ,
108
- universalBridgeUsage ,
109
- ] = await Promise . all ( [
110
- // Aggregated wallet connections
111
- getWalletConnections ( {
112
- teamId : teamId ,
113
- from : range . from ,
114
- to : range . to ,
115
- period : "all" ,
116
- } ) ,
117
- // Time series data for wallet users
118
- getWalletUsers ( {
119
- teamId : teamId ,
120
- from : range . from ,
121
- to : range . to ,
122
- period : interval ,
123
- } ) ,
124
- // In-app wallet usage
125
- getInAppWalletUsage ( {
126
- teamId : teamId ,
127
- from : range . from ,
128
- to : range . to ,
129
- period : "all" ,
130
- } ) ,
131
- // User operations usage
137
+ const [ walletUserStatsTimeSeries , universalBridgeUsage ] =
138
+ await Promise . allSettled ( [
139
+ getWalletUsers ( {
140
+ teamId : props . teamId ,
141
+ from : props . range . from ,
142
+ to : props . range . to ,
143
+ period : props . interval ,
144
+ } ) ,
145
+ getUniversalBridgeUsage ( {
146
+ teamId : props . teamId ,
147
+ from : props . range . from ,
148
+ to : props . range . to ,
149
+ period : props . interval ,
150
+ } ) ,
151
+ ] ) ;
152
+
153
+ if (
154
+ walletUserStatsTimeSeries . status === "fulfilled" &&
155
+ universalBridgeUsage . status === "fulfilled" &&
156
+ walletUserStatsTimeSeries . value . some ( ( w ) => w . totalUsers !== 0 )
157
+ ) {
158
+ return (
159
+ < div className = "" >
160
+ < AppHighlightsCard
161
+ userStats = { walletUserStatsTimeSeries . value }
162
+ volumeStats = { universalBridgeUsage . value }
163
+ searchParams = { props . searchParams }
164
+ />
165
+ </ div >
166
+ ) ;
167
+ }
168
+
169
+ return (
170
+ < EmptyStateCard
171
+ metric = "Connect"
172
+ link = "https://portal.thirdweb.com/connect/quickstart"
173
+ />
174
+ ) ;
175
+ }
176
+
177
+ async function AsyncWalletDistributionCard ( props : {
178
+ teamId : string ;
179
+ range : Range ;
180
+ } ) {
181
+ const walletConnections = await getWalletConnections ( {
182
+ teamId : props . teamId ,
183
+ from : props . range . from ,
184
+ to : props . range . to ,
185
+ period : "all" ,
186
+ } ) . catch ( ( ) => undefined ) ;
187
+
188
+ return walletConnections && walletConnections . length > 0 ? (
189
+ < WalletDistributionCard data = { walletConnections } />
190
+ ) : (
191
+ < EmptyStateCard
192
+ metric = "Connect"
193
+ link = "https://portal.thirdweb.com/connect/quickstart"
194
+ />
195
+ ) ;
196
+ }
197
+
198
+ async function AsyncAuthMethodDistributionCard ( props : {
199
+ teamId : string ;
200
+ range : Range ;
201
+ } ) {
202
+ const inAppWalletUsage = await getInAppWalletUsage ( {
203
+ teamId : props . teamId ,
204
+ from : props . range . from ,
205
+ to : props . range . to ,
206
+ period : "all" ,
207
+ } ) . catch ( ( ) => undefined ) ;
208
+
209
+ return inAppWalletUsage && inAppWalletUsage . length > 0 ? (
210
+ < AuthMethodDistributionCard data = { inAppWalletUsage } />
211
+ ) : (
212
+ < EmptyStateCard
213
+ metric = "In-App Wallets"
214
+ link = "https://portal.thirdweb.com/typescript/v5/inAppWallet"
215
+ />
216
+ ) ;
217
+ }
218
+
219
+ async function AsyncTransactionsChartCard ( props : {
220
+ teamId : string ;
221
+ range : Range ;
222
+ interval : "day" | "week" ;
223
+ searchParams : SearchParams ;
224
+ } ) {
225
+ const [ clientTransactionsTimeSeries , clientTransactions ] =
226
+ await Promise . allSettled ( [
227
+ getClientTransactions ( {
228
+ teamId : props . teamId ,
229
+ from : props . range . from ,
230
+ to : props . range . to ,
231
+ period : props . interval ,
232
+ } ) ,
233
+ getClientTransactions ( {
234
+ teamId : props . teamId ,
235
+ from : props . range . from ,
236
+ to : props . range . to ,
237
+ period : "all" ,
238
+ } ) ,
239
+ ] ) ;
240
+
241
+ return clientTransactionsTimeSeries . status === "fulfilled" &&
242
+ clientTransactions . status === "fulfilled" &&
243
+ clientTransactions . value . length > 0 ? (
244
+ < TransactionsChartCardUI
245
+ searchParams = { props . searchParams }
246
+ data = { clientTransactionsTimeSeries . value }
247
+ aggregatedData = { clientTransactions . value }
248
+ className = "max-md:rounded-none max-md:border-r-0 max-md:border-l-0"
249
+ />
250
+ ) : (
251
+ < EmptyStateCard
252
+ metric = "Transactions"
253
+ link = "https://portal.thirdweb.com/connect/quickstart"
254
+ />
255
+ ) ;
256
+ }
257
+
258
+ async function AsyncTotalSponsoredCard ( props : {
259
+ teamId : string ;
260
+ range : Range ;
261
+ interval : "day" | "week" ;
262
+ searchParams : SearchParams ;
263
+ } ) {
264
+ const [ userOpUsageTimeSeries , userOpUsage ] = await Promise . allSettled ( [
132
265
getUserOpUsage ( {
133
- teamId,
134
- from : range . from ,
135
- to : range . to ,
136
- period : interval ,
266
+ teamId : props . teamId ,
267
+ from : props . range . from ,
268
+ to : props . range . to ,
269
+ period : props . interval ,
137
270
} ) ,
138
271
getUserOpUsage ( {
139
- teamId,
140
- from : range . from ,
141
- to : range . to ,
272
+ teamId : props . teamId ,
273
+ from : props . range . from ,
274
+ to : props . range . to ,
142
275
period : "all" ,
143
276
} ) ,
144
- // Client transactions
145
- getClientTransactions ( {
146
- teamId : teamId ,
147
- from : range . from ,
148
- to : range . to ,
149
- period : interval ,
150
- } ) ,
151
- getClientTransactions ( {
152
- teamId : teamId ,
153
- from : range . from ,
154
- to : range . to ,
155
- period : "all" ,
156
- } ) ,
157
- // Universal Bridge
158
- getUniversalBridgeUsage ( {
159
- teamId : teamId ,
160
- from : range . from ,
161
- to : range . to ,
162
- period : interval ,
163
- } ) ,
164
277
] ) ;
165
278
166
- const isEmpty =
167
- ! walletUserStatsTimeSeries . some ( ( w ) => w . totalUsers !== 0 ) &&
168
- walletConnections . length === 0 &&
169
- inAppWalletUsage . length === 0 &&
170
- userOpUsage . length === 0 ;
171
-
172
- if ( isEmpty ) {
173
- return < EmptyState /> ;
174
- }
175
-
176
- return (
177
- < div className = "flex grow flex-col gap-6" >
178
- { walletUserStatsTimeSeries . some ( ( w ) => w . totalUsers !== 0 ) ? (
179
- < div className = "" >
180
- < AppHighlightsCard
181
- userStats = { walletUserStatsTimeSeries }
182
- volumeStats = { universalBridgeUsage }
183
- searchParams = { searchParams }
184
- />
185
- </ div >
186
- ) : (
187
- < EmptyStateCard
188
- metric = "Connect"
189
- link = "https://portal.thirdweb.com/connect/quickstart"
190
- />
191
- ) }
192
- < div className = "grid gap-6 max-md:px-6 md:grid-cols-2" >
193
- { walletConnections . length > 0 ? (
194
- < WalletDistributionCard data = { walletConnections } />
195
- ) : (
196
- < EmptyStateCard
197
- metric = "Connect"
198
- link = "https://portal.thirdweb.com/connect/quickstart"
199
- />
200
- ) }
201
- { inAppWalletUsage . length > 0 ? (
202
- < AuthMethodDistributionCard data = { inAppWalletUsage } />
203
- ) : (
204
- < EmptyStateCard
205
- metric = "In-App Wallets"
206
- link = "https://portal.thirdweb.com/typescript/v5/inAppWallet"
207
- />
208
- ) }
209
- </ div >
210
- { clientTransactions . length > 0 && (
211
- < TransactionsChartCardUI
212
- searchParams = { searchParams }
213
- data = { clientTransactionsTimeSeries }
214
- aggregatedData = { clientTransactions }
215
- className = "max-md:rounded-none max-md:border-r-0 max-md:border-l-0"
216
- />
217
- ) }
218
- { userOpUsage . length > 0 ? (
219
- < TotalSponsoredChartCardUI
220
- searchParams = { searchParams }
221
- data = { userOpUsageTimeSeries }
222
- aggregatedData = { userOpUsage }
223
- className = "max-md:rounded-none max-md:border-r-0 max-md:border-l-0"
224
- />
225
- ) : (
226
- < EmptyStateCard
227
- metric = "Gas Sponsored"
228
- link = "https://portal.thirdweb.com/typescript/v5/account-abstraction/get-started"
229
- />
230
- ) }
231
- </ div >
279
+ return userOpUsageTimeSeries . status === "fulfilled" &&
280
+ userOpUsage . status === "fulfilled" &&
281
+ userOpUsage . value . length > 0 ? (
282
+ < TotalSponsoredChartCardUI
283
+ searchParams = { props . searchParams }
284
+ data = { userOpUsageTimeSeries . value }
285
+ aggregatedData = { userOpUsage . value }
286
+ className = "max-md:rounded-none max-md:border-r-0 max-md:border-l-0"
287
+ />
288
+ ) : (
289
+ < EmptyStateCard
290
+ metric = "Gas Sponsored"
291
+ link = "https://portal.thirdweb.com/typescript/v5/account-abstraction/get-started"
292
+ />
232
293
) ;
233
294
}
234
295
@@ -251,11 +312,19 @@ function processTimeSeriesData(
251
312
252
313
for ( const stat of userStats ) {
253
314
const volume = volumeStats
254
- . filter ( ( v ) => v . date === stat . date && v . status === "completed" )
315
+ . filter (
316
+ ( v ) =>
317
+ new Date ( v . date ) . toISOString ( ) ===
318
+ new Date ( stat . date ) . toISOString ( ) && v . status === "completed" ,
319
+ )
255
320
. reduce ( ( acc , curr ) => acc + curr . amountUsdCents / 100 , 0 ) ;
256
321
257
322
const fees = volumeStats
258
- . filter ( ( v ) => v . date === stat . date && v . status === "completed" )
323
+ . filter (
324
+ ( v ) =>
325
+ new Date ( v . date ) . toISOString ( ) ===
326
+ new Date ( stat . date ) . toISOString ( ) && v . status === "completed" ,
327
+ )
259
328
. reduce ( ( acc , curr ) => acc + curr . developerFeeUsdCents / 100 , 0 ) ;
260
329
261
330
metrics . push ( {
0 commit comments