Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ import {
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";

type ShadcnSidebarBaseLink = {
export type ShadcnSidebarBaseLink = {
href: string;
label: React.ReactNode;
exactMatch?: boolean;
icon?: React.FC<{ className?: string }>;
isActive?: (pathname: string) => boolean;
};

type ShadcnSidebarLink =
export type ShadcnSidebarLink =
| ShadcnSidebarBaseLink
| {
group: string;
links: ShadcnSidebarBaseLink[];
links: ShadcnSidebarLink[];
}
| {
separator: true;
Expand Down Expand Up @@ -97,10 +97,7 @@ export function FullWidthSidebarLayout(props: {

function MobileSidebarTrigger(props: { links: ShadcnSidebarLink[] }) {
const activeLink = useActiveShadcnSidebarLink(props.links);
const parentSubNav = props.links.find(
(link) =>
"subMenu" in link && link.links.some((l) => l.href === activeLink?.href),
);
const parentSubNav = findParentSubmenu(props.links, activeLink?.href);

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

for (const link of links) {
if ("links" in link) {
for (const subLink of link.links) {
if (isActive(subLink)) {
return subLink;
function walk(
navLinks: ShadcnSidebarLink[],
): ShadcnSidebarBaseLink | undefined {
for (const link of navLinks) {
if ("subMenu" in link) {
for (const subLink of link.links) {
if (isActive(subLink)) {
return subLink;
}
}
} else if ("href" in link) {
if (isActive(link)) {
return link;
}
}
} else if ("href" in link) {
if (isActive(link)) {
return link;

if ("links" in link && !("subMenu" in link)) {
const nested = walk(link.links);
if (nested) {
return nested;
}
}
}

return undefined;
}

return walk(links);
}, [links, pathname]);

return activeLink;
}

function findParentSubmenu(
links: ShadcnSidebarLink[],
activeHref: string | undefined,
): Extract<ShadcnSidebarLink, { subMenu: unknown }> | undefined {
if (!activeHref) {
return undefined;
}

for (const link of links) {
if ("subMenu" in link) {
if (link.links.some((subLink) => subLink.href === activeHref)) {
return link;
}
}

if ("links" in link && !("subMenu" in link)) {
const nested = findParentSubmenu(link.links, activeHref);
if (nested) {
return nested;
}
}
}

return undefined;
}

function useIsSubnavActive(links: ShadcnSidebarBaseLink[]) {
const pathname = usePathname();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,163 +1,11 @@
import type { Metadata } from "next";
import { notFound, redirect } from "next/navigation";
import type { SearchParams } from "nuqs/server";
import { getUserOpUsage } from "@/api/analytics";
import { getAuthToken } from "@/api/auth-token";
import { getProject } from "@/api/project/projects";
import { getTeamBySlug } from "@/api/team/get-team";
import {
getLastNDaysRange,
type Range,
} from "@/components/analytics/date-range-selector";
import { ProjectPage } from "@/components/blocks/project-page/project-page";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { SmartAccountIcon } from "@/icons/SmartAccountIcon";
import { getAbsoluteUrl } from "@/utils/vercel";
import { AccountAbstractionSummary } from "./AccountAbstractionAnalytics/AccountAbstractionSummary";
import { SmartWalletsBillingAlert } from "./Alerts";
import { AccountAbstractionAnalytics } from "./aa-analytics";
import { searchParamLoader } from "./search-params";

interface PageParams {
team_slug: string;
project_slug: string;
}
import { redirect } from "next/navigation";

// Redirect old Account Abstraction page to new Sponsored Gas Overview
export default async function Page(props: {
params: Promise<PageParams>;
searchParams: Promise<SearchParams>;
children: React.ReactNode;
params: Promise<{ team_slug: string; project_slug: string }>;
}) {
const [params, searchParams, authToken] = await Promise.all([
props.params,
searchParamLoader(props.searchParams),
getAuthToken(),
]);

if (!authToken) {
notFound();
}

const [team, project] = await Promise.all([
getTeamBySlug(params.team_slug),
getProject(params.team_slug, params.project_slug),
]);

if (!team) {
redirect("/team");
}

if (!project) {
redirect(`/team/${params.team_slug}`);
}

const interval = searchParams.interval ?? "week";
const rangeType = searchParams.range || "last-120";

const range: Range = {
from:
rangeType === "custom"
? searchParams.from
: getLastNDaysRange(rangeType).from,
to:
rangeType === "custom"
? searchParams.to
: getLastNDaysRange(rangeType).to,
type: rangeType,
};

const userOpStats = await getUserOpUsage(
{
from: range.from,
period: interval,
projectId: project.id,
teamId: project.teamId,
to: range.to,
},
authToken,
);

const client = getClientThirdwebClient({
jwt: authToken,
teamId: project.teamId,
});

const isBundlerServiceEnabled = !!project.services.find(
(s) => s.name === "bundler",
);

const hasSmartWalletsWithoutBilling =
isBundlerServiceEnabled &&
team.billingStatus !== "validPayment" &&
team.billingStatus !== "pastDue";

return (
<ProjectPage
header={{
icon: SmartAccountIcon,
client,
title: "Account Abstraction",
description:
"Integrate EIP-7702 and EIP-4337 compliant smart accounts for gasless sponsorships and more.",
actions: null,
settings: {
href: `/team/${params.team_slug}/${params.project_slug}/settings/account-abstraction`,
},
links: [
{
type: "docs",
href: "https://portal.thirdweb.com/transactions/sponsor",
},
{
type: "playground",
href: "https://playground.thirdweb.com/account-abstraction/eip-7702",
},
],
}}
>
{hasSmartWalletsWithoutBilling && (
<>
<SmartWalletsBillingAlert teamSlug={params.team_slug} />
<div className="h-10" />
</>
)}
<div className="flex grow flex-col gap-10">
<AccountAbstractionSummary
projectId={project.id}
teamId={project.teamId}
authToken={authToken}
/>

<AccountAbstractionAnalytics
client={client}
projectId={project.id}
teamId={project.teamId}
teamSlug={params.team_slug}
userOpStats={userOpStats}
/>
</div>
</ProjectPage>
const params = await props.params;
redirect(
`/team/${params.team_slug}/${params.project_slug}/wallets/sponsored-gas/overview`,
);
}

const seo = {
desc: "Add account abstraction to your web3 app & unlock powerful features for seamless onboarding, customizable transactions, & maximum security. Get started.",
title: "The Complete Account Abstraction Toolkit | thirdweb",
};

export const metadata: Metadata = {
description: seo.desc,
openGraph: {
description: seo.desc,
images: [
{
alt: seo.title,
height: 630,
url: `${getAbsoluteUrl()}/assets/og-image/dashboard-wallets-smart-wallet.png`,
width: 1200,
},
],
title: seo.title,
},
title: seo.title,
};
Loading
Loading