Skip to content

Commit 4829799

Browse files
committed
[PRO-132] Dashboard: Update Wallet Connection charts in project (#8480)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the analytics and chart components related to wallet connections in the application. It introduces new types, refactors existing components, and updates links and chart functionalities to improve usability and data representation. ### Detailed summary - Deleted unused files related to wallet analytics charts. - Introduced `WalletStatsWithName` type for wallet statistics. - Updated links to point to new endpoints. - Refactored chart components to improve data handling and display. - Added new `EOAConnectionsChart` and `UserWalletConnectionsChart` components. - Replaced `InAppWalletAnalytics` with `UserWalletConnectionsChart`. - Enhanced `RPCRequestsChartUI` to include an `isPending` prop. - Updated metrics and labels in various charts for better clarity. - Improved error handling in API calls for wallet connections. - Removed deprecated code and streamlined analytics data fetching. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent cd9e429 commit 4829799

File tree

24 files changed

+754
-564
lines changed

24 files changed

+754
-564
lines changed

apps/dashboard/src/@/api/analytics.ts

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "server-only";
22

33
import { unstable_cache } from "next/cache";
4+
import { getWalletInfo, type WalletId } from "thirdweb/wallets";
45
import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs";
56
import { normalizeTime } from "@/lib/time";
67
import type {
@@ -14,6 +15,7 @@ import type {
1415
UniversalBridgeWalletStats,
1516
UserOpStats,
1617
WalletStats,
18+
WalletStatsWithName,
1719
WebhookLatencyStats,
1820
WebhookSummaryStats,
1921
X402QueryParams,
@@ -148,25 +150,118 @@ const cached_getWalletConnections = unstable_cache(
148150
}
149151

150152
const json = await res.json();
151-
return (json.data as WalletStats[]).filter(
153+
return json.data as WalletStats[];
154+
},
155+
["getWalletConnections"],
156+
{
157+
revalidate: 60 * 60, // 1 hour
158+
},
159+
);
160+
161+
const cache_getEOAWalletConnections = unstable_cache(
162+
async (
163+
params: AnalyticsQueryParams,
164+
authToken: string,
165+
): Promise<WalletStatsWithName[]> => {
166+
const result = await cached_getWalletConnections(
167+
normalizedParams(params),
168+
authToken,
169+
);
170+
const filteredResult = result.filter(
152171
(w) =>
153172
w.walletType !== "smart" &&
154173
w.walletType !== "smartWallet" &&
155174
w.walletType !== "inApp" &&
156175
w.walletType !== "inAppWallet",
157176
);
177+
178+
const modifiedResult = await Promise.all(
179+
filteredResult.map(async (w) => {
180+
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
181+
() => undefined,
182+
);
183+
184+
if (!wallet) {
185+
return {
186+
...w,
187+
walletName: w.walletType,
188+
};
189+
}
190+
191+
return {
192+
...w,
193+
walletName: wallet.name,
194+
};
195+
}),
196+
);
197+
198+
return modifiedResult;
158199
},
159-
["getWalletConnections"],
160-
{
161-
revalidate: 60 * 60, // 1 hour
162-
},
200+
["getEOAWalletConnections"],
163201
);
164202

165-
export function getWalletConnections(
203+
export async function getEOAWalletConnections(
166204
params: AnalyticsQueryParams,
167205
authToken: string,
168206
) {
169-
return cached_getWalletConnections(normalizedParams(params), authToken);
207+
return cache_getEOAWalletConnections(normalizedParams(params), authToken);
208+
}
209+
210+
const cache_getEOAAndInAppWalletConnections = unstable_cache(
211+
async (
212+
params: AnalyticsQueryParams,
213+
authToken: string,
214+
): Promise<WalletStatsWithName[]> => {
215+
const result = await cached_getWalletConnections(
216+
normalizedParams(params),
217+
authToken,
218+
);
219+
220+
const modifiedResult = await Promise.all(
221+
result.map(async (w) => {
222+
if (
223+
w.walletType === "inApp" ||
224+
w.walletType === "inAppWallet" ||
225+
w.walletType === "smart" ||
226+
w.walletType === "smartWallet"
227+
) {
228+
return {
229+
...w,
230+
walletName: "User wallet",
231+
};
232+
}
233+
234+
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
235+
() => undefined,
236+
);
237+
238+
if (!wallet) {
239+
return {
240+
...w,
241+
walletName: w.walletType,
242+
};
243+
}
244+
245+
return {
246+
...w,
247+
walletName: wallet.name,
248+
};
249+
}),
250+
);
251+
252+
return modifiedResult;
253+
},
254+
["getEOAAndInAppWalletConnections"],
255+
);
256+
257+
export async function getEOAAndInAppWalletConnections(
258+
params: AnalyticsQueryParams,
259+
authToken: string,
260+
): Promise<WalletStatsWithName[]> {
261+
return cache_getEOAAndInAppWalletConnections(
262+
normalizedParams(params),
263+
authToken,
264+
);
170265
}
171266

172267
const cached_getInAppWalletUsage = unstable_cache(

apps/dashboard/src/@/components/blocks/ExportToCSVButton.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,18 @@ export function ExportToCSVButton(props: {
3434

3535
return (
3636
<Button
37-
className={cn(
38-
"flex items-center gap-2 border text-sm rounded-full",
39-
props.className,
40-
)}
37+
className={cn("gap-2 rounded-full", props.className)}
38+
size="sm"
4139
disabled={props.disabled || exportMutation.isPending}
4240
onClick={async () => {
4341
exportMutation.mutate();
4442
}}
4543
variant="outline"
4644
>
4745
{exportMutation.isPending ? (
48-
<Spinner className="size-3.5" />
46+
<Spinner className="size-3.5 text-muted-foreground" />
4947
) : (
50-
<DownloadIcon className="size-3.5" />
48+
<DownloadIcon className="size-3.5 text-muted-foreground" />
5149
)}
5250
Export
5351
</Button>

apps/dashboard/src/@/components/blocks/charts/area-chart.tsx

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
"use client";
22

33
import { format } from "date-fns";
4-
import { ArrowUpRightIcon } from "lucide-react";
5-
import Link from "next/link";
64
import { useMemo } from "react";
75
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
86
import {
97
EmptyChartState,
108
LoadingChartState,
119
} from "@/components/analytics/empty-chart-state";
12-
import { Button } from "@/components/ui/button";
1310
import {
1411
Card,
1512
CardContent,
@@ -26,7 +23,6 @@ import {
2623
ChartTooltipContent,
2724
} from "@/components/ui/chart";
2825
import { cn } from "@/lib/utils";
29-
import { SkeletonContainer } from "../../ui/skeleton";
3026

3127
type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
3228
header?: {
@@ -193,49 +189,3 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
193189
</Card>
194190
);
195191
}
196-
197-
export function TotalValueChartHeader(props: {
198-
total: number;
199-
title: string;
200-
isPending: boolean;
201-
viewMoreLink: string | undefined;
202-
}) {
203-
return (
204-
<div className="space-y-1 p-6 flex justify-between items-start gap-3">
205-
<div>
206-
<SkeletonContainer
207-
loadedData={!props.isPending ? props.total : undefined}
208-
skeletonData={100}
209-
render={(value) => {
210-
return (
211-
<p className="text-3xl font-semibold tracking-tight">
212-
{compactNumberFormatter.format(value)}
213-
</p>
214-
);
215-
}}
216-
/>
217-
218-
<h3 className="text-muted-foreground">{props.title}</h3>
219-
</div>
220-
221-
{props.viewMoreLink && (
222-
<Button
223-
asChild
224-
size="sm"
225-
variant="outline"
226-
className="gap-2 rounded-full text-muted-foreground hover:text-foreground"
227-
>
228-
<Link href={props.viewMoreLink}>
229-
<span>View More</span>
230-
<ArrowUpRightIcon className="size-4" />
231-
</Link>
232-
</Button>
233-
)}
234-
</div>
235-
);
236-
}
237-
238-
const compactNumberFormatter = new Intl.NumberFormat("en-US", {
239-
notation: "compact",
240-
maximumFractionDigits: 2,
241-
});

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/InAppWalletUsersChartCard.stories.tsx renamed to apps/dashboard/src/@/components/blocks/charts/automerge-barchart.stories.tsx

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import type { Meta, StoryObj } from "@storybook/nextjs";
22
import type { InAppWalletAuth } from "thirdweb/wallets";
33
import { BadgeContainer } from "@/storybook/utils";
4-
import type { InAppWalletStats } from "@/types/analytics";
5-
import { InAppWalletUsersChartCardUI } from "./InAppWalletUsersChartCard";
4+
import { AutoMergeBarChart, type StatData } from "./automerge-barchart";
65

76
const meta = {
87
component: Component,
9-
title: "Charts/InAppWallets",
8+
title: "Charts/AutoMergeBarChart",
109
} satisfies Meta<typeof Component>;
1110

1211
export default meta;
@@ -22,57 +21,106 @@ function Component() {
2221
"This is an example of a description about user wallet usage chart";
2322
return (
2423
<div className="container flex max-w-6xl flex-col gap-10 py-10">
24+
<BadgeContainer label="10 days, view more variant">
25+
<AutoMergeBarChart
26+
viewMoreLink={"#"}
27+
description={description}
28+
stats={createStatsStub(10)}
29+
isPending={false}
30+
title={title}
31+
maxLabelsToShow={5}
32+
emptyChartState={undefined}
33+
exportButton={undefined}
34+
/>
35+
</BadgeContainer>
36+
2537
<BadgeContainer label="30 days">
26-
<InAppWalletUsersChartCardUI
38+
<AutoMergeBarChart
39+
viewMoreLink={undefined}
2740
description={description}
28-
inAppWalletStats={createInAppWalletStatsStub(30)}
41+
stats={createStatsStub(30)}
2942
isPending={false}
3043
title={title}
44+
maxLabelsToShow={5}
45+
emptyChartState={undefined}
46+
exportButton={{
47+
fileName: "in-app-wallets",
48+
}}
3149
/>
3250
</BadgeContainer>
3351

3452
<BadgeContainer label="60 days">
35-
<InAppWalletUsersChartCardUI
53+
<AutoMergeBarChart
54+
viewMoreLink={undefined}
3655
description={description}
37-
inAppWalletStats={createInAppWalletStatsStub(60)}
56+
stats={createStatsStub(60)}
3857
isPending={false}
3958
title={title}
59+
maxLabelsToShow={5}
60+
emptyChartState={undefined}
61+
exportButton={{
62+
fileName: "in-app-wallets",
63+
}}
4064
/>
4165
</BadgeContainer>
4266

4367
<BadgeContainer label="120 days">
44-
<InAppWalletUsersChartCardUI
68+
<AutoMergeBarChart
69+
viewMoreLink={undefined}
4570
description={description}
46-
inAppWalletStats={createInAppWalletStatsStub(120)}
71+
stats={createStatsStub(120)}
4772
isPending={false}
4873
title={title}
74+
maxLabelsToShow={5}
75+
emptyChartState={undefined}
76+
exportButton={{
77+
fileName: "in-app-wallets",
78+
}}
4979
/>
5080
</BadgeContainer>
5181

5282
<BadgeContainer label="10 days">
53-
<InAppWalletUsersChartCardUI
83+
<AutoMergeBarChart
84+
viewMoreLink={undefined}
5485
description={description}
55-
inAppWalletStats={createInAppWalletStatsStub(10)}
86+
stats={createStatsStub(10)}
5687
isPending={false}
5788
title={title}
89+
maxLabelsToShow={5}
90+
emptyChartState={undefined}
91+
exportButton={{
92+
fileName: "in-app-wallets",
93+
}}
5894
/>
5995
</BadgeContainer>
6096

6197
<BadgeContainer label="Empty List">
62-
<InAppWalletUsersChartCardUI
98+
<AutoMergeBarChart
99+
viewMoreLink={undefined}
63100
description={description}
64-
inAppWalletStats={createInAppWalletStatsStub(0)}
101+
stats={createStatsStub(0)}
65102
isPending={false}
66103
title={title}
104+
maxLabelsToShow={5}
105+
emptyChartState={undefined}
106+
exportButton={{
107+
fileName: "in-app-wallets",
108+
}}
67109
/>
68110
</BadgeContainer>
69111

70112
<BadgeContainer label="Pending">
71-
<InAppWalletUsersChartCardUI
113+
<AutoMergeBarChart
114+
viewMoreLink={undefined}
72115
description={description}
73-
inAppWalletStats={[]}
116+
stats={[]}
74117
isPending={true}
75118
title={title}
119+
maxLabelsToShow={5}
120+
emptyChartState={undefined}
121+
exportButton={{
122+
fileName: "in-app-wallets",
123+
}}
76124
/>
77125
</BadgeContainer>
78126
</div>
@@ -111,17 +159,16 @@ const pickRandomAuthMethod = () => {
111159
return capitalized;
112160
};
113161

114-
function createInAppWalletStatsStub(days: number): InAppWalletStats[] {
115-
const stubbedData: InAppWalletStats[] = [];
162+
function createStatsStub(days: number): StatData[] {
163+
const stubbedData: StatData[] = [];
116164

117165
let d = days;
118166
while (d !== 0) {
119167
const uniqueWallets = Math.floor(Math.random() * 100);
120168
stubbedData.push({
121-
authenticationMethod: pickRandomAuthMethod(),
169+
label: pickRandomAuthMethod(),
122170
date: new Date(2024, 11, d).toLocaleString(),
123-
uniqueWalletsConnected: uniqueWallets,
124-
newUsers: Math.floor(Math.random() * 100) + 1,
171+
count: uniqueWallets,
125172
});
126173

127174
if (Math.random() > 0.7) {

0 commit comments

Comments
 (0)