Skip to content
Merged
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
@@ -0,0 +1,19 @@
import { redirect } from "next/navigation";
import { getAuthTokenWalletAddress } from "../../../../api/lib/getAuthToken";
import { DeployedContractsPage } from "../../../../team/[team_slug]/[project_slug]/contracts/_components/DeployedContractsPage";

export default function Page() {
const accountAddress = getAuthTokenWalletAddress();
if (!accountAddress) {
return redirect(
`/login?next=${encodeURIComponent("/dashboard/contracts/deploy")}`,
);
}

return (
<DeployedContractsPage
address={accountAddress}
className="flex grow flex-col"
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BillingAlerts } from "../../../../components/settings/Account/Billing/alerts/Alert";
import { ContractsSidebarLayout } from "../../../../core-ui/sidebar/contracts";

export default function Layout(props: {
children: React.ReactNode;
}) {
return (
<ContractsSidebarLayout>
<BillingAlerts className="pb-6" />
{props.children}
</ContractsSidebarLayout>
);
}
2 changes: 1 addition & 1 deletion apps/dashboard/src/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function DashboardLayout(props: { children: React.ReactNode }) {
<ErrorProvider>
<div className="flex min-h-screen flex-col bg-background">
<DashboardHeader />
<main className="grow">{props.children}</main>
<div className="flex grow flex-col">{props.children}</div>
<AppFooter />
</div>
</ErrorProvider>
Expand Down
16 changes: 16 additions & 0 deletions apps/dashboard/src/app/api/lib/getAuthToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,19 @@ export function getAuthToken() {

return token;
}

export function getAuthTokenWalletAddress() {
const cookiesManager = cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
if (!activeAccount) {
return null;
}

const token = cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value;

if (token) {
return activeAccount;
}

return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";

import { DownloadIcon, PlusIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { Button } from "../../../../../@/components/ui/button";
import { ImportModal } from "../../../../../components/contract-components/import-contract/modal";

export function DeployedContractsPageHeader() {
const [importModalOpen, setImportModalOpen] = useState(false);

return (
<div>
<ImportModal
isOpen={importModalOpen}
onClose={() => {
setImportModalOpen(false);
}}
/>
<div className="flex flex-col gap-4 md:pb-4 lg:flex-row lg:justify-between">
<div>
<h1 className="mb-1.5 font-semibold text-3xl tracking-tight lg:text-4xl">
Your contracts
</h1>
<p className="text-muted-foreground text-sm">
The list of contract instances that you have deployed or imported
with thirdweb across all networks
</p>
</div>
<div className="flex gap-2 [&>*]:grow">
<Button
className="gap-2"
variant="outline"
onClick={() => setImportModalOpen(true)}
>
<DownloadIcon className="size-4" />
Import contract
</Button>
<Button asChild className="gap-2">
<Link href="/explore">
<PlusIcon className="size-4" />
Deploy contract
</Link>
</Button>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Suspense } from "react";
import { ClientOnly } from "../../../../../../components/ClientOnly/ClientOnly";
import { DeployedContractsPageHeader } from "../DeployedContractsPageHeader";
import { DeployedContractsTable } from "./DeployedContractsTable";
import { GetStartedWithContractsDeploy } from "./GetStartedWithContractsDeploy";
import { getSortedDeployedContracts } from "./getSortedDeployedContracts";

export function DeployedContractsPage(props: {
address: string;
className?: string;
}) {
return (
<div className={props.className}>
<DeployedContractsPageHeader />
<div className="h-6" />
<Suspense fallback={<Loading />}>
<DeployedContractsPageAsync {...props} />
</Suspense>
</div>
);
}

async function DeployedContractsPageAsync(props: {
address: string;
}) {
const deployedContracts = await getSortedDeployedContracts({
address: props.address,
});

if (deployedContracts.length === 0) {
return <GetStartedWithContractsDeploy />;
}

return (
<ClientOnly ssr={<Loading />}>
<DeployedContractsTable contracts={deployedContracts} />
</ClientOnly>
);
}

function Loading() {
return (
<div className="flex min-h-[300px] grow items-center justify-center rounded-lg border border-border">
<Spinner className="size-10" />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import type { BasicContract } from "contract-ui/types/types";
import { DeployedContracts } from "../../../../../../components/contract-components/tables/deployed-contracts";

export function DeployedContractsTable(props: {
contracts: BasicContract[];
}) {
const router = useDashboardRouter();
return (
<DeployedContracts
contractList={props.contracts}
limit={50}
isPending={false}
onContractRemoved={router.refresh}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
"use client";
import { TabButtons } from "@/components/ui/tabs";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import Image from "next/image";
import { useMemo, useState } from "react";
import { useActiveAccount } from "thirdweb/react";
import { ImportModal } from "../../../../../../components/contract-components/import-contract/modal";
import { StepsCard } from "../../../../../../components/dashboard/StepsCard";
import { useTrack } from "../../../../../../hooks/analytics/useTrack";

export function GetStartedWithContractsDeploy() {
const address = useActiveAccount()?.address;
const steps = useMemo(
() => [
{
title: "Connect your wallet to get started",
description:
"In order to interact with your contracts you need to connect an EVM compatible wallet.",
children: <CustomConnectWallet />,
completed: !!address,
},

{
title: "Build, deploy or import a contract",
description:
Expand All @@ -29,7 +18,7 @@ export function GetStartedWithContractsDeploy() {
completed: false, // because we only show this component if the user does not have any contracts
},
],
[address],
[],
);

return (
Expand Down Expand Up @@ -139,7 +128,7 @@ const DeployOptions = () => {
<h4 className="text-start font-semibold text-lg">
{activeTabContent.title}
</h4>
<p className="text-muted-foreground text-sm">
<p className="text-start text-muted-foreground text-sm">
{activeTabContent.description}
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { BasicContract } from "contract-ui/types/types";
import { MULTICHAIN_REGISTRY_CONTRACT } from "../../../../../../constants/contracts";
import { getAllMultichainRegistry } from "../../../../../../dashboard-extensions/common/read/getAllMultichainRegistry";
import { fetchChain } from "../../../../../../utils/fetchChain";

export async function getSortedDeployedContracts(params: {
address: string;
}) {
const contracts = await getAllMultichainRegistry({
contract: MULTICHAIN_REGISTRY_CONTRACT,
address: params.address,
});

const chainIds = Array.from(new Set(contracts.map((c) => c.chainId)));
const chains = (
await Promise.allSettled(
chainIds.map((chainId) => fetchChain(chainId.toString())),
)
)
.filter((c) => c.status === "fulfilled")
.map((c) => c.value)
.filter((c) => c !== null);

const mainnetContracts: BasicContract[] = [];
const testnetContracts: BasicContract[] = [];

for (const contract of contracts) {
const chain = chains.find((chain) => contract.chainId === chain.chainId);
if (chain && chain.status !== "deprecated") {
if (chain.testnet) {
testnetContracts.push(contract);
} else {
mainnetContracts.push(contract);
}
}
}

mainnetContracts.sort((a, b) => a.chainId - b.chainId);
testnetContracts.sort((a, b) => a.chainId - b.chainId);

return [...mainnetContracts, ...testnetContracts];
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default function Layout(props: {
children: React.ReactNode;
}) {
return <div className="container">{props.children}</div>;
return <div className="container flex grow flex-col">{props.children}</div>;
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
"use client";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { useAllContractList } from "@3rdweb-sdk/react/hooks/useRegistry";
import { useActiveAccount } from "thirdweb/react";
import { DeployedContracts } from "../../../../../components/contract-components/tables/deployed-contracts";
import { GetStartedWithContractsDeploy } from "./_components/GetStartedWithContractsDeploy";
import { redirect } from "next/navigation";
import { getAuthTokenWalletAddress } from "../../../../api/lib/getAuthToken";
import { DeployedContractsPage } from "./_components/DeployedContractsPage";

export default function Page() {
const address = useActiveAccount()?.address;
const deployedContracts = useAllContractList(address);
const hasContracts =
deployedContracts.data && deployedContracts.data?.length > 0;
export default function Page(props: {
params: { team_slug: string; project_slug: string };
}) {
const { team_slug, project_slug } = props.params;
const accountAddress = getAuthTokenWalletAddress();

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

return (
<div className="pt-10 pb-10">
{!hasContracts ? (
<GetStartedWithContractsDeploy />
) : (
<DeployedContracts contractListQuery={deployedContracts} limit={50} />
)}
</div>
<DeployedContractsPage
address={accountAddress}
className="flex grow flex-col pt-10 pb-10"
/>
);
}
Loading
Loading