Skip to content

Commit 8bafb63

Browse files
committed
feat: empty wallet states
1 parent 0b48b76 commit 8bafb63

File tree

9 files changed

+296
-127
lines changed

9 files changed

+296
-127
lines changed
Lines changed: 88 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { UserOpStats, WalletStats } from "@3rdweb-sdk/react/hooks/useApi";
22
import {
3-
ActivityIcon,
4-
CableIcon,
5-
CoinsIcon,
6-
WalletCardsIcon,
3+
ActivityIcon,
4+
CableIcon,
5+
CoinsIcon,
6+
WalletCardsIcon,
77
} from "lucide-react";
88
import type React from "react";
99
import { useMemo } from "react";
@@ -14,112 +14,100 @@ import { WalletConnectorsChartCard } from "./_components/WalletConnectorsChartCa
1414
import { WalletDistributionChartCard } from "./_components/WalletDistributionChartCard";
1515

1616
export function ConnectAnalyticsDashboardUI(props: {
17-
walletUsage: WalletStats[];
18-
aggregateWalletUsage: WalletStats[];
19-
userOpUsage: UserOpStats[];
20-
aggregateUserOpUsage: UserOpStats[];
21-
isPending: boolean;
17+
walletUsage: WalletStats[];
18+
aggregateWalletUsage: WalletStats[];
19+
userOpUsage: UserOpStats[];
20+
aggregateUserOpUsage: UserOpStats[];
21+
isPending: boolean;
2222
}) {
23-
// This hook aggregates wallets across wallet types, only one date is returned for the aggregate query
24-
const { totalWallets, uniqueWallets } = useMemo(() => {
25-
return props.aggregateWalletUsage.reduce(
26-
(acc, curr) => {
27-
acc.totalWallets += curr.totalConnections;
28-
acc.uniqueWallets += curr.uniqueWalletsConnected;
29-
return acc;
30-
},
31-
{ uniqueWallets: 0, totalWallets: 0 },
32-
);
33-
}, [props.aggregateWalletUsage]);
23+
const { totalSponsoredTransactions, totalSponsoredUsd } = useMemo(() => {
24+
return props.aggregateUserOpUsage.reduce(
25+
(acc, curr) => {
26+
acc.totalSponsoredTransactions += curr.successful;
27+
acc.totalSponsoredUsd += curr.sponsoredUsd;
28+
return acc;
29+
},
30+
{ totalSponsoredTransactions: 0, totalSponsoredUsd: 0 },
31+
);
32+
}, [props.aggregateUserOpUsage]);
3433

35-
const { totalSponsoredTransactions, totalSponsoredUsd } = useMemo(() => {
36-
return props.aggregateUserOpUsage.reduce(
37-
(acc, curr) => {
38-
acc.totalSponsoredTransactions += curr.successful;
39-
acc.totalSponsoredUsd += curr.sponsoredUsd;
40-
return acc;
41-
},
42-
{ totalSponsoredTransactions: 0, totalSponsoredUsd: 0 },
43-
);
44-
}, [props.aggregateUserOpUsage]);
34+
return (
35+
<div className="flex flex-col gap-4 lg:gap-6">
36+
{/* Connections */}
37+
<div className="grid grid-cols-2 gap-4 lg:gap-6">
38+
<Stat label="Connections" value={totalWallets} icon={CableIcon} />
39+
<Stat
40+
label="Unique Wallets"
41+
value={uniqueWallets}
42+
icon={WalletCardsIcon}
43+
/>
44+
</div>
4545

46-
return (
47-
<div className="flex flex-col gap-4 lg:gap-6">
48-
{/* Connections */}
49-
<div className="grid grid-cols-2 gap-4 lg:gap-6">
50-
<Stat label="Connections" value={totalWallets} icon={CableIcon} />
51-
<Stat
52-
label="Unique Wallets"
53-
value={uniqueWallets}
54-
icon={WalletCardsIcon}
55-
/>
56-
</div>
46+
<DailyConnectionsChartCard
47+
walletStats={props.walletUsage}
48+
isPending={props.isPending}
49+
/>
5750

58-
<DailyConnectionsChartCard
59-
walletStats={props.walletUsage}
60-
isPending={props.isPending}
61-
/>
51+
<WalletConnectorsChartCard
52+
walletStats={props.walletUsage}
53+
isPending={props.isPending}
54+
/>
6255

63-
<WalletConnectorsChartCard
64-
walletStats={props.walletUsage}
65-
isPending={props.isPending}
66-
/>
56+
<WalletDistributionChartCard
57+
walletStats={props.walletUsage}
58+
isPending={props.isPending}
59+
/>
6760

68-
<WalletDistributionChartCard
69-
walletStats={props.walletUsage}
70-
isPending={props.isPending}
71-
/>
61+
{/* Connections */}
62+
<div className="grid grid-cols-2 gap-4 lg:gap-6">
63+
<Stat
64+
label="Sponsored Transactions"
65+
value={totalSponsoredTransactions}
66+
icon={ActivityIcon}
67+
/>
68+
<Stat
69+
label="Total Sponsored"
70+
value={totalSponsoredUsd}
71+
formatter={(value) =>
72+
new Intl.NumberFormat("en-US", {
73+
style: "currency",
74+
currency: "USD",
75+
}).format(value)
76+
}
77+
icon={CoinsIcon}
78+
/>
79+
</div>
7280

73-
{/* Connections */}
74-
<div className="grid grid-cols-2 gap-4 lg:gap-6">
75-
<Stat
76-
label="Sponsored Transactions"
77-
value={totalSponsoredTransactions}
78-
icon={ActivityIcon}
79-
/>
80-
<Stat
81-
label="Total Sponsored"
82-
value={totalSponsoredUsd}
83-
formatter={(value) =>
84-
new Intl.NumberFormat("en-US", {
85-
style: "currency",
86-
currency: "USD",
87-
}).format(value)
88-
}
89-
icon={CoinsIcon}
90-
/>
91-
</div>
81+
<TotalSponsoredChartCard
82+
userOpStats={props.userOpUsage}
83+
isPending={props.isPending}
84+
/>
9285

93-
<TotalSponsoredChartCard
94-
userOpStats={props.userOpUsage}
95-
isPending={props.isPending}
96-
/>
97-
98-
<SponsoredTransactionsChartCard
99-
userOpStats={props.userOpUsage}
100-
isPending={props.isPending}
101-
/>
102-
</div>
103-
);
86+
<SponsoredTransactionsChartCard
87+
userOpStats={props.userOpUsage}
88+
isPending={props.isPending}
89+
/>
90+
</div>
91+
);
10492
}
10593

