Skip to content

Commit 031a66b

Browse files
committed
fix: asyn team analytics rendering
1 parent 7ab79dc commit 031a66b

File tree

3 files changed

+222
-147
lines changed
  • apps/dashboard/src/app/(app)/team

3 files changed

+222
-147
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx

Lines changed: 209 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ import type {
2121
} from "types/analytics";
2222
import { AnalyticsHeader } from "../../../../components/Analytics/AnalyticsHeader";
2323
import { CombinedBarChartCard } from "../../../../components/Analytics/CombinedBarChartCard";
24-
import { EmptyState } from "../../../../components/Analytics/EmptyState";
2524
import { PieChartCard } from "../../../../components/Analytics/PieChartCard";
2625

2726
import { getTeamBySlug } from "@/api/team";
28-
import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage";
2927
import {
3028
EmptyStateCard,
3129
EmptyStateContent,
3230
} from "app/(app)/team/components/Analytics/EmptyStateCard";
31+
import { LoadingChartState } from "components/analytics/empty-chart-state";
3332
import { Suspense } from "react";
3433
import { TotalSponsoredChartCardUI } from "../../_components/TotalSponsoredCard";
3534
import { TransactionsChartCardUI } from "../../_components/TransactionsCard";
@@ -76,159 +75,221 @@ export default async function TeamOverviewPage(props: {
7675
/>
7776
</div>
7877
<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>
87126
</div>
88127
</div>
89128
);
90129
}
91130

92-
async function OverviewPageContent(props: {
131+
async function AsyncAppHighlightsCard(props: {
93132
teamId: string;
94133
range: Range;
95134
interval: "day" | "week";
96135
searchParams: SearchParams;
97136
}) {
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([
132265
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,
137270
}),
138271
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,
142275
period: "all",
143276
}),
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-
}),
164277
]);
165278

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+
/>
232293
);
233294
}
234295

@@ -251,11 +312,19 @@ function processTimeSeriesData(
251312

252313
for (const stat of userStats) {
253314
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+
)
255320
.reduce((acc, curr) => acc + curr.amountUsdCents / 100, 0);
256321

257322
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+
)
259328
.reduce((acc, curr) => acc + curr.developerFeeUsdCents / 100, 0);
260329

261330
metrics.push({

0 commit comments

Comments
 (0)