Skip to content

Commit 2623cf2

Browse files
authored
Dashboard: Add webhooks tab in Contracts page (#7384)
1 parent e232527 commit 2623cf2

File tree

49 files changed

+476
-302
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+476
-302
lines changed

apps/dashboard/redirects.js

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,51 @@ const legacyDashboardToTeamRedirects = [
6262
},
6363
];
6464

65+
const projectRoute = "/team/:team_slug/:project_slug";
66+
67+
const projectPageRedirects = [
68+
{
69+
source: `${projectRoute}/connect/pay/:path*`,
70+
destination: `${projectRoute}/universal-bridge/:path*`,
71+
permanent: false,
72+
},
73+
{
74+
source: `${projectRoute}/connect/universal-bridge/:path*`,
75+
destination: `${projectRoute}/universal-bridge/:path*`,
76+
permanent: false,
77+
},
78+
{
79+
source: `${projectRoute}/connect/account-abstraction/:path*`,
80+
destination: `${projectRoute}/account-abstraction/:path*`,
81+
permanent: false,
82+
},
83+
{
84+
source: `${projectRoute}/connect/in-app-wallets/:path*`,
85+
destination: `${projectRoute}/wallets/:path*`,
86+
permanent: false,
87+
},
88+
{
89+
source: `${projectRoute}/engine/cloud/vault/:path*`,
90+
destination: `${projectRoute}/vault/:path*`,
91+
permanent: false,
92+
},
93+
{
94+
source: `${projectRoute}/engine/cloud/:path*`,
95+
destination: `${projectRoute}/transactions/:path*`,
96+
permanent: false,
97+
},
98+
{
99+
source: `${projectRoute}/assets/:path*`,
100+
destination: `${projectRoute}/tokens/:path*`,
101+
permanent: false,
102+
},
103+
{
104+
source: `${projectRoute}/nebula/:path*`,
105+
destination: projectRoute,
106+
permanent: false,
107+
},
108+
];
109+
65110
/** @type {import('next').NextConfig['redirects']} */
66111
async function redirects() {
67112
return [
@@ -326,14 +371,6 @@ async function redirects() {
326371
destination: "/",
327372
permanent: false,
328373
},
329-
// pay > universal-bridge redirect
330-
{
331-
source: "/team/:team_slug/:project_slug/connect/pay/:path*",
332-
destination:
333-
"/team/:team_slug/:project_slug/connect/universal-bridge/:path*",
334-
permanent: false,
335-
},
336-
337374
// all /learn/tutorials (and sub-routes) -> /learn/guides
338375
{
339376
source: "/learn/tutorials/:path*",
@@ -382,8 +419,8 @@ async function redirects() {
382419
destination: "/transactions",
383420
permanent: false,
384421
},
385-
386422
...legacyDashboardToTeamRedirects,
423+
...projectPageRedirects,
387424
];
388425
}
389426

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type SidebarBaseLink = {
99
label: React.ReactNode;
1010
exactMatch?: boolean;
1111
icon?: React.FC<{ className?: string }>;
12+
isActive?: (pathname: string) => boolean;
1213
};
1314

1415
export type SidebarLink =

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ function RenderSidebarGroup(props: {
123123
className="flex items-center gap-2 text-muted-foreground text-sm hover:bg-accent"
124124
activeClassName="text-foreground bg-accent"
125125
exactMatch={link.exactMatch}
126+
isActive={link.isActive}
126127
onClick={() => {
127128
sidebar.setOpenMobile(false);
128129
}}

apps/dashboard/src/@/components/ui/NavLink.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ export type NavButtonProps = {
1111
href: string;
1212
exactMatch?: boolean;
1313
onClick?: () => void;
14+
isActive?: (pathname: string) => boolean;
1415
};
1516

1617
export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
1718
const pathname = usePathname();
18-
const isActive = pathname
19-
? props.exactMatch
20-
? pathname === props.href
21-
: pathname.startsWith(props.href)
22-
: false;
19+
const isActive = props.isActive
20+
? props.isActive(pathname)
21+
: pathname
22+
? props.exactMatch
23+
? pathname === props.href
24+
: pathname.startsWith(props.href)
25+
: false;
2326
return (
2427
<Link
2528
href={props.href}

apps/dashboard/src/app/(app)/account/contracts/DeployedContractsPageHeader.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function DeployedContractsPageHeader(props: {
1616
const [importModalOpen, setImportModalOpen] = useState(false);
1717

1818
return (
19-
<div className="border-b">
19+
<div>
2020
<ImportModal
2121
client={props.client}
2222
isOpen={importModalOpen}
@@ -28,24 +28,28 @@ export function DeployedContractsPageHeader(props: {
2828
type="contract"
2929
/>
3030

31-
<div className="container flex max-w-7xl flex-col gap-3 py-10 lg:flex-row lg:items-center lg:justify-between">
31+
<div className="container flex max-w-7xl flex-col gap-3 pt-10 pb-5 lg:flex-row lg:items-center lg:justify-between">
3232
<div>
3333
<h1 className="font-semibold text-2xl tracking-tight lg:text-3xl">
3434
Contracts
3535
</h1>
36+
<p className="text-muted-foreground">
37+
Deploy and manage contracts for your project
38+
</p>
3639
</div>
3740
<div className="flex gap-3 [&>*]:grow">
3841
<Button
39-
className="gap-2 bg-card"
42+
className="gap-1.5 bg-card"
43+
size="sm"
4044
variant="outline"
4145
onClick={() => {
4246
setImportModalOpen(true);
4347
}}
4448
>
45-
<DownloadIcon className="size-4" />
49+
<DownloadIcon className="size-4 text-muted-foreground" />
4650
Import contract
4751
</Button>
48-
<Button asChild className="gap-2">
52+
<Button asChild className="gap-1.5" size="sm">
4953
<Link href="/explore">
5054
<PlusIcon className="size-4" />
5155
Deploy contract

apps/dashboard/src/app/(app)/account/contracts/_components/DeployedContractsPage.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
33
import { ContractTable } from "components/contract-components/tables/contract-table";
44
import { Suspense } from "react";
55
import type { ThirdwebClient } from "thirdweb";
6-
import { DeployedContractsPageHeader } from "../DeployedContractsPageHeader";
76
import { DeployViaCLIOrImportCard } from "./DeployViaCLIOrImportCard";
87
import { getSortedDeployedContracts } from "./getSortedDeployedContracts";
98

@@ -16,24 +15,16 @@ export function DeployedContractsPage(props: {
1615
projectSlug: string;
1716
}) {
1817
return (
19-
<div className="flex grow flex-col">
20-
<DeployedContractsPageHeader
18+
<div className="container flex max-w-7xl grow flex-col">
19+
<Suspense fallback={<Loading />}>
20+
<DeployedContractsPageAsync {...props} />
21+
</Suspense>
22+
<div className="h-8" />
23+
<DeployViaCLIOrImportCard
24+
client={props.client}
2125
teamId={props.teamId}
2226
projectId={props.projectId}
23-
client={props.client}
2427
/>
25-
<div className="h-6" />
26-
<div className="container flex max-w-7xl grow flex-col">
27-
<Suspense fallback={<Loading />}>
28-
<DeployedContractsPageAsync {...props} />
29-
</Suspense>
30-
<div className="h-8" />
31-
<DeployViaCLIOrImportCard
32-
client={props.client}
33-
teamId={props.teamId}
34-
projectId={props.projectId}
35-
/>
36-
</div>
3728
</div>
3829
);
3930
}

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ import { SmartAccountIcon } from "../../../../../(dashboard)/(chain)/components/
1919

2020
export function ProjectSidebarLayout(props: {
2121
layoutPath: string;
22+
engineLinkType: "cloud" | "dedicated";
2223
children: React.ReactNode;
2324
}) {
24-
const { layoutPath, children } = props;
25+
const { layoutPath, engineLinkType, children } = props;
2526

2627
return (
2728
<FullWidthSidebarLayout
@@ -62,9 +63,18 @@ export function ProjectSidebarLayout(props: {
6263
icon: CoinsIcon,
6364
},
6465
{
65-
href: `${layoutPath}/engine`,
66+
href:
67+
engineLinkType === "cloud"
68+
? `${layoutPath}/transactions`
69+
: `${layoutPath}/engine/dedicated`,
6670
label: "Transactions",
6771
icon: ArrowLeftRightIcon,
72+
isActive: (pathname) => {
73+
return (
74+
pathname.startsWith(`${layoutPath}/transactions`) ||
75+
pathname.startsWith(`${layoutPath}/engine/dedicated`)
76+
);
77+
},
6878
},
6979
{
7080
href: `${layoutPath}/insight`,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { getProject } from "@/api/projects";
2+
import { getTeamBySlug } from "@/api/team";
3+
import { TabPathLinks } from "@/components/ui/tabs";
4+
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
5+
import { DeployedContractsPageHeader } from "@app/account/contracts/DeployedContractsPageHeader";
6+
import { getAuthToken } from "@app/api/lib/getAuthToken";
7+
import { loginRedirect } from "@app/login/loginRedirect";
8+
import { redirect } from "next/navigation";
9+
10+
export default async function Layout(props: {
11+
children: React.ReactNode;
12+
params: Promise<{ team_slug: string; project_slug: string }>;
13+
}) {
14+
const params = await props.params;
15+
16+
const [authToken, team, project] = await Promise.all([
17+
getAuthToken(),
18+
getTeamBySlug(params.team_slug),
19+
getProject(params.team_slug, params.project_slug),
20+
]);
21+
22+
if (!authToken) {
23+
loginRedirect(`/team/${params.team_slug}/${params.project_slug}/contracts`);
24+
}
25+
26+
if (!team) {
27+
redirect("/team");
28+
}
29+
30+
if (!project) {
31+
redirect(`/team/${params.team_slug}`);
32+
}
33+
34+
const client = getClientThirdwebClient({
35+
jwt: authToken,
36+
teamId: team.id,
37+
});
38+
39+
const layoutPath = `/team/${params.team_slug}/${params.project_slug}/contracts`;
40+
41+
return (
42+
<div className="flex grow flex-col">
43+
<DeployedContractsPageHeader
44+
teamId={team.id}
45+
projectId={project.id}
46+
client={client}
47+
/>
48+
<TabPathLinks
49+
scrollableClassName="container max-w-7xl"
50+
links={[
51+
{
52+
name: "Contracts",
53+
path: layoutPath,
54+
exactMatch: true,
55+
},
56+
{
57+
name: "Webhooks",
58+
path: `${layoutPath}/webhooks`,
59+
},
60+
]}
61+
/>
62+
<div className="h-6" />
63+
{props.children}
64+
</div>
65+
);
66+
}

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/contracts/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { getProject } from "@/api/projects";
22
import { getTeamBySlug } from "@/api/team";
33
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
4+
import { DeployedContractsPage } from "@app/account/contracts/_components/DeployedContractsPage";
5+
import { getAuthToken } from "@app/api/lib/getAuthToken";
6+
import { loginRedirect } from "@app/login/loginRedirect";
47
import { redirect } from "next/navigation";
5-
import { DeployedContractsPage } from "../../../../../account/contracts/_components/DeployedContractsPage";
6-
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
7-
import { loginRedirect } from "../../../../../login/loginRedirect";
88
import { FooterLinksSection } from "../components/footer/FooterLinksSection";
99

1010
export default async function Page(props: {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { getProject } from "@/api/projects";
2+
import { getTeamBySlug } from "@/api/team";
3+
import { getAuthToken } from "@app/api/lib/getAuthToken";
4+
import { loginRedirect } from "@app/login/loginRedirect";
5+
import { redirect } from "next/navigation";
6+
import { ContractsWebhooksPageContent } from "../../webhooks/contract-webhooks/contract-webhooks-page";
7+
8+
export default async function Page(props: {
9+
params: Promise<{ team_slug: string; project_slug: string }>;
10+
}) {
11+
const params = await props.params;
12+
13+
const [authToken, team, project] = await Promise.all([
14+
getAuthToken(),
15+
getTeamBySlug(params.team_slug),
16+
getProject(params.team_slug, params.project_slug),
17+
]);
18+
19+
if (!authToken) {
20+
loginRedirect(
21+
`/team/${params.team_slug}/${params.project_slug}/contracts/webhooks`,
22+
);
23+
}
24+
25+
if (!team) {
26+
redirect("/team");
27+
}
28+
29+
if (!project) {
30+
redirect(`/team/${params.team_slug}`);
31+
}
32+
33+
return (
34+
<div className="container flex max-w-7xl grow flex-col">
35+
<ContractsWebhooksPageContent project={project} authToken={authToken} />
36+
</div>
37+
);
38+
}

0 commit comments

Comments
 (0)