10694
const Stat: React.FC<{
107-
label: string;
108-
value?: number;
109-
icon: React.FC<{ className?: string }>;
110-
formatter?: (value: number) => string;
95+
label: string;
96+
value?: number;
97+
icon: React.FC<{ className?: string }>;
98+
formatter?: (value: number) => string;
11199
}> = ({ label, value, formatter, icon: Icon }) => {
112-
return (
113-
<dl className="flex items-center justify-between gap-4 rounded-lg border border-border bg-muted/50 p-4 lg:p-6">
114-
<div>
115-
<dd className="font-semibold text-3xl tracking-tight lg:text-5xl">
116-
{value && formatter ? formatter(value) : value?.toLocaleString()}
117-
</dd>
118-
<dt className="font-medium text-muted-foreground text-sm tracking-tight lg:text-lg">
119-
{label}
120-
</dt>
121-
</div>
122-
<Icon className="hidden size-12 text-muted-foreground opacity-50 lg:block" />
123-
</dl>
124-
);
100+
return (
101+
<dl className="flex items-center justify-between gap-4 rounded-lg border border-border bg-muted/50 p-4 lg:p-6">
102+
<div>
103+
<dd className="font-semibold text-3xl tracking-tight lg:text-5xl">
104+
{value && formatter ? formatter(value) : value?.toLocaleString()}
105+
</dd>
106+
<dt className="font-medium text-muted-foreground text-sm tracking-tight lg:text-lg">
107+
{label}
108+
</dt>
109+
</div>
110+
<Icon className="hidden size-12 text-muted-foreground opacity-50 lg:block" />
111+
</dl>
112+
);
125113
};

apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/DailyConnectionsChartCard.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import {
1515
SelectValue,
1616
} from "@/components/ui/select";
1717
import type { WalletStats } from "@3rdweb-sdk/react/hooks/useApi";
18+
import { DotNetIcon } from "components/icons/brand-icons/DotNetIcon";
19+
import { ReactIcon } from "components/icons/brand-icons/ReactIcon";
20+
import { TypeScriptIcon } from "components/icons/brand-icons/TypeScriptIcon";
21+
import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
22+
import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon";
23+
import { DocLink } from "components/shared/DocLink";
1824
import { format } from "date-fns";
1925
import { useMemo, useState } from "react";
2026
import { Bar, BarChart, CartesianGrid, LabelList, XAxis } from "recharts";
@@ -129,7 +135,43 @@ export function DailyConnectionsChartCard(props: {
129135
{props.isPending ? (
130136
<LoadingChartState />
131137
) : barChartData.length === 0 ? (
132-
<EmptyChartState />
138+
<EmptyChartState>
139+
<div className="flex flex-col items-center justify-center">
140+
<span className="mb-6 text-lg">Get started with wallets</span>
141+
<div className="flex max-w-md flex-wrap items-center justify-center gap-x-6 gap-y-4">
142+
<DocLink
143+
link="https://portal.thirdweb.com/typescript/v5/getting-started"
144+
label="TypeScript"
145+
icon={TypeScriptIcon}
146+
/>
147+
<DocLink
148+
link="https://portal.thirdweb.com/react/v5"
149+
label="React"
150+
icon={ReactIcon}
151+
/>
152+
<DocLink
153+
link="https://portal.thirdweb.com/react-native/v5"
154+
label="React Native"
155+
icon={ReactIcon}
156+
/>
157+
<DocLink
158+
link="https://portal.thirdweb.com/dotnet/getting-started"
159+
label="Unity"
160+
icon={UnityIcon}
161+
/>
162+
<DocLink
163+
link="https://portal.thirdweb.com/unreal-engine/getting-started"
164+
label="Unreal Engine"
165+
icon={UnrealIcon}
166+
/>
167+
<DocLink
168+
link="https://portal.thirdweb.com/dotnet/getting-started"
169+
label=".NET"
170+
icon={DotNetIcon}
171+
/>
172+
</div>
173+
</div>
174+
</EmptyChartState>
133175
) : (
134176
<BarChart
135177
accessibilityLayer

apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/EmptyChartState.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ const skeletonChartConfig = {
2222
},
2323
} satisfies ChartConfig;
2424

25-
export function EmptyChartState() {
25+
export function EmptyChartState({ children }: { children?: React.ReactNode }) {
2626
const barChartData = useMemo(() => generateRandomData(), []);
2727

2828
return (
2929
<div className="relative z-0 h-full w-full">
30-
<span className="absolute inset-0 z-[1] flex items-center justify-center font-semibold text-base text-muted-foreground">
31-
No data available
32-
</span>
30+
<div className="absolute inset-0 z-[1] flex flex-col items-center justify-center font-semibold text-base text-muted-foreground">
31+
{children ?? "No data available"}
32+
</div>
3333
<SkeletonBarChart data={barChartData} />
3434
</div>
3535
);

apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/SponsoredTransactionsChartCard.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import {
99
ChartTooltipContent,
1010
} from "@/components/ui/chart";
1111
import type { UserOpStats } from "@3rdweb-sdk/react/hooks/useApi";
12+
import { DotNetIcon } from "components/icons/brand-icons/DotNetIcon";
13+
import { ReactIcon } from "components/icons/brand-icons/ReactIcon";
14+
import { TypeScriptIcon } from "components/icons/brand-icons/TypeScriptIcon";
15+
import { UnityIcon } from "components/icons/brand-icons/UnityIcon";
16+
import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon";
17+
import { DocLink } from "components/shared/DocLink";
1218
import { format } from "date-fns";
1319
import { useMemo } from "react";
1420
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
@@ -85,7 +91,45 @@ export function SponsoredTransactionsChartCard(props: {
8591
{props.isPending ? (
8692
<LoadingChartState />
8793
) : barChartData.length === 0 ? (
88-
<EmptyChartState />
94+
<EmptyChartState>
95+
<div className="flex flex-col items-center justify-center">
96+
<span className="mb-6 text-lg">
97+
Send smart account transactions
98+
</span>
99+
<div className="flex max-w-md flex-wrap items-center justify-center gap-x-6 gap-y-4">
100+
<DocLink
101+
link="https://portal.thirdweb.com/typescript/v5/account-abstraction/batching-transactions"
102+
label="TypeScript"
103+
icon={TypeScriptIcon}
104+
/>
105+
<DocLink
106+
link="https://portal.thirdweb.com/react/v5/account-abstraction/batching-transactions"
107+
label="React"
108+
icon={ReactIcon}
109+
/>
110+
<DocLink
111+
link="https://portal.thirdweb.com/react/v5/account-abstraction/get-started"
112+
label="React Native"
113+
icon={ReactIcon}
114+
/>
115+
<DocLink
116+
link="https://portal.thirdweb.com/unity/v5/wallets/account-abstraction"
117+
label="Unity"
118+
icon={UnityIcon}
119+
/>
120+
<DocLink
121+
link="https://portal.thirdweb.com/unreal-engine/blueprints/smart-wallet"
122+
label="Unreal Engine"
123+
icon={UnrealIcon}
124+
/>
125+
<DocLink
126+
link="https://portal.thirdweb.com/dotnet/wallets/providers/account-abstraction"
127+
label=".NET"
128+
icon={DotNetIcon}
129+
/>
130+
</div>
131+
</div>
132+
</EmptyChartState>
89133
) : (
90134
<BarChart
91135
accessibilityLayer

0 commit comments

Comments
 (0)