diff --git a/.gitignore b/.gitignore index b9bd7c27d98..426de0f07b4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ storybook-static .aider* tsconfig.tsbuildinfo -.cursor \ No newline at end of file +.cursor diff --git a/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx b/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx index d0b5b0fb23b..cd67c9ec438 100644 --- a/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx +++ b/apps/dashboard/src/@/components/blocks/SidebarLayout.tsx @@ -113,7 +113,7 @@ function RenderSidebarGroup(props: { return ( - {sidebarLinks.map((link) => { + {sidebarLinks.map((link, idx) => { if ("href" in link) { return ( @@ -137,14 +137,14 @@ function RenderSidebarGroup(props: { } if ("separator" in link) { - return ; + return ; } return ( ); })} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/assets/create-nft/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/assets/create-nft/page.tsx new file mode 100644 index 00000000000..a2761c73b41 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/assets/create-nft/page.tsx @@ -0,0 +1,979 @@ +"use client"; + +import { FormFieldSetup } from "@/components/blocks/FormFieldSetup"; +import { Button } from "@/components/ui/button"; +import { Form } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { useThirdwebClient } from "@/constants/thirdweb.client"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; +import { cn } from "@/lib/utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Fieldset } from "components/contract-components/contract-deploy-form/common"; +import { BasisPointsInput } from "components/inputs/BasisPointsInput"; +import { NetworkSelectorButton } from "components/selects/NetworkSelectorButton"; +import { FileInput } from "components/shared/FileInput"; +import { SolidityInput } from "contract-ui/components/solidity-inputs"; +import { + ArrowLeftIcon, + ArrowRightIcon, + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, + ImageIcon, + Loader2Icon, +} from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { defineChain } from "thirdweb/chains"; +import { + arbitrum, + avalanche, + base, + ethereum, + optimism, + polygon, +} from "thirdweb/chains"; +import { getContract } from "thirdweb/contract"; +import { deployERC721Contract } from "thirdweb/deploys"; +import { setClaimConditions } from "thirdweb/extensions/erc721"; +import { + useActiveAccount, + useActiveWalletChain, + useSwitchActiveWalletChain, +} from "thirdweb/react"; +import { upload } from "thirdweb/storage"; +import { sendTransaction } from "thirdweb/transaction"; +import * as z from "zod"; + +// Form schemas +const collectionInfoSchema = z.object({ + name: z.string().min(1, "Name is required"), + symbol: z + .string() + .min(1, "Symbol is required") + .max(10, "Symbol must be 10 characters or less"), + chain: z.string().min(1, "Chain is required"), + description: z.string().optional(), + asCollection: z.boolean().default(true), + image: z.any().optional(), +}); + +const mintSettingsSchema = z.object({ + price: z.string().default("0.1"), + supply: z.string().min(1, "Supply is required"), + initialMint: z.string().default("1"), + // Royalty settings + royaltyPercentage: z.string().default("0"), + royaltyAddress: z.string().optional(), + collectionType: z.enum(["new", "existing", "project"]).default("new"), + platformFeeBps: z.string().default("250"), // Using basis points (2.5% = 250 basis points) +}); + +type CollectionInfoValues = z.infer; +type MintSettingsValues = z.infer; + +// Step indicator component +const StepIndicator = ({ + step, + currentStep, + label, +}: { + step: number; + currentStep: number; + label: string; +}) => ( +
+
step + ? "bg-primary/20 text-primary" + : "bg-muted text-muted-foreground" + )} + > + {currentStep > step ? ( + + ) : ( + {step} + )} +
+ {label} +
+); + +export default function CreateNFTPage() { + const [currentStep, setCurrentStep] = useState(1); + const [collectionInfo, setCollectionInfo] = + useState(null); + const [mintSettings, setMintSettings] = useState( + null + ); + const [showRoyaltySettings, setShowRoyaltySettings] = useState(false); + const [isDeploying, setIsDeploying] = useState(false); + const router = useDashboardRouter(); + const activeAccount = useActiveAccount(); + const activeChain = useActiveWalletChain(); + const switchChain = useSwitchActiveWalletChain(); + const thirdwebClient = useThirdwebClient(); + const connectedAddress = activeAccount?.address; + + // Forms + const collectionInfoForm = useForm({ + resolver: zodResolver(collectionInfoSchema), + defaultValues: { + name: "", + symbol: "", + chain: activeChain?.id ? activeChain.id.toString() : "Ethereum", + description: "", + asCollection: true, + image: undefined, + }, + }); + + const mintSettingsForm = useForm({ + resolver: zodResolver(mintSettingsSchema), + defaultValues: { + price: "0.1", + supply: "10000", + initialMint: "1", + royaltyPercentage: "0", + royaltyAddress: "", + collectionType: "new", + platformFeeBps: "250", + }, + }); + + // Step handlers + const onCollectionInfoSubmit = (data: CollectionInfoValues) => { + setCollectionInfo(data); + setCurrentStep(2); + }; + + const onMintSettingsSubmit = (data: MintSettingsValues) => { + setMintSettings(data); + setCurrentStep(3); + }; + + const goBack = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1); + } + }; + + // Render functions + const renderStepIndicators = () => ( +
+
+ {/* Segmented lines between circles instead of a single line */} +
+ {/* First segment: between step 1 and 2 */} +
+
1 ? "bg-primary/20" : "bg-muted" + }`} + style={{ marginLeft: "25px", marginRight: "25px" }} + /> +
+ {/* Second segment: between step 2 and 3 */} +
+
2 ? "bg-primary/20" : "bg-muted" + }`} + style={{ marginLeft: "25px", marginRight: "25px" }} + /> +
+
+ + + + +
+
+ ); + + const renderStep1 = () => ( +
+

Create NFT Collection

+

+ Create a collection of NFTs with shared properties +

+ + {renderStepIndicators()} + +
+
+ + collectionInfoForm.setValue("asCollection", checked) + } + /> + +
+ +
+ +
+ + + collectionInfoForm.setValue("image", file, { + shouldTouch: true, + }) + } + className="rounded border-border bg-background transition-all duration-200 hover:border-active-border hover:bg-background" + /> + + +
+
+ + + + + + + +
+ + + { + collectionInfoForm.setValue( + "chain", + chain.chainId?.toString() || "" + ); + }} + /> + + + +