Skip to content

Commit 7e3947b

Browse files
committed
update
1 parent a1b518c commit 7e3947b

File tree

6 files changed

+367
-0
lines changed

6 files changed

+367
-0
lines changed
1.39 MB
Binary file not shown.

apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const CustomConnectWallet = (props: {
3535
connectButtonClassName?: string;
3636
signInLinkButtonClassName?: string;
3737
detailsButtonClassName?: string;
38+
chain?: Chain;
3839
}) => {
3940
const thirdwebClient = useThirdwebClient();
4041
const loginRequired =
@@ -204,6 +205,7 @@ export const CustomConnectWallet = (props: {
204205
},
205206
},
206207
}}
208+
chain={props.chain}
207209
/>
208210

209211
<LazyConfigureNetworkModal
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { AppFooter } from "@/components/blocks/app-footer";
2+
import { ErrorProvider } from "contexts/error-handler";
3+
4+
export default function DashboardLayout(props: { children: React.ReactNode }) {
5+
return (
6+
<ErrorProvider>
7+
<div className="flex min-h-screen flex-col bg-background">
8+
{/* <DashboardHeader /> */}
9+
<div className="flex grow flex-col">{props.children}</div>
10+
<AppFooter />
11+
</div>
12+
</ErrorProvider>
13+
);
14+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
"use client";
2+
3+
import { Badge } from "@/components/ui/badge";
4+
import { Button } from "@/components/ui/button";
5+
import { Card, CardContent, CardFooter } from "@/components/ui/card";
6+
import { Input } from "@/components/ui/input";
7+
import { Label } from "@/components/ui/label";
8+
import { Switch } from "@/components/ui/switch";
9+
import { useThirdwebClient } from "@/constants/thirdweb.client";
10+
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
11+
import { MinusIcon, PlusIcon } from "lucide-react";
12+
import { useState } from "react";
13+
import type React from "react";
14+
import { toast } from "sonner";
15+
import type { ThirdwebContract } from "thirdweb";
16+
import { ClaimButton, MediaRenderer, useActiveAccount } from "thirdweb/react";
17+
18+
type Props = {
19+
contract: ThirdwebContract;
20+
displayName: string;
21+
description: string;
22+
thumbnail: string;
23+
hideQuantitySelector?: boolean;
24+
hideMintToCustomAddress?: boolean;
25+
} & ({ type: "erc1155"; tokenId: bigint } | { type: "erc721" }) &
26+
(
27+
| {
28+
pricePerToken: number;
29+
currencySymbol: string | null;
30+
noActiveClaimCondition: false;
31+
}
32+
| { noActiveClaimCondition: true }
33+
);
34+
35+
export function NftMint(props: Props) {
36+
const [isMinting, setIsMinting] = useState(false);
37+
const [quantity, setQuantity] = useState(1);
38+
const [useCustomAddress, setUseCustomAddress] = useState(false);
39+
const [customAddress, setCustomAddress] = useState("");
40+
const account = useActiveAccount();
41+
const client = useThirdwebClient();
42+
43+
const decreaseQuantity = () => {
44+
setQuantity((prev) => Math.max(1, prev - 1));
45+
};
46+
47+
const increaseQuantity = () => {
48+
setQuantity((prev) => prev + 1); // Assuming a max of 10 NFTs can be minted at once
49+
};
50+
51+
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
52+
const value = Number.parseInt(e.target.value);
53+
if (!Number.isNaN(value)) {
54+
setQuantity(Math.min(Math.max(1, value)));
55+
}
56+
};
57+
58+
return (
59+
<div className="mx-4 my-16 flex flex-col items-center justify-center transition-colors duration-200">
60+
<Card className="w-full max-w-md">
61+
<CardContent className="pt-6">
62+
<div className="relative mb-4 aspect-square overflow-hidden rounded-lg">
63+
<MediaRenderer
64+
client={client}
65+
className="h-full w-full object-cover"
66+
alt=""
67+
src={props.thumbnail}
68+
/>
69+
{!props.noActiveClaimCondition && (
70+
<Badge className="absolute top-2 right-2">
71+
{props.pricePerToken === 0
72+
? "Free"
73+
: `${props.pricePerToken} ${props.currencySymbol}/each`}
74+
</Badge>
75+
)}
76+
</div>
77+
<h2 className="mb-2 font-bold text-2xl">{props.displayName}</h2>
78+
<p className="mb-4 text-muted-foreground">{props.description}</p>
79+
{!props.hideQuantitySelector && !props.noActiveClaimCondition && (
80+
<div className="mb-4 flex items-center justify-between">
81+
<div className="flex items-center">
82+
<Button
83+
variant="outline"
84+
size="icon"
85+
onClick={decreaseQuantity}
86+
disabled={quantity <= 1}
87+
aria-label="Decrease quantity"
88+
className="rounded-r-none"
89+
>
90+
<MinusIcon className="h-4 w-4" />
91+
</Button>
92+
<Input
93+
type="number"
94+
value={quantity}
95+
onChange={handleQuantityChange}
96+
className="w-28 rounded-none border-x-0 pl-6 text-center"
97+
min="1"
98+
/>
99+
<Button
100+
variant="outline"
101+
size="icon"
102+
onClick={increaseQuantity}
103+
aria-label="Increase quantity"
104+
className="rounded-l-none"
105+
>
106+
<PlusIcon className="h-4 w-4" />
107+
</Button>
108+
</div>
109+
<div className="pr-1 font-semibold text-base">
110+
Total: {props.pricePerToken * quantity} {props.currencySymbol}
111+
</div>
112+
</div>
113+
)}
114+
115+
{!props.hideMintToCustomAddress && (
116+
<div className="mb-4 flex items-center space-x-2">
117+
<Switch
118+
id="custom-address"
119+
checked={useCustomAddress}
120+
onCheckedChange={setUseCustomAddress}
121+
/>
122+
<Label
123+
htmlFor="custom-address"
124+
className={`${useCustomAddress ? "" : "text-gray-400"} cursor-pointer`}
125+
>
126+
Mint to a custom address
127+
</Label>
128+
</div>
129+
)}
130+
{useCustomAddress && (
131+
<div className="mb-4">
132+
<Input
133+
id="address-input"
134+
type="text"
135+
placeholder="Enter recipient address"
136+
value={customAddress}
137+
onChange={(e) => setCustomAddress(e.target.value)}
138+
className="w-full"
139+
/>
140+
</div>
141+
)}
142+
</CardContent>
143+
<CardFooter>
144+
{account ? (
145+
<ClaimButton
146+
style={{ width: "100%" }}
147+
contractAddress={props.contract.address}
148+
chain={props.contract.chain}
149+
client={props.contract.client}
150+
claimParams={
151+
props.type === "erc1155"
152+
? {
153+
type: "ERC1155",
154+
tokenId: props.tokenId,
155+
quantity: BigInt(quantity),
156+
to: customAddress || account.address,
157+
from: account.address,
158+
}
159+
: {
160+
type: "ERC721",
161+
quantity: BigInt(quantity),
162+
to: customAddress || account.address,
163+
from: account.address,
164+
}
165+
}
166+
disabled={isMinting || props.noActiveClaimCondition}
167+
onTransactionSent={() => {
168+
toast.loading("Minting NFT", { id: "toastId" });
169+
setIsMinting(true);
170+
}}
171+
onTransactionConfirmed={() => {
172+
toast.success("Minted successfully", { id: "toastId" });
173+
setIsMinting(false);
174+
}}
175+
onError={(err) => {
176+
toast.error(err.message, { id: "toastId" });
177+
setIsMinting(false);
178+
}}
179+
>
180+
{props.noActiveClaimCondition
181+
? "Minting not ready"
182+
: `${quantity > 1 ? `Mint ${quantity} NFTs` : "Mint"}`}
183+
</ClaimButton>
184+
) : (
185+
<CustomConnectWallet
186+
loginRequired={false}
187+
connectButtonClassName="!w-full !rounded !bg-primary !text-primary-foreground !px-4 !py-2 !text-sm"
188+
chain={props.contract.chain}
189+
/>
190+
)}
191+
</CardFooter>
192+
</Card>
193+
</div>
194+
);
195+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { getThirdwebClient } from "@/constants/thirdweb.server";
2+
import { defineDashboardChain } from "lib/defineDashboardChain";
3+
import type { Metadata } from "next";
4+
import { notFound } from "next/navigation";
5+
import { getContract, toTokens } from "thirdweb";
6+
import { getContractMetadata } from "thirdweb/extensions/common";
7+
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
8+
import {
9+
getActiveClaimCondition as getActiveClaimCondition721,
10+
getNFT as getNFT721,
11+
} from "thirdweb/extensions/erc721";
12+
import {
13+
getActiveClaimCondition as getActiveClaimCondition1155,
14+
getNFT as getNFT1155,
15+
} from "thirdweb/extensions/erc1155";
16+
import { NftMint } from "./mint-ui";
17+
18+
type DropPageData = {
19+
slug: string;
20+
contractAddress: string;
21+
chainId: number;
22+
hideQuantitySelector?: boolean;
23+
hideMintToCustomAddress?: boolean;
24+
// If not defined, we will use the image of the NFT or contract's image
25+
thumbnail?: string;
26+
metadata?: Metadata;
27+
} & ({ type: "erc1155"; tokenId: bigint } | { type: "erc721" });
28+
29+
const DROP_PAGES: DropPageData[] = [
30+
{
31+
slug: "test",
32+
type: "erc1155",
33+
contractAddress: "0xBD9d7f15f3C850B35c30b8F9F698B511c20b7263",
34+
tokenId: 0n,
35+
chainId: 11155111,
36+
hideQuantitySelector: true,
37+
hideMintToCustomAddress: true,
38+
thumbnail: "/drops/zerion.mp4",
39+
},
40+
{
41+
slug: "zero-chain-announcement",
42+
type: "erc1155",
43+
contractAddress: "0x78264a0af02d894f2d9ae3e11E4a503b352CC437",
44+
tokenId: 0n,
45+
chainId: 543210,
46+
hideMintToCustomAddress: true,
47+
hideQuantitySelector: true,
48+
thumbnail: "/drops/zerion.mp4",
49+
metadata: {
50+
title: "ZERO x thirdweb",
51+
description:
52+
"This exclusive commemorative NFT marks the official launch of ZERϴ's mainnet and our exciting partnership with thirdweb. Own a piece of this milestone in blockchain history as we make onchain transactions free with zero.network",
53+
},
54+
},
55+
56+
// Add more chains here
57+
];
58+
59+
export async function generateMetadata({
60+
params,
61+
}: { params: Promise<{ slug: string }> }) {
62+
const { slug } = await params;
63+
const project = DROP_PAGES.find((p) => p.slug === slug);
64+
return project?.metadata;
65+
}
66+
67+
export default async function DropPage({
68+
params,
69+
}: { params: Promise<{ slug: string }> }) {
70+
const { slug } = await params;
71+
72+
const project = DROP_PAGES.find((p) => p.slug === slug);
73+
74+
if (!project) {
75+
return notFound();
76+
}
77+
// eslint-disable-next-line no-restricted-syntax
78+
const chain = defineDashboardChain(project.chainId, undefined);
79+
const client = getThirdwebClient();
80+
81+
const contract = getContract({
82+
address: project.contractAddress,
83+
chain,
84+
client,
85+
});
86+
87+
const [nft, claimCondition, contractMetadata] = await Promise.all([
88+
project.type === "erc1155"
89+
? getNFT1155({ contract, tokenId: project.tokenId })
90+
: getNFT721({ contract, tokenId: 0n }),
91+
project.type === "erc1155"
92+
? getActiveClaimCondition1155({
93+
contract,
94+
tokenId: project.tokenId,
95+
}).catch(() => undefined)
96+
: getActiveClaimCondition721({ contract }).catch(() => undefined),
97+
getContractMetadata({ contract }),
98+
]);
99+
100+
const thumbnail =
101+
project.thumbnail || nft.metadata.image || contractMetadata.image || "";
102+
103+
const displayName = contractMetadata.name || nft.metadata.name || "";
104+
105+
const description =
106+
typeof contractMetadata.description === "string" &&
107+
contractMetadata.description
108+
? contractMetadata.description
109+
: nft.metadata.description || "";
110+
111+
if (!claimCondition) {
112+
return (
113+
<NftMint
114+
contract={contract}
115+
displayName={displayName}
116+
thumbnail={thumbnail}
117+
description={description}
118+
{...project}
119+
noActiveClaimCondition
120+
/>
121+
);
122+
}
123+
124+
const currencyMetadata = claimCondition.currency
125+
? await getCurrencyMetadata({
126+
contract: getContract({
127+
address: claimCondition.currency,
128+
chain,
129+
client,
130+
}),
131+
})
132+
: undefined;
133+
134+
if (!currencyMetadata) {
135+
return notFound();
136+
}
137+
138+
const pricePerToken = Number(
139+
toTokens(claimCondition.pricePerToken, currencyMetadata.decimals),
140+
);
141+
142+
return (
143+
<NftMint
144+
contract={contract}
145+
displayName={displayName || ""}
146+
thumbnail={thumbnail}
147+
description={description || ""}
148+
currencySymbol={currencyMetadata.symbol}
149+
pricePerToken={pricePerToken}
150+
noActiveClaimCondition={false}
151+
{...project}
152+
/>
153+
);
154+
}

apps/dashboard/src/components/product-pages/common/Topnav.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { Box, Container, Flex, useBreakpointValue } from "@chakra-ui/react";
24
import { useScrollPosition } from "@n8tb1t/use-scroll-position";
35
import { Logo } from "components/logo";

0 commit comments

Comments
 (0)