bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
17 lines • 40.2 kB
JSON
{
"name": "inscriptions-components-collectionmintbutton",
"type": "registry:component",
"dependencies": [
"@radix-ui/react-icons"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/inscriptions/components/CollectionMintButton.tsx",
"type": "registry:component",
"content": "/**\n * CollectionMintButton Component\n *\n * Component for creating NFT collections with metadata, traits, and rarities\n * Collections are parent inscriptions that define the structure for collection items\n */\n\nimport { ImageIcon, InfoCircledIcon, PlusIcon } from \"@radix-ui/react-icons\";\nimport {\n\ttype CollectionSubTypeData,\n\ttype CollectionTrait,\n\ttype CollectionTraits,\n\ttype PreMAP,\n\tRoytaltyType,\n} from \"js-1sat-ord\";\nimport { useCallback, useState } from \"react\";\nimport { DropZone } from \"../../ui-components/DropZone.js\";\nimport { ErrorDisplay } from \"../../ui-components/ErrorDisplay.js\";\nimport { LoadingButton } from \"../../ui-components/LoadingButton.js\";\nimport { StepIndicator } from \"../../ui-components/StepIndicator.js\";\nimport { Badge } from \"../../ui/badge.js\";\nimport { Button } from \"../../ui/button.js\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogHeader,\n\tDialogTitle,\n} from \"../../ui/dialog.js\";\nimport { Input } from \"../../ui/input.js\";\nimport { ScrollArea } from \"../../ui/scroll-area.js\";\nimport { Separator } from \"../../ui/separator.js\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../../ui/tabs.js\";\nimport { Textarea } from \"../../ui/textarea.js\";\nimport { useInscription } from \"../hooks/useInscription.js\";\n\n// Collection types\nexport interface CollectionRarity {\n\tlabel: string;\n\tpercentage: string; // Decimal format: \"0.5000\" = 50%\n\titems?: string; // Number of items with this rarity\n}\n\n// Remove our CollectionTrait interface - use the one from js-1sat-ord\n// which doesn't have a name field (the trait name is the object key)\n\nexport interface CollectionRoyalty {\n\ttype: RoytaltyType;\n\tdestination: string;\n\tpercentage: string; // Decimal format: \"0.0100\" = 1%\n}\n\nexport interface CollectionData {\n\t// Basic info\n\tname: string;\n\tdescription: string;\n\tquantity?: number;\n\t// Cover image\n\tcoverImage: File | null;\n\t// Rarities\n\trarities: CollectionRarity[];\n\t// Traits\n\ttraits: CollectionTraits;\n\t// Royalties\n\troyalties: CollectionRoyalty[];\n}\n\nexport interface CollectionMintButtonProps {\n\t/** Pre-filled collection data */\n\tcollection?: Partial<CollectionData>;\n\t/** Button text */\n\tbuttonText?: string;\n\t/** Dialog title */\n\tdialogTitle?: string;\n\t/** Button variant */\n\tvariant?: \"solid\" | \"soft\" | \"outline\" | \"ghost\";\n\t/** Button size */\n\tsize?: \"1\" | \"2\" | \"3\" | \"4\";\n\t/** Button color */\n\tcolor?: \"blue\" | \"green\" | \"red\" | \"gray\";\n\t/** Additional CSS classes */\n\tclassName?: string;\n\t/** Whether button is disabled */\n\tdisabled?: boolean;\n\t/** Success callback */\n\tonSuccess?: (txid: string, collection: CollectionData) => void;\n\t/** Error callback */\n\tonError?: (error: Error) => void;\n}\n\nconst SUPPORTED_IMAGE_TYPES = [\n\t\"image/png\",\n\t\"image/jpeg\",\n\t\"image/jpg\",\n\t\"image/gif\",\n\t\"image/webp\",\n];\nconst MAX_IMAGE_SIZE = 1024 * 1024; // 1MB\nconst MAX_IMAGE_DIMENSION = 2048; // 2048x2048px\n\ntype Step = \"basic\" | \"rarities\" | \"traits\" | \"royalties\" | \"review\";\n\nconst STEPS: Step[] = [\"basic\", \"rarities\", \"traits\", \"royalties\", \"review\"];\n\nexport function CollectionMintButton({\n\tcollection: initialCollection,\n\tbuttonText = \"Create Collection\",\n\tdialogTitle = \"Create NFT Collection\",\n\tvariant = \"solid\",\n\tsize = \"2\",\n\tcolor = \"blue\",\n\tclassName = \"\",\n\tdisabled = false,\n\tonSuccess,\n\tonError,\n}: CollectionMintButtonProps) {\n\tconst [open, setOpen] = useState(false);\n\tconst [currentStep, setCurrentStep] = useState<Step>(\"basic\");\n\tconst currentStepIndex = STEPS.indexOf(currentStep);\n\n\t// Form state\n\tconst [name, setName] = useState(initialCollection?.name || \"\");\n\tconst [description, setDescription] = useState(\n\t\tinitialCollection?.description || \"\",\n\t);\n\tconst [quantity, setQuantity] = useState(\n\t\tinitialCollection?.quantity?.toString() || \"\",\n\t);\n\tconst [coverImage, setCoverImage] = useState<File | null>(\n\t\tinitialCollection?.coverImage || null,\n\t);\n\tconst [coverImagePreview, setCoverImagePreview] = useState<string | null>(\n\t\tnull,\n\t);\n\tconst [rarities, setRarities] = useState<CollectionRarity[]>(\n\t\tinitialCollection?.rarities || [\n\t\t\t{ label: \"Common\", percentage: \"1.0000\", items: \"\" },\n\t\t],\n\t);\n\tconst [traits, setTraits] = useState<Record<string, CollectionTrait>>(\n\t\tinitialCollection?.traits || {},\n\t);\n\tconst [royalties, setRoyalties] = useState<CollectionRoyalty[]>(\n\t\tinitialCollection?.royalties || [],\n\t);\n\n\t// Validation errors\n\tconst [errors, setErrors] = useState<Record<string, string>>({});\n\n\t// Inscription hook\n\tconst {\n\t\tinscribe,\n\t\tloading,\n\t\terror: inscriptionError,\n\t} = useInscription({\n\t\tonSuccess: (result) => {\n\t\t\tconst collectionData: CollectionData = {\n\t\t\t\tname,\n\t\t\t\tdescription,\n\t\t\t\tquantity: quantity ? Number.parseInt(quantity) : undefined,\n\t\t\t\tcoverImage,\n\t\t\t\trarities,\n\t\t\t\ttraits,\n\t\t\t\troyalties,\n\t\t\t};\n\t\t\tonSuccess?.(result.txid, collectionData);\n\t\t\thandleClose();\n\t\t},\n\t\tonError,\n\t});\n\n\t// Handle cover image selection\n\tconst handleImageSelect = useCallback((file: File) => {\n\t\tconst newErrors: Record<string, string> = {};\n\n\t\t// Validate file type\n\t\tif (!SUPPORTED_IMAGE_TYPES.includes(file.type)) {\n\t\t\tnewErrors.coverImage =\n\t\t\t\t\"Unsupported image type. Please use PNG, JPEG, GIF, or WebP.\";\n\t\t\tsetErrors(newErrors);\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate file size\n\t\tif (file.size > MAX_IMAGE_SIZE) {\n\t\t\tnewErrors.coverImage = \"Image too large. Maximum size is 1MB.\";\n\t\t\tsetErrors(newErrors);\n\t\t\treturn;\n\t\t}\n\n\t\t// Create preview and validate dimensions\n\t\tconst reader = new FileReader();\n\t\treader.onload = (e) => {\n\t\t\tconst img = new Image();\n\t\t\timg.onload = () => {\n\t\t\t\t// Check if image is square\n\t\t\t\tif (img.width !== img.height) {\n\t\t\t\t\tnewErrors.coverImage =\n\t\t\t\t\t\t\"Image must be square (same width and height).\";\n\t\t\t\t\tsetErrors(newErrors);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Check dimensions\n\t\t\t\tif (\n\t\t\t\t\timg.width > MAX_IMAGE_DIMENSION ||\n\t\t\t\t\timg.height > MAX_IMAGE_DIMENSION\n\t\t\t\t) {\n\t\t\t\t\tnewErrors.coverImage = `Image too large. Maximum dimensions are ${MAX_IMAGE_DIMENSION}x${MAX_IMAGE_DIMENSION}px.`;\n\t\t\t\t\tsetErrors(newErrors);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// All validations passed\n\t\t\t\tsetCoverImage(file);\n\t\t\t\tsetCoverImagePreview(e.target?.result as string);\n\t\t\t\tsetErrors((prev) => ({ ...prev, coverImage: \"\" }));\n\t\t\t};\n\t\t\timg.src = e.target?.result as string;\n\t\t};\n\t\treader.readAsDataURL(file);\n\t}, []);\n\n\t// Validate current step\n\tconst validateStep = useCallback((): boolean => {\n\t\tconst newErrors: Record<string, string> = {};\n\n\t\tswitch (currentStep) {\n\t\t\tcase \"basic\":\n\t\t\t\tif (!name || name.trim().length === 0) {\n\t\t\t\t\tnewErrors.name = \"Collection name is required\";\n\t\t\t\t}\n\t\t\t\tif (!description || description.trim().length === 0) {\n\t\t\t\t\tnewErrors.description = \"Collection description is required\";\n\t\t\t\t}\n\t\t\t\tif (!coverImage) {\n\t\t\t\t\tnewErrors.coverImage = \"Cover image is required\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"rarities\":\n\t\t\t\tif (rarities.length === 0) {\n\t\t\t\t\tnewErrors.rarities = \"At least one rarity tier is required\";\n\t\t\t\t} else {\n\t\t\t\t\t// Validate percentages add up to 100%\n\t\t\t\t\tconst total = rarities.reduce(\n\t\t\t\t\t\t(sum, r) => sum + Number.parseFloat(r.percentage || \"0\"),\n\t\t\t\t\t\t0,\n\t\t\t\t\t);\n\t\t\t\t\tif (Math.abs(total - 1) > 0.0001) {\n\t\t\t\t\t\tnewErrors.rarities = \"Rarity percentages must add up to 100%\";\n\t\t\t\t\t}\n\n\t\t\t\t\t// If quantity is specified, validate item counts match\n\t\t\t\t\tif (quantity) {\n\t\t\t\t\t\tconst totalItems = rarities.reduce(\n\t\t\t\t\t\t\t(sum, r) => sum + Number.parseInt(r.items || \"0\"),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (totalItems !== Number.parseInt(quantity)) {\n\t\t\t\t\t\t\tnewErrors.rarities = `Item counts must add up to the total quantity (${quantity})`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"royalties\":\n\t\t\t\tif (royalties.length > 0) {\n\t\t\t\t\t// Validate royalty percentages\n\t\t\t\t\tconst total = royalties.reduce(\n\t\t\t\t\t\t(sum, r) => sum + Number.parseFloat(r.percentage || \"0\"),\n\t\t\t\t\t\t0,\n\t\t\t\t\t);\n\t\t\t\t\tif (total > 0.07) {\n\t\t\t\t\t\t// Max 7% royalties (1sat standard)\n\t\t\t\t\t\tnewErrors.royalties = \"Total royalties cannot exceed 7%\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t\tsetErrors(newErrors);\n\t\treturn Object.keys(newErrors).length === 0;\n\t}, [\n\t\tcurrentStep,\n\t\tname,\n\t\tdescription,\n\t\tcoverImage,\n\t\trarities,\n\t\troyalties,\n\t\tquantity,\n\t]);\n\n\t// Handle step navigation\n\tconst handleNext = useCallback(() => {\n\t\tif (!validateStep()) return;\n\n\t\tconst nextIndex = currentStepIndex + 1;\n\t\tif (nextIndex < STEPS.length) {\n\t\t\tsetCurrentStep(STEPS[nextIndex] as Step);\n\t\t}\n\t}, [currentStepIndex, validateStep]);\n\n\tconst handlePrev = useCallback(() => {\n\t\tconst prevIndex = currentStepIndex - 1;\n\t\tif (prevIndex >= 0) {\n\t\t\tsetCurrentStep(STEPS[prevIndex] as Step);\n\t\t}\n\t}, [currentStepIndex]);\n\n\t// Handle collection creation\n\tconst handleCreate = useCallback(async () => {\n\t\tif (!coverImage) return;\n\n\t\t// Read image file\n\t\tconst imageData = await coverImage.arrayBuffer();\n\t\tconst imageB64 = Buffer.from(imageData).toString(\"base64\");\n\n\t\t// Create collection metadata\n\t\tconst metadata: PreMAP = {\n\t\t\tapp: \"1sat.market\",\n\t\t\ttype: \"ord\",\n\t\t\tsubType: \"collection\",\n\t\t\tname,\n\t\t};\n\n\t\t// Add optional fields\n\t\tif (royalties.length > 0) {\n\t\t\tmetadata.royalties = royalties;\n\t\t}\n\n\t\t// Convert CollectionRarity[] to RarityLabels format\n\t\tconst rarityLabels = rarities.map((rarity) => ({\n\t\t\tlabel: rarity.label,\n\t\t\tpercentage: rarity.percentage,\n\t\t\t...(rarity.items ? { items: rarity.items } : {}),\n\t\t}));\n\n\t\t// Build subTypeData dynamically based on what's provided\n\t\t// Build subTypeData according to js-1sat-ord types\n\t\tconst subTypeData: Partial<CollectionSubTypeData> = {\n\t\t\tdescription,\n\t\t\tquantity: quantity ? Number.parseInt(quantity) : 0, // 0 means unlimited in 1sat\n\t\t\trarityLabels: rarityLabels.length > 0 ? rarityLabels : [],\n\t\t\ttraits: Object.keys(traits).length > 0 ? traits : {},\n\t\t};\n\n\t\tmetadata.subTypeData = subTypeData as CollectionSubTypeData;\n\n\t\t// Create inscription with image data\n\t\t// IMPORTANT: Collections inscribe the image data as JSON, not the raw image\n\t\tawait inscribe({\n\t\t\tcontent: imageB64,\n\t\t\tcontentType: \"application/json\", // Collections use JSON content type\n\t\t\tmetadata,\n\t\t});\n\t}, [\n\t\tcoverImage,\n\t\tname,\n\t\tdescription,\n\t\tquantity,\n\t\trarities,\n\t\ttraits,\n\t\troyalties,\n\t\tinscribe,\n\t]);\n\n\t// Reset form\n\tconst resetForm = useCallback(() => {\n\t\tsetCurrentStep(\"basic\");\n\t\tsetName(\"\");\n\t\tsetDescription(\"\");\n\t\tsetQuantity(\"\");\n\t\tsetCoverImage(null);\n\t\tsetCoverImagePreview(null);\n\t\tsetRarities([{ label: \"Common\", percentage: \"1.0000\", items: \"\" }]);\n\t\tsetTraits({});\n\t\tsetRoyalties([]);\n\t\tsetErrors({});\n\t}, []);\n\n\t// Handle dialog close\n\tconst handleClose = useCallback(() => {\n\t\tsetOpen(false);\n\t\t// Reset after animation\n\t\tsetTimeout(resetForm, 200);\n\t}, [resetForm]);\n\n\t// Add rarity tier\n\tconst addRarity = useCallback(() => {\n\t\tsetRarities([...rarities, { label: \"\", percentage: \"0\", items: \"\" }]);\n\t}, [rarities]);\n\n\t// Remove rarity tier\n\tconst removeRarity = useCallback(\n\t\t(index: number) => {\n\t\t\tsetRarities(rarities.filter((_, i) => i !== index));\n\t\t},\n\t\t[rarities],\n\t);\n\n\t// Update rarity\n\tconst updateRarity = useCallback(\n\t\t(index: number, field: keyof CollectionRarity, value: string) => {\n\t\t\tconst newRarities = [...rarities];\n\t\t\t// Ensure we have a valid object with all required fields\n\t\t\tconst currentRarity = newRarities[index] || {\n\t\t\t\tlabel: \"\",\n\t\t\t\tpercentage: \"0\",\n\t\t\t\titems: \"\",\n\t\t\t};\n\t\t\tnewRarities[index] = { ...currentRarity, [field]: value };\n\t\t\tsetRarities(newRarities);\n\t\t},\n\t\t[rarities],\n\t);\n\n\t// Add trait\n\tconst addTrait = useCallback(() => {\n\t\tconst traitName = `Trait ${Object.keys(traits).length + 1}`;\n\t\tsetTraits({\n\t\t\t...traits,\n\t\t\t[traitName]: {\n\t\t\t\tvalues: [],\n\t\t\t\toccurancePercentages: [],\n\t\t\t},\n\t\t});\n\t}, [traits]);\n\n\t// Remove trait\n\tconst removeTrait = useCallback(\n\t\t(traitName: string) => {\n\t\t\tconst newTraits = { ...traits };\n\t\t\tdelete newTraits[traitName];\n\t\t\tsetTraits(newTraits);\n\t\t},\n\t\t[traits],\n\t);\n\n\t// Add royalty recipient\n\tconst addRoyalty = useCallback(() => {\n\t\tsetRoyalties([\n\t\t\t...royalties,\n\t\t\t{ type: RoytaltyType.Address, destination: \"\", percentage: \"0\" },\n\t\t]);\n\t}, [royalties]);\n\n\t// Remove royalty recipient\n\tconst removeRoyalty = useCallback(\n\t\t(index: number) => {\n\t\t\tsetRoyalties(royalties.filter((_, i) => i !== index));\n\t\t},\n\t\t[royalties],\n\t);\n\n\t// Update royalty\n\tconst updateRoyalty = useCallback(\n\t\t(\n\t\t\tindex: number,\n\t\t\tfield: keyof CollectionRoyalty,\n\t\t\tvalue: string | RoytaltyType,\n\t\t) => {\n\t\t\tconst newRoyalties = [...royalties];\n\t\t\t// Ensure we have a valid object with all required fields\n\t\t\tconst currentRoyalty = newRoyalties[index] || {\n\t\t\t\ttype: RoytaltyType.Address,\n\t\t\t\tdestination: \"\",\n\t\t\t\tpercentage: \"0\",\n\t\t\t};\n\t\t\tif (field === \"type\") {\n\t\t\t\tnewRoyalties[index] = {\n\t\t\t\t\t...currentRoyalty,\n\t\t\t\t\ttype: value as RoytaltyType,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tnewRoyalties[index] = { ...currentRoyalty, [field]: value };\n\t\t\t}\n\t\t\tsetRoyalties(newRoyalties);\n\t\t},\n\t\t[royalties],\n\t);\n\n\t// Render step content\n\tconst renderStepContent = () => {\n\t\tswitch (currentStep) {\n\t\t\tcase \"basic\":\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t{/* Collection name */}\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Collection Name</p>\n\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\tplaceholder=\"e.g., Pixel Punks\"\n\t\t\t\t\t\t\t\tvalue={name}\n\t\t\t\t\t\t\t\tonChange={(e) => setName(e.target.value)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{errors.name && (\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{errors.name}</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* Description */}\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Description</p>\n\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\tplaceholder=\"Describe your collection...\"\n\t\t\t\t\t\t\t\tvalue={description}\n\t\t\t\t\t\t\t\tonChange={(e) => setDescription(e.target.value)}\n\t\t\t\t\t\t\t\tstyle={{ minHeight: \"100px\" }}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{errors.description && (\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{errors.description}</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* Quantity (optional) */}\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\tTotal Supply (Optional)\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\tplaceholder=\"e.g., 10000\"\n\t\t\t\t\t\t\t\tvalue={quantity}\n\t\t\t\t\t\t\t\tonChange={(e) => setQuantity(e.target.value)}\n\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\tmin=\"1\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\tLeave empty for unlimited supply\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* Cover image */}\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">Cover Image</span>\n\t\t\t\t\t\t\t{coverImagePreview ? (\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex flex-col items-center gap-2 p-3\"\n\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\tborder: \"1px solid var(--border)\",\n\t\t\t\t\t\t\t\t\t\t\tborderRadius: \"calc(var(--radius) * 0.75)\",\n\t\t\t\t\t\t\t\t\t\t\tbackgroundColor: \"var(--card)\",\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\t\t\t\tsrc={coverImagePreview}\n\t\t\t\t\t\t\t\t\t\t\talt=\"Collection cover preview\"\n\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\twidth: \"200px\",\n\t\t\t\t\t\t\t\t\t\t\t\theight: \"200px\",\n\t\t\t\t\t\t\t\t\t\t\t\tobjectFit: \"contain\",\n\t\t\t\t\t\t\t\t\t\t\t\tborderRadius: \"calc(var(--radius) * 0.5)\",\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t{coverImage?.name}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\tsetCoverImage(null);\n\t\t\t\t\t\t\t\t\t\t\t\tsetCoverImagePreview(null);\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<DropZone\n\t\t\t\t\t\t\t\t\tonFileSelect={handleImageSelect}\n\t\t\t\t\t\t\t\t\taccept={SUPPORTED_IMAGE_TYPES.join(\",\")}\n\t\t\t\t\t\t\t\t\tmaxSize={MAX_IMAGE_SIZE}\n\t\t\t\t\t\t\t\t\tmultiple={false}\n\t\t\t\t\t\t\t\t\ticon={<ImageIcon width=\"32\" height=\"32\" />}\n\t\t\t\t\t\t\t\t\ttitle=\"Drop image here or click to browse\"\n\t\t\t\t\t\t\t\t\tdescription={`Square image, max ${MAX_IMAGE_DIMENSION}x${MAX_IMAGE_DIMENSION}px, under 1MB`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{errors.coverImage && (\n\t\t\t\t\t\t\t\t<span className=\"text-xs text-destructive\">\n\t\t\t\t\t\t\t\t\t{errors.coverImage}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\n\t\t\tcase \"rarities\":\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tDefine rarity tiers for your collection. Percentages must add up\n\t\t\t\t\t\t\tto 100%.\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t{rarities.map((rarity, index) => (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={`${rarity.label}-${rarity.percentage}-${index}`}\n\t\t\t\t\t\t\t\t\tclassName=\"flex gap-2 items-end\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\" style={{ flex: 1 }}>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Label</span>\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"e.g., Common\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={rarity.label}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\tupdateRarity(index, \"label\", e.target.value)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\" style={{ flex: 1 }}>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\tPercentage\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"50\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={(\n\t\t\t\t\t\t\t\t\t\t\t\tNumber.parseFloat(rarity.percentage || \"0\") * 100\n\t\t\t\t\t\t\t\t\t\t\t).toFixed(2)}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst value =\n\t\t\t\t\t\t\t\t\t\t\t\t\tNumber.parseFloat(e.target.value || \"0\") / 100;\n\t\t\t\t\t\t\t\t\t\t\t\tupdateRarity(index, \"percentage\", value.toFixed(4));\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\t\t\t\tmax=\"100\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t{quantity && (\n\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex flex-col gap-1\"\n\t\t\t\t\t\t\t\t\t\t\tstyle={{ width: \"100px\" }}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\tItems\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"0\"\n\t\t\t\t\t\t\t\t\t\t\t\tvalue={rarity.items || \"\"}\n\t\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tupdateRarity(index, \"items\", e.target.value)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\tclassName=\"text-destructive\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => removeRarity(index)}\n\t\t\t\t\t\t\t\t\t\tdisabled={rarities.length === 1}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<Button size=\"default\" variant=\"secondary\" onClick={addRarity}>\n\t\t\t\t\t\t\t<PlusIcon />\n\t\t\t\t\t\t\tAdd Rarity Tier\n\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t{errors.rarities && (\n\t\t\t\t\t\t\t<span className=\"text-xs text-destructive\">\n\t\t\t\t\t\t\t\t{errors.rarities}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Show total percentage */}\n\t\t\t\t\t\t<div className=\"flex items-center gap-2 justify-between\">\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">Total:</span>\n\t\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\t\tvariant={\n\t\t\t\t\t\t\t\t\t\tMath.abs(\n\t\t\t\t\t\t\t\t\t\t\trarities.reduce(\n\t\t\t\t\t\t\t\t\t\t\t\t(sum, r) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tsum + Number.parseFloat(r.percentage || \"0\"),\n\t\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t\t) - 1,\n\t\t\t\t\t\t\t\t\t\t) < 0.0001\n\t\t\t\t\t\t\t\t\t\t\t? \"default\"\n\t\t\t\t\t\t\t\t\t\t\t: \"destructive\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\trarities.reduce(\n\t\t\t\t\t\t\t\t\t\t\t(sum, r) => sum + Number.parseFloat(r.percentage || \"0\"),\n\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t) * 100\n\t\t\t\t\t\t\t\t\t).toFixed(2)}\n\t\t\t\t\t\t\t\t\t%\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t{quantity && (\n\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">Items:</span>\n\t\t\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\t\t\tvariant={\n\t\t\t\t\t\t\t\t\t\t\trarities.reduce(\n\t\t\t\t\t\t\t\t\t\t\t\t(sum, r) => sum + Number.parseInt(r.items || \"0\"),\n\t\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t\t) === Number.parseInt(quantity)\n\t\t\t\t\t\t\t\t\t\t\t\t? \"default\"\n\t\t\t\t\t\t\t\t\t\t\t\t: \"destructive\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{rarities.reduce(\n\t\t\t\t\t\t\t\t\t\t\t(sum, r) => sum + Number.parseInt(r.items || \"0\"),\n\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t)}{\" \"}\n\t\t\t\t\t\t\t\t\t\t/ {quantity}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\n\t\t\tcase \"traits\":\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tDefine trait categories and their possible values for your\n\t\t\t\t\t\t\tcollection items.\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t{Object.entries(traits).map(([traitName, trait]) => (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={traitName}\n\t\t\t\t\t\t\t\t\tclassName=\"flex flex-col gap-2 p-3\"\n\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\tborder: \"1px solid var(--border)\",\n\t\t\t\t\t\t\t\t\t\tborderRadius: \"calc(var(--radius) * 0.75)\",\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"Trait name\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={traitName}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst newTraits = { ...traits };\n\t\t\t\t\t\t\t\t\t\t\t\tdelete newTraits[traitName];\n\t\t\t\t\t\t\t\t\t\t\t\tnewTraits[e.target.value] = trait;\n\t\t\t\t\t\t\t\t\t\t\t\tsetTraits(newTraits);\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-destructive\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => removeTrait(traitName)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"Enter values separated by commas (e.g., Red, Blue, Green)\"\n\t\t\t\t\t\t\t\t\t\tvalue={trait.values.join(\", \")}\n\t\t\t\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\t\t\t\tconst values = e.target.value\n\t\t\t\t\t\t\t\t\t\t\t\t.split(\",\")\n\t\t\t\t\t\t\t\t\t\t\t\t.map((v: string) => v.trim())\n\t\t\t\t\t\t\t\t\t\t\t\t.filter((v: string) => v);\n\t\t\t\t\t\t\t\t\t\t\tconst newTrait = { ...trait, values };\n\t\t\t\t\t\t\t\t\t\t\t// Auto-calculate equal percentages\n\t\t\t\t\t\t\t\t\t\t\tif (values.length > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst percentage = (1 / values.length).toFixed(4);\n\t\t\t\t\t\t\t\t\t\t\t\tnewTrait.occurancePercentages = values.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t() => percentage,\n\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tsetTraits({ ...traits, [traitName]: newTrait });\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\tstyle={{ minHeight: \"60px\" }}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t{trait.values.length} values defined\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<Button size=\"default\" variant=\"secondary\" onClick={addTrait}>\n\t\t\t\t\t\t\t<PlusIcon />\n\t\t\t\t\t\t\tAdd Trait\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\n\t\t\tcase \"royalties\":\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t<span className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tSet up royalty recipients for secondary sales (optional). Maximum\n\t\t\t\t\t\t\ttotal: 10%.\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t{royalties.map((royalty, index) => (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={`${royalty.destination}-${index}`}\n\t\t\t\t\t\t\t\t\tclassName=\"flex gap-2 items-end\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex flex-col gap-1\"\n\t\t\t\t\t\t\t\t\t\tstyle={{ width: \"120px\" }}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">Type</span>\n\t\t\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\t\t\tvalue={royalty.type}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\tupdateRoyalty(\n\t\t\t\t\t\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\te.target.value as RoytaltyType,\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\tpadding: \"0.5rem\",\n\t\t\t\t\t\t\t\t\t\t\t\tborderRadius: \"calc(var(--radius) * 0.5)\",\n\t\t\t\t\t\t\t\t\t\t\t\tborder: \"1px solid var(--border)\",\n\t\t\t\t\t\t\t\t\t\t\t\tbackgroundColor: \"var(--background)\",\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<option value={RoytaltyType.Address}>Address</option>\n\t\t\t\t\t\t\t\t\t\t\t<option value={RoytaltyType.Paymail}>Paymail</option>\n\t\t\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\" style={{ flex: 1 }}>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\tDestination\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tplaceholder={\n\t\t\t\t\t\t\t\t\t\t\t\troyalty.type === RoytaltyType.Paymail\n\t\t\t\t\t\t\t\t\t\t\t\t\t? \"user@example.com\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t: \"1Bitcoin...\"\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tvalue={royalty.destination}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\t\t\t\t\tupdateRoyalty(index, \"destination\", e.target.value)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex flex-col gap-1\"\n\t\t\t\t\t\t\t\t\t\tstyle={{ width: \"100px\" }}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\tPercentage\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"1\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={(\n\t\t\t\t\t\t\t\t\t\t\t\tNumber.parseFloat(royalty.percentage || \"0\") * 100\n\t\t\t\t\t\t\t\t\t\t\t).toFixed(2)}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst value =\n\t\t\t\t\t\t\t\t\t\t\t\t\tNumber.parseFloat(e.target.value || \"0\") / 100;\n\t\t\t\t\t\t\t\t\t\t\t\tupdateRoyalty(index, \"percentage\", value.toFixed(4));\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\t\t\t\tmax=\"7\"\n\t\t\t\t\t\t\t\t\t\t\tstep=\"0.1\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\tclassName=\"text-destructive\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => removeRoyalty(index)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{royalties.length === 0 && (\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground\"\n\t\t\t\t\t\t\t\tstyle={{ textAlign: \"center\", padding: \"1rem\" }}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tNo royalties configured\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<Button size=\"default\" variant=\"secondary\" onClick={addRoyalty}>\n\t\t\t\t\t\t\t<PlusIcon />\n\t\t\t\t\t\t\tAdd Royalty Recipient\n\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t{errors.royalties && (\n\t\t\t\t\t\t\t<span className=\"text-xs text-destructive\">\n\t\t\t\t\t\t\t\t{errors.royalties}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Show total royalties */}\n\t\t\t\t\t\t{royalties.length > 0 && (\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">Total Royalties:</span>\n\t\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\t\tvariant={\n\t\t\t\t\t\t\t\t\t\troyalties.reduce(\n\t\t\t\t\t\t\t\t\t\t\t(sum, r) => sum + Number.parseFloat(r.percentage || \"0\"),\n\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t) <= 0.07\n\t\t\t\t\t\t\t\t\t\t\t? \"default\"\n\t\t\t\t\t\t\t\t\t\t\t: \"destructive\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\troyalties.reduce(\n\t\t\t\t\t\t\t\t\t\t\t(sum, r) => sum + Number.parseFloat(r.percentage || \"0\"),\n\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t) * 100\n\t\t\t\t\t\t\t\t\t).toFixed(2)}\n\t\t\t\t\t\t\t\t\t%\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t);\n\n\t\t\tcase \"review\":\n\t\t\t\treturn (\n\t\t\t\t\t<ScrollArea style={{ height: \"400px\" }}>\n\t\t\t\t\t\t<div className=\"flex flex-col gap-4 pr-4\">\n\t\t\t\t\t\t\t<span className=\"text-base font-bold\">\n\t\t\t\t\t\t\t\tReview Your Collection\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t{/* Basic info */}\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tBasic Information\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t<strong>Name:</strong> {name}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t<strong>Description:</strong> {description}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{quantity && (\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t<strong>Total Supply:</strong> {quantity}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<Separator />\n\n\t\t\t\t\t\t\t{/* Cover image */}\n\t\t\t\t\t\t\t{coverImagePreview && (\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tCover Image\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\t\t\tsrc={coverImagePreview}\n\t\t\t\t\t\t\t\t\t\talt=\"Collection cover\"\n\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\twidth: \"150px\",\n\t\t\t\t\t\t\t\t\t\t\theight: \"150px\",\n\t\t\t\t\t\t\t\t\t\t\tobjectFit: \"contain\",\n\t\t\t\t\t\t\t\t\t\t\tborderRadius: \"calc(var(--radius) * 0.5)\",\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<Separator />\n\n\t\t\t\t\t\t\t{/* Rarities */}\n\t\t\t\t\t\t\t{rarities.length > 0 && (\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tRarity Tiers\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t{rarities.map((rarity) => (\n\t\t\t\t\t\t\t\t\t\t\t<span key={`${rarity.label}-${rarity.percentage}`}>\n\t\t\t\t\t\t\t\t\t\t\t\t{rarity.label}:{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t{(Number.parseFloat(rarity.percentage) * 100).toFixed(\n\t\t\t\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t%\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<Separator />\n\n\t\t\t\t\t\t\t{/* Traits */}\n\t\t\t\t\t\t\t{Object.keys(traits).length > 0 && (\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tTraits\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t{Object.entries(traits).map(([name, trait]) => (\n\t\t\t\t\t\t\t\t\t\t\t<span key={name} className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t<strong>{name}:</strong> {trait.values.join(\", \")}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<Separator />\n\n\t\t\t\t\t\t\t{/* Royalties */}\n\t\t\t\t\t\t\t{royalties.length > 0 && (\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tRoyalties\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t{royalties.map((royalty) => (\n\t\t\t\t\t\t\t\t\t\t\t<span key={royalty.destination} className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t{royalty.destination}:{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t{(Number.parseFloat(royalty.percentage) * 100).toFixed(\n\t\t\t\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t%\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t{/* Info box */}\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"flex items-start gap-2 p-3\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tbackgroundColor: \"var(--blue-2)\",\n\t\t\t\t\t\t\t\t\tborderRadius: \"calc(var(--radius) * 0.75)\",\n\t\t\t\t\t\t\t\t\tborder: \"1px solid var(--blue-6)\",\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<InfoCircledIcon style={{ marginTop: 2, flexShrink: 0 }} />\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t\t\tCollection Creation\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tThis will create a parent inscription that defines your NFT\n\t\t\t\t\t\t\t\t\t\tcollection. Individual collection items can be minted later,\n\t\t\t\t\t\t\t\t\t\treferencing this collection.\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</ScrollArea>\n\t\t\t\t);\n\t\t}\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<Button\n\t\t\t\tvariant={\n\t\t\t\t\tvariant === \"solid\"\n\t\t\t\t\t\t? \"default\"\n\t\t\t\t\t\t: variant === \"soft\"\n\t\t\t\t\t\t\t? \"secondary\"\n\t\t\t\t\t\t\t: variant\n\t\t\t\t}\n\t\t\t\tsize={size === \"1\" ? \"sm\" : size === \"2\" ? \"default\" : \"lg\"}\n\t\t\t\tclassName={className}\n\t\t\t\tdisabled={disabled}\n\t\t\t\tonClick={() => setOpen(true)}\n\t\t\t>\n\t\t\t\t<PlusIcon />\n\t\t\t\t{buttonText}\n\t\t\t</Button>\n\n\t\t\t<Dialog open={open} onOpenChange={setOpen}>\n\t\t\t\t<DialogContent className=\"max-w-[600px]\">\n\t\t\t\t\t<DialogHeader>\n\t\t\t\t\t\t<DialogTitle>{dialogTitle}</DialogTitle>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tStep {currentStepIndex + 1} of {STEPS.length}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</DialogHeader>\n\n\t\t\t\t\t{/* Step indicator */}\n\t\t\t\t\t<StepIndicator\n\t\t\t\t\t\tsteps={STEPS.map((step, index) => ({\n\t\t\t\t\t\t\tid: step,\n\t\t\t\t\t\t\tlabel: step.charAt(0).toUpperCase() + step.slice(1),\n\t\t\t\t\t\t\tstatus:\n\t\t\t\t\t\t\t\tindex < currentStepIndex\n\t\t\t\t\t\t\t\t\t? \"complete\"\n\t\t\t\t\t\t\t\t\t: index === currentStepIndex\n\t\t\t\t\t\t\t\t\t\t? \"active\"\n\t\t\t\t\t\t\t\t\t\t: \"pending\",\n\t\t\t\t\t\t}))}\n\t\t\t\t\t/>\n\n\t\t\t\t\t{/* Step content */}\n\t\t\t\t\t<Tabs value={currentStep}>\n\t\t\t\t\t\t<TabsContent value={currentStep}>{renderStepContent()}</TabsContent>\n\t\t\t\t\t</Tabs>\n\n\t\t\t\t\t{/* Error display */}\n\t\t\t\t\t{inscriptionError && (\n\t\t\t\t\t\t<ErrorDisplay error={inscriptionError.message} />\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Actions */}\n\t\t\t\t\t<div className=\"flex gap-3 mt-4 justify-between\">\n\t\t\t\t\t\t<div className=\"flex gap-3\">\n\t\t\t\t\t\t\t{currentStepIndex > 0 && (\n\t\t\t\t\t\t\t\t<Button variant=\"secondary\" onClick={handlePrev}>\n\t\t\t\t\t\t\t\t\tPrevious\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex gap-3\">\n\t\t\t\t\t\t\t<Button variant=\"outline\" onClick={handleClose}>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t{currentStep === \"review\" ? (\n\t\t\t\t\t\t\t\t<LoadingButton\n\t\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\t\tloading={loading}\n\t\t\t\t\t\t\t\t\tonClick={handleCreate}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tCreate Collection\n\t\t\t\t\t\t\t\t</LoadingButton>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<Button variant=\"default\" onClick={handleNext}>\n\t\t\t\t\t\t\t\t\tNext\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</DialogContent>\n\t\t\t</Dialog>\n\t\t</>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/collectionmintbutton.tsx"
}
]
}