Skip to content

Commit be1b851

Browse files
committed
Move Deployed Contracts Page to App Router
1 parent 926d823 commit be1b851

File tree

12 files changed

+176
-145
lines changed

12 files changed

+176
-145
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
import { Spinner } from "@/components/ui/Spinner/Spinner";
4+
import { useAllContractList } from "@3rdweb-sdk/react/hooks/useRegistry";
5+
import { DeployedContracts } from "../../../../../components/contract-components/tables/deployed-contracts";
6+
import { GetStartedWithContractsDeploy } from "../../../../team/[team_slug]/[project_slug]/contracts/_components/GetStartedWithContractsDeploy";
7+
8+
export function DeployedContractsPage(props: {
9+
address: string;
10+
}) {
11+
const deployedContracts = useAllContractList(props.address);
12+
13+
if (deployedContracts.isPending) {
14+
return (
15+
<div className="flex min-h-[300px] grow items-center justify-center rounded-lg border border-border">
16+
<Spinner className="size-10" />
17+
</div>
18+
);
19+
}
20+
const hasContracts =
21+
deployedContracts.data && deployedContracts.data.length > 0;
22+
23+
if (!hasContracts) {
24+
return <GetStartedWithContractsDeploy />;
25+
}
26+
27+
return (
28+
<DeployedContracts
29+
contractList={deployedContracts.data}
30+
limit={50}
31+
isPending={deployedContracts.isPending}
32+
/>
33+
);
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use client";
2+
3+
import { DownloadIcon, PlusIcon } from "lucide-react";
4+
import Link from "next/link";
5+
import { useState } from "react";
6+
import { Button } from "../../../../../@/components/ui/button";
7+
import { ImportModal } from "../../../../../components/contract-components/import-contract/modal";
8+
9+
export function DeployedContractsPageHeader() {
10+
const [importModalOpen, setImportModalOpen] = useState(false);
11+
12+
return (
13+
<div>
14+
<ImportModal
15+
isOpen={importModalOpen}
16+
onClose={() => {
17+
setImportModalOpen(false);
18+
}}
19+
/>
20+
<div className="flex flex-col gap-4 md:pb-4 lg:flex-row lg:justify-between">
21+
<div>
22+
<h1 className="mb-1.5 font-semibold text-3xl tracking-tight lg:text-4xl">
23+
Your contracts
24+
</h1>
25+
<p className="text-muted-foreground text-sm">
26+
The list of contract instances that you have deployed or imported
27+
with thirdweb across all networks
28+
</p>
29+
</div>
30+
<div className="flex gap-2 [&>*]:grow">
31+
<Button
32+
className="gap-2"
33+
variant="outline"
34+
onClick={() => setImportModalOpen(true)}
35+
>
36+
<DownloadIcon className="size-4" />
37+
Import contract
38+
</Button>
39+
<Button asChild className="gap-2">
40+
<Link href="/explore">
41+
<PlusIcon className="size-4" />
42+
Deploy contract
43+
</Link>
44+
</Button>
45+
</div>
46+
</div>
47+
</div>
48+
);
49+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { redirect } from "next/navigation";
2+
import { getAuthTokenWalletAddress } from "../../../../api/lib/getAuthToken";
3+
import { DeployedContractsPage } from "./DeployedContractsPage";
4+
import { DeployedContractsPageHeader } from "./PageHeader";
5+
6+
export default function Page() {
7+
const accountAddress = getAuthTokenWalletAddress();
8+
if (!accountAddress) {
9+
return redirect(`/login?next=${encodeURIComponent("/dashboard")}`);
10+
}
11+
12+
return (
13+
<div className="flex grow flex-col">
14+
<DeployedContractsPageHeader />
15+
<div className="h-6" />
16+
<DeployedContractsPage address={accountAddress} />
17+
</div>
18+
);
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { BillingAlerts } from "../../../../components/settings/Account/Billing/alerts/Alert";
2+
import { ContractsSidebarLayout } from "../../../../core-ui/sidebar/contracts";
3+
4+
export default function Layout(props: {
5+
children: React.ReactNode;
6+
}) {
7+
return (
8+
<ContractsSidebarLayout>
9+
<BillingAlerts className="pb-6" />
10+
{props.children}
11+
</ContractsSidebarLayout>
12+
);
13+
}

apps/dashboard/src/app/(dashboard)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function DashboardLayout(props: { children: React.ReactNode }) {
77
<ErrorProvider>
88
<div className="flex min-h-screen flex-col bg-background">
99
<DashboardHeader />
10-
<main className="grow">{props.children}</main>
10+
<div className="flex grow flex-col">{props.children}</div>
1111
<AppFooter />
1212
</div>
1313
</ErrorProvider>

apps/dashboard/src/app/api/lib/getAuthToken.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,19 @@ export function getAuthToken() {
1010

1111
return token;
1212
}
13+
14+
export function getAuthTokenWalletAddress() {
15+
const cookiesManager = cookies();
16+
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
17+
if (!activeAccount) {
18+
return null;
19+
}
20+
21+
const token = cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value;
22+
23+
if (token) {
24+
return activeAccount;
25+
}
26+
27+
return null;
28+
}

apps/dashboard/src/app/team/[team_slug]/[project_slug]/contracts/_components/GetStartedWithContractsDeploy.tsx

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
"use client";
22
import { TabButtons } from "@/components/ui/tabs";
33
import { useDashboardRouter } from "@/lib/DashboardRouter";
4-
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
54
import Image from "next/image";
65
import { useMemo, useState } from "react";
7-
import { useActiveAccount } from "thirdweb/react";
86
import { ImportModal } from "../../../../../../components/contract-components/import-contract/modal";
97
import { StepsCard } from "../../../../../../components/dashboard/StepsCard";
108
import { useTrack } from "../../../../../../hooks/analytics/useTrack";
119

1210
export function GetStartedWithContractsDeploy() {
13-
const address = useActiveAccount()?.address;
1411
const steps = useMemo(
1512
() => [
16-
{
17-
title: "Connect your wallet to get started",
18-
description:
19-
"In order to interact with your contracts you need to connect an EVM compatible wallet.",
20-
children: <CustomConnectWallet />,
21-
completed: !!address,
22-
},
23-
2413
{
2514
title: "Build, deploy or import a contract",
2615
description:
@@ -29,7 +18,7 @@ export function GetStartedWithContractsDeploy() {
2918
completed: false, // because we only show this component if the user does not have any contracts
3019
},
3120
],
32-
[address],
21+
[],
3322
);
3423

3524
return (
@@ -139,7 +128,7 @@ const DeployOptions = () => {
139128
<h4 className="text-start font-semibold text-lg">
140129
{activeTabContent.title}
141130
</h4>
142-
<p className="text-muted-foreground text-sm">
131+
<p className="text-start text-muted-foreground text-sm">
143132
{activeTabContent.description}
144133
</p>
145134
</div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default function Layout(props: {
22
children: React.ReactNode;
33
}) {
4-
return <div className="container">{props.children}</div>;
4+
return <div className="container flex grow flex-col">{props.children}</div>;
55
}
Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,25 @@
1-
"use client";
2-
import { Spinner } from "@/components/ui/Spinner/Spinner";
3-
import { useAllContractList } from "@3rdweb-sdk/react/hooks/useRegistry";
4-
import { useActiveAccount } from "thirdweb/react";
5-
import { DeployedContracts } from "../../../../../components/contract-components/tables/deployed-contracts";
6-
import { GetStartedWithContractsDeploy } from "./_components/GetStartedWithContractsDeploy";
1+
import { redirect } from "next/navigation";
2+
import { DeployedContractsPage } from "../../../../(dashboard)/dashboard/contracts/deploy/DeployedContractsPage";
3+
import { DeployedContractsPageHeader } from "../../../../(dashboard)/dashboard/contracts/deploy/PageHeader";
4+
import { getAuthTokenWalletAddress } from "../../../../api/lib/getAuthToken";
75

8-
export default function Page() {
9-
const address = useActiveAccount()?.address;
10-
const deployedContracts = useAllContractList(address);
11-
const hasContracts =
12-
deployedContracts.data && deployedContracts.data?.length > 0;
6+
export default function Page(props: {
7+
params: { team_slug: string; project_slug: string };
8+
}) {
9+
const { team_slug, project_slug } = props.params;
10+
const accountAddress = getAuthTokenWalletAddress();
1311

14-
if (deployedContracts.isPending) {
15-
return (
16-
<div className="flex min-h-[400px] items-center justify-center">
17-
<Spinner className="size-10" />
18-
</div>
12+
if (!accountAddress) {
13+
return redirect(
14+
`/login?next=${encodeURIComponent(`/team/${team_slug}/${project_slug}/contracts`)}`,
1915
);
2016
}
2117

2218
return (
23-
<div className="pt-10 pb-10">
24-
{!hasContracts ? (
25-
<GetStartedWithContractsDeploy />
26-
) : (
27-
<DeployedContracts contractListQuery={deployedContracts} limit={50} />
28-
)}
19+
<div className="flex grow flex-col pt-10 pb-10">
20+
<DeployedContractsPageHeader />
21+
<div className="h-6" />
22+
<DeployedContractsPage address={accountAddress} />
2923
</div>
3024
);
3125
}

apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx

Lines changed: 23 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,7 @@ import {
2626
import { ChainIcon } from "components/icons/ChainIcon";
2727
import { NetworkSelectDropdown } from "components/selects/NetworkSelectDropdown";
2828
import type { BasicContract } from "contract-ui/types/types";
29-
import {
30-
DownloadIcon,
31-
EllipsisVerticalIcon,
32-
PlusIcon,
33-
XIcon,
34-
} from "lucide-react";
35-
import Link from "next/link";
29+
import { EllipsisVerticalIcon, XIcon } from "lucide-react";
3630
import { memo, useEffect, useMemo, useState } from "react";
3731
import {
3832
type Column,
@@ -42,82 +36,42 @@ import {
4236
usePagination,
4337
useTable,
4438
} from "react-table";
39+
import { SkeletonContainer } from "../../../@/components/ui/skeleton";
4540
import { useAllChainsData } from "../../../hooks/chains/allChains";
4641
import { useChainSlug } from "../../../hooks/chains/chainSlug";
47-
import { ImportModal } from "../import-contract/modal";
4842
import { AsyncContractNameCell, AsyncContractTypeCell } from "./cells";
4943
import { ShowMoreButton } from "./show-more-button";
5044

5145
interface DeployedContractsProps {
52-
noHeader?: boolean;
53-
contractListQuery: ReturnType<typeof useAllContractList>;
46+
contractList: ReturnType<typeof useAllContractList>["data"];
47+
isPending: boolean;
5448
limit?: number;
5549
}
5650

5751
export const DeployedContracts: React.FC<DeployedContractsProps> = ({
58-
noHeader,
59-
contractListQuery,
52+
contractList,
6053
limit = 10,
54+
isPending,
6155
}) => {
62-
const [importModalOpen, setImportModalOpen] = useState(false);
63-
6456
const chainIdsWithDeployments = useMemo(() => {
6557
const set = new Set<number>();
6658
// biome-ignore lint/complexity/noForEach: FIXME
67-
contractListQuery.data.forEach((contract) => {
59+
contractList.forEach((contract) => {
6860
set.add(contract.chainId);
6961
});
7062
return [...set];
71-
}, [contractListQuery.data]);
63+
}, [contractList]);
7264

7365
return (
7466
<div className="flex flex-col gap-8">
75-
{!noHeader && (
76-
<>
77-
<ImportModal
78-
isOpen={importModalOpen}
79-
onClose={() => {
80-
setImportModalOpen(false);
81-
}}
82-
/>
83-
<div className="flex flex-col gap-4 md:pb-4 lg:flex-row lg:justify-between">
84-
<div>
85-
<h1 className="mb-1.5 font-semibold text-3xl tracking-tight lg:text-4xl">
86-
Your contracts
87-
</h1>
88-
<p className="text-muted-foreground text-sm">
89-
The list of contract instances that you have deployed or
90-
imported with thirdweb across all networks
91-
</p>
92-
</div>
93-
<div className="flex gap-2 [&>*]:grow">
94-
<Button
95-
className="gap-2"
96-
variant="outline"
97-
onClick={() => setImportModalOpen(true)}
98-
>
99-
<DownloadIcon className="size-4" />
100-
Import contract
101-
</Button>
102-
<Button asChild className="gap-2">
103-
<Link href="/explore">
104-
<PlusIcon className="size-4" />
105-
Deploy contract
106-
</Link>
107-
</Button>
108-
</div>
109-
</div>
110-
</>
111-
)}
112-
11367
<ContractTable
114-
combinedList={contractListQuery.data}
68+
combinedList={contractList}
11569
limit={limit}
11670
chainIdsWithDeployments={chainIdsWithDeployments}
117-
loading={contractListQuery.isPending}
71+
loading={isPending}
11872
/>
11973

120-
{contractListQuery.data.length === 0 && contractListQuery.isFetched && (
74+
{contractList.length === 0 && !isPending && (
12175
<div className="flex h-[100px] items-center justify-center text-muted-foreground">
12276
No contracts found
12377
</div>
@@ -142,7 +96,7 @@ const RemoveFromDashboardButton: React.FC<RemoveFromDashboardButtonProps> = ({
14296
variant="ghost"
14397
onClick={(e) => {
14498
e.stopPropagation();
145-
mutation.mutate({ chainId, contractAddress });
99+
mutation.mutateAsync({ chainId, contractAddress });
146100
}}
147101
disabled={mutation.isPending}
148102
className="!bg-background hover:!bg-accent gap-2"
@@ -236,9 +190,14 @@ const ContractTable: React.FC<ContractTableProps> = ({
236190
return (
237191
<div className="flex items-center gap-2">
238192
<ChainIcon size={24} ipfsSrc={data?.icon?.url} />
239-
<p className="text-muted-foreground text-sm">
240-
{cleanedChainName}
241-
</p>
193+
<SkeletonContainer
194+
loadedData={data ? cleanedChainName : undefined}
195+
skeletonData={`Chain ID ${cell.row.original.chainId}`}
196+
render={(v) => {
197+
return <p className="text-muted-foreground text-sm">{v}</p>;
198+
}}
199+
/>
200+
242201
{data?.testnet && (
243202
<Badge variant="outline" className="text-muted-foreground">
244203
Testnet
@@ -395,10 +354,12 @@ const ContractTable: React.FC<ContractTableProps> = ({
395354
const ContractTableRow = memo(({ row }: { row: Row<BasicContract> }) => {
396355
const chainSlug = useChainSlug(row.original.chainId);
397356
const router = useDashboardRouter();
357+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
358+
const { key, ...rowProps } = row.getRowProps();
398359

399360
return (
400361
<TableRow
401-
{...row.getRowProps()}
362+
{...rowProps}
402363
role="group"
403364
className="cursor-pointer hover:bg-muted/50"
404365
// TODO - replace this with before:absolute thing

0 commit comments

Comments
 (0)