Skip to content

Commit 2b13c3f

Browse files
committed
Refactor wallet and sidebar routes, add new wallet pages
Reorganized wallet-related routes under a new sidebar structure, replacing legacy pages with redirects to new user, server, and sponsored gas wallet sections. Added new layout and overview/configuration pages for server wallets and sponsored gas, updated sidebar navigation to reflect new wallet grouping, and removed legacy Account Abstraction and Vault pages in favor of the new structure.
1 parent fd04eef commit 2b13c3f

File tree

26 files changed

+2569
-912
lines changed

26 files changed

+2569
-912
lines changed

apps/dashboard/src/@/components/blocks/full-width-sidebar-layout.tsx

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ import {
3030
} from "@/components/ui/sidebar";
3131
import { cn } from "@/lib/utils";
3232

33-
type ShadcnSidebarBaseLink = {
33+
export type ShadcnSidebarBaseLink = {
3434
href: string;
3535
label: React.ReactNode;
3636
exactMatch?: boolean;
3737
icon?: React.FC<{ className?: string }>;
3838
isActive?: (pathname: string) => boolean;
3939
};
4040

41-
type ShadcnSidebarLink =
41+
export type ShadcnSidebarLink =
4242
| ShadcnSidebarBaseLink
4343
| {
4444
group: string;
45-
links: ShadcnSidebarBaseLink[];
45+
links: ShadcnSidebarLink[];
4646
}
4747
| {
4848
separator: true;
@@ -97,10 +97,7 @@ export function FullWidthSidebarLayout(props: {
9797

9898
function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) {
9999
const activeLink = useActiveShadcnSidebarLink(props.links);
100-
const parentSubNav = props.links.find(
101-
(link) =>
102-
"subMenu" in link && link.links.some((l) => l.href === activeLink?.href),
103-
);
100+
const parentSubNav = findParentSubmenu(props.links, activeLink?.href);
104101

105102
return (
106103
<div className="flex items-center gap-3 border-b px-4 py-4 lg:hidden">
@@ -109,7 +106,7 @@ function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) {
109106
className="h-4 bg-muted-foreground/50"
110107
orientation="vertical"
111108
/>
112-
{parentSubNav && "subMenu" in parentSubNav && (
109+
{parentSubNav && (
113110
<>
114111
<span className="text-sm">{parentSubNav.subMenu.label}</span>
115112
<ChevronRightIcon className="size-4 text-muted-foreground/50 -mx-1.5" />
@@ -131,24 +128,65 @@ function useActiveShadcnSidebarLink(links: ShadcnSidebarLink[]) {
131128
return pathname?.startsWith(link.href);
132129
}
133130

134-
for (const link of links) {
135-
if ("links" in link) {
136-
for (const subLink of link.links) {
137-
if (isActive(subLink)) {
138-
return subLink;
131+
function walk(
132+
navLinks: ShadcnSidebarLink[],
133+
): ShadcnSidebarBaseLink | undefined {
134+
for (const link of navLinks) {
135+
if ("subMenu" in link) {
136+
for (const subLink of link.links) {
137+
if (isActive(subLink)) {
138+
return subLink;
139+
}
140+
}
141+
} else if ("href" in link) {
142+
if (isActive(link)) {
143+
return link;
139144
}
140145
}
141-
} else if ("href" in link) {
142-
if (isActive(link)) {
143-
return link;
146+
147+
if ("links" in link && !("subMenu" in link)) {
148+
const nested = walk(link.links);
149+
if (nested) {
150+
return nested;
151+
}
144152
}
145153
}
154+
155+
return undefined;
146156
}
157+
158+
return walk(links);
147159
}, [links, pathname]);
148160

149161
return activeLink;
150162
}
151163

164+
function findParentSubmenu(
165+
links: ShadcnSidebarLink[],
166+
activeHref: string | undefined,
167+
): Extract<ShadcnSidebarLink, { subMenu: unknown }> | undefined {
168+
if (!activeHref) {
169+
return undefined;
170+
}
171+
172+
for (const link of links) {
173+
if ("subMenu" in link) {
174+
if (link.links.some((subLink) => subLink.href === activeHref)) {
175+
return link;
176+
}
177+
}
178+
179+
if ("links" in link && !("subMenu" in link)) {
180+
const nested = findParentSubmenu(link.links, activeHref);
181+
if (nested) {
182+
return nested;
183+
}
184+
}
185+
}
186+
187+
return undefined;
188+
}
189+
152190
function useIsSubnavActive(links: ShadcnSidebarBaseLink[]) {
153191
const pathname = usePathname();
154192

Lines changed: 6 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,11 @@
1-
import type { Metadata } from "next";
2-
import { notFound, redirect } from "next/navigation";
3-
import type { SearchParams } from "nuqs/server";
4-
import { getUserOpUsage } from "@/api/analytics";
5-
import { getAuthToken } from "@/api/auth-token";
6-
import { getProject } from "@/api/project/projects";
7-
import { getTeamBySlug } from "@/api/team/get-team";
8-
import {
9-
getLastNDaysRange,
10-
type Range,
11-
} from "@/components/analytics/date-range-selector";
12-
import { ProjectPage } from "@/components/blocks/project-page/project-page";
13-
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
14-
import { SmartAccountIcon } from "@/icons/SmartAccountIcon";
15-
import { getAbsoluteUrl } from "@/utils/vercel";
16-
import { AccountAbstractionSummary } from "./AccountAbstractionAnalytics/AccountAbstractionSummary";
17-
import { SmartWalletsBillingAlert } from "./Alerts";
18-
import { AccountAbstractionAnalytics } from "./aa-analytics";
19-
import { searchParamLoader } from "./search-params";
20-
21-
interface PageParams {
22-
team_slug: string;
23-
project_slug: string;
24-
}
1+
import { redirect } from "next/navigation";
252

3+
// Redirect old Account Abstraction page to new Sponsored Gas Overview
264
export default async function Page(props: {
27-
params: Promise<PageParams>;
28-
searchParams: Promise<SearchParams>;
29-
children: React.ReactNode;
5+
params: Promise<{ team_slug: string; project_slug: string }>;
306
}) {
31-
const [params, searchParams, authToken] = await Promise.all([
32-
props.params,
33-
searchParamLoader(props.searchParams),
34-
getAuthToken(),
35-
]);
36-
37-
if (!authToken) {
38-
notFound();
39-
}
40-
41-
const [team, project] = await Promise.all([
42-
getTeamBySlug(params.team_slug),
43-
getProject(params.team_slug, params.project_slug),
44-
]);
45-
46-
if (!team) {
47-
redirect("/team");
48-
}
49-
50-
if (!project) {
51-
redirect(`/team/${params.team_slug}`);
52-
}
53-
54-
const interval = searchParams.interval ?? "week";
55-
const rangeType = searchParams.range || "last-120";
56-
57-
const range: Range = {
58-
from:
59-
rangeType === "custom"
60-
? searchParams.from
61-
: getLastNDaysRange(rangeType).from,
62-
to:
63-
rangeType === "custom"
64-
? searchParams.to
65-
: getLastNDaysRange(rangeType).to,
66-
type: rangeType,
67-
};
68-
69-
const userOpStats = await getUserOpUsage(
70-
{
71-
from: range.from,
72-
period: interval,
73-
projectId: project.id,
74-
teamId: project.teamId,
75-
to: range.to,
76-
},
77-
authToken,
78-
);
79-
80-
const client = getClientThirdwebClient({
81-
jwt: authToken,
82-
teamId: project.teamId,
83-
});
84-
85-
const isBundlerServiceEnabled = !!project.services.find(
86-
(s) => s.name === "bundler",
87-
);
88-
89-
const hasSmartWalletsWithoutBilling =
90-
isBundlerServiceEnabled &&
91-
team.billingStatus !== "validPayment" &&
92-
team.billingStatus !== "pastDue";
93-
94-
return (
95-
<ProjectPage
96-
header={{
97-
icon: SmartAccountIcon,
98-
client,
99-
title: "Account Abstraction",
100-
description:
101-
"Integrate EIP-7702 and EIP-4337 compliant smart accounts for gasless sponsorships and more.",
102-
actions: null,
103-
settings: {
104-
href: `/team/${params.team_slug}/${params.project_slug}/settings/account-abstraction`,
105-
},
106-
links: [
107-
{
108-
type: "docs",
109-
href: "https://portal.thirdweb.com/transactions/sponsor",
110-
},
111-
{
112-
type: "playground",
113-
href: "https://playground.thirdweb.com/account-abstraction/eip-7702",
114-
},
115-
],
116-
}}
117-
>
118-
{hasSmartWalletsWithoutBilling && (
119-
<>
120-
<SmartWalletsBillingAlert teamSlug={params.team_slug} />
121-
<div className="h-10" />
122-
</>
123-
)}
124-
<div className="flex grow flex-col gap-10">
125-
<AccountAbstractionSummary
126-
projectId={project.id}
127-
teamId={project.teamId}
128-
authToken={authToken}
129-
/>
130-
131-
<AccountAbstractionAnalytics
132-
client={client}
133-
projectId={project.id}
134-
teamId={project.teamId}
135-
teamSlug={params.team_slug}
136-
userOpStats={userOpStats}
137-
/>
138-
</div>
139-
</ProjectPage>
7+
const params = await props.params;
8+
redirect(
9+
`/team/${params.team_slug}/${params.project_slug}/wallets/sponsored-gas/overview`,
14010
);
14111
}
142-
143-
const seo = {
144-
desc: "Add account abstraction to your web3 app & unlock powerful features for seamless onboarding, customizable transactions, & maximum security. Get started.",
145-
title: "The Complete Account Abstraction Toolkit | thirdweb",
146-
};
147-
148-
export const metadata: Metadata = {
149-
description: seo.desc,
150-
openGraph: {
151-
description: seo.desc,
152-
images: [
153-
{
154-
alt: seo.title,
155-
height: 630,
156-
url: `${getAbsoluteUrl()}/assets/og-image/dashboard-wallets-smart-wallet.png`,
157-
width: 1200,
158-
},
159-
],
160-
title: seo.title,
161-
},
162-
title: seo.title,
163-
};

0 commit comments

Comments
 (0)