bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
18 lines • 10.6 kB
JSON
{
"name": "market-components-createlistingbutton",
"type": "registry:component",
"dependencies": [
"@radix-ui/react-icons",
"js-1sat-ord"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/market/components/CreateListingButton.tsx",
"type": "registry:component",
"content": "import { Cross2Icon, PlusIcon } from \"@radix-ui/react-icons\";\nimport type { Utxo } from \"js-1sat-ord\";\n// CreateListingButton Component - Create marketplace listings\nimport { useState } from \"react\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { Badge } from \"../../ui/badge.js\";\nimport { Button } from \"../../ui/button.js\";\nimport {\n\tDialog,\n\tDialogClose,\n\tDialogContent,\n\tDialogTitle,\n\tDialogTrigger,\n} from \"../../ui/dialog.js\";\nimport { Input } from \"../../ui/input.js\";\nimport { Label } from \"../../ui/label.js\";\n// import { formatBSV } from '../../wallet/utils/currency.js';\nimport {\n\ttype WalletUser,\n\tgetWalletProps,\n} from \"../../wallet/types/extended-user.js\";\nimport { useCreateListing } from \"../hooks/useCreateListing.js\";\nimport type { NewListing, NewTokenListing } from \"../types/listings.js\";\nimport { AssetType } from \"../types/market.js\";\n\nexport interface CreateListingButtonProps {\n\t// Asset to list\n\tasset?: {\n\t\ttype: AssetType;\n\t\tid?: string; // Token ID for BSV21 or collection ID\n\t\tsymbol?: string; // Token symbol\n\t\tamount?: string; // For token listings\n\t\tdecimals?: number;\n\t\tutxo: Utxo; // The UTXO containing the asset\n\t};\n\n\t// Configuration\n\tdisabled?: boolean;\n\tloading?: boolean;\n\tclassName?: string;\n\tvariant?: \"default\" | \"secondary\" | \"outline\" | \"ghost\";\n\tsize?: \"sm\" | \"default\" | \"lg\";\n\n\t// Callbacks\n\tonSuccess?: (txid: string) => void;\n\tonError?: (error: Error) => void;\n\n\t// UI customization\n\tbuttonText?: string;\n\tdialogTitle?: string;\n}\n\nexport function CreateListingButton({\n\tasset,\n\tdisabled = false,\n\tloading = false,\n\tclassName,\n\tvariant = \"default\",\n\tsize = \"default\",\n\tonSuccess,\n\tonError,\n\tbuttonText = \"List for Sale\",\n\tdialogTitle = \"Create Listing\",\n}: CreateListingButtonProps) {\n\tconst [open, setOpen] = useState(false);\n\tconst [price, setPrice] = useState(\"\");\n\tconst [tokenAmount, setTokenAmount] = useState(\"\");\n\n\tconst { user, isAuthenticated } = useBitcoinAuth();\n\tconst {\n\t\tcreateListing,\n\t\tcreateTokenListing,\n\t\tisLoading: isCreating,\n\t} = useCreateListing({\n\t\tonSuccess: (result) => {\n\t\t\tsetOpen(false);\n\t\t\tsetPrice(\"\");\n\t\t\tsetTokenAmount(\"\");\n\t\t\tonSuccess?.(result.txid || \"\");\n\t\t},\n\t\tonError: (error) => {\n\t\t\tonError?.(error);\n\t\t},\n\t});\n\n\tconst handleSubmit = async (e: React.FormEvent) => {\n\t\te.preventDefault();\n\n\t\tif (!isAuthenticated || !user || !asset) {\n\t\t\tonError?.(new Error(\"Authentication required\"));\n\t\t\treturn;\n\t\t}\n\n\t\tconst priceValue = Number.parseFloat(price);\n\t\tif (Number.isNaN(priceValue) || priceValue <= 0) {\n\t\t\tonError?.(new Error(\"Please enter a valid price\"));\n\t\t\treturn;\n\t\t}\n\n\t\tconst priceSats = Math.floor(priceValue * 100000000); // Convert BSV to satoshis\n\n\t\ttry {\n\t\t\tconst walletProps = getWalletProps(user as WalletUser);\n\n\t\t\tif (asset.type === AssetType.Ordinals) {\n\t\t\t\t// Create ordinal/NFT listing\n\t\t\t\tconst listing: NewListing = {\n\t\t\t\t\tpayAddress: walletProps.fundingAddress,\n\t\t\t\t\tprice: priceSats,\n\t\t\t\t\tordAddress: walletProps.ordinalAddress,\n\t\t\t\t\tlistingUtxo: asset.utxo,\n\t\t\t\t};\n\n\t\t\t\tawait createListing([listing]);\n\t\t\t} else {\n\t\t\t\t// Create token listing (BSV20/BSV21)\n\t\t\t\tif (!tokenAmount || !asset.decimals) {\n\t\t\t\t\tonError?.(new Error(\"Token amount is required\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst tokens = Number.parseFloat(tokenAmount);\n\t\t\t\tif (Number.isNaN(tokens) || tokens <= 0) {\n\t\t\t\t\tonError?.(new Error(\"Please enter a valid token amount\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst listing: NewTokenListing = {\n\t\t\t\t\tpayAddress: walletProps.fundingAddress,\n\t\t\t\t\tprice: priceSats,\n\t\t\t\t\ttokens: tokens,\n\t\t\t\t\tordAddress: walletProps.ordinalAddress,\n\t\t\t\t};\n\n\t\t\t\tawait createTokenListing([listing], {\n\t\t\t\t\tprotocol: asset.type === AssetType.BSV20 ? \"bsv20\" : \"bsv21\",\n\t\t\t\t\ttokenID: asset.id || \"\",\n\t\t\t\t\tdecimals: asset.decimals,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError?.(error as Error);\n\t\t}\n\t};\n\n\tif (!isAuthenticated) {\n\t\treturn (\n\t\t\t<Button disabled variant=\"ghost\" size={size} className={className}>\n\t\t\t\tSign in to List\n\t\t\t</Button>\n\t\t);\n\t}\n\n\tconst isTokenListing =\n\t\tasset?.type === AssetType.BSV20 || asset?.type === AssetType.BSV21;\n\n\treturn (\n\t\t<Dialog open={open} onOpenChange={setOpen}>\n\t\t\t<DialogTrigger asChild>\n\t\t\t\t<Button\n\t\t\t\t\tvariant={variant}\n\t\t\t\t\tsize={size}\n\t\t\t\t\tdisabled={disabled || loading || !asset}\n\t\t\t\t\tclassName={className}\n\t\t\t\t>\n\t\t\t\t\t<PlusIcon className=\"w-4 h-4 mr-2\" />\n\t\t\t\t\t{buttonText}\n\t\t\t\t</Button>\n\t\t\t</DialogTrigger>\n\n\t\t\t<DialogContent className=\"max-w-md\">\n\t\t\t\t<DialogTitle>{dialogTitle}</DialogTitle>\n\n\t\t\t\t<form onSubmit={handleSubmit}>\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t{/* Asset Info */}\n\t\t\t\t\t\t{asset && (\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Asset Details</p>\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<Badge\n\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\tasset.type === AssetType.Ordinals\n\t\t\t\t\t\t\t\t\t\t\t\t? \"bg-orange-100 text-orange-700\"\n\t\t\t\t\t\t\t\t\t\t\t\t: asset.type === AssetType.BSV20\n\t\t\t\t\t\t\t\t\t\t\t\t\t? \"bg-blue-100 text-blue-700\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t: \"bg-green-100 text-green-700\",\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{asset.type.toUpperCase()}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t{asset.symbol && <p className=\"text-sm\">{asset.symbol}</p>}\n\t\t\t\t\t\t\t\t\t{asset.id && (\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground font-mono\">\n\t\t\t\t\t\t\t\t\t\t\t{asset.id.slice(0, 8)}...{asset.id.slice(-8)}\n\t\t\t\t\t\t\t\t\t\t</p>\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\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Token Amount (for token listings) */}\n\t\t\t\t\t\t{isTokenListing && (\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor=\"token-amount\">Token Amount to List</Label>\n\t\t\t\t\t\t\t\t<div className=\"relative\">\n\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\tid=\"token-amount\"\n\t\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\t\tstep=\"any\"\n\t\t\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\t\t\tplaceholder={`0${asset?.decimals ? `.${Array(asset.decimals).fill(\"0\").join(\"\")}` : \"\"}`}\n\t\t\t\t\t\t\t\t\t\tvalue={tokenAmount}\n\t\t\t\t\t\t\t\t\t\tonChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n\t\t\t\t\t\t\t\t\t\t\tsetTokenAmount(e.target.value)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t\t\t\t\tclassName=\"pr-16\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<span className=\"absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t{asset?.symbol}\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\t{asset?.amount && (\n\t\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tAvailable: {asset.amount} {asset.symbol}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Price */}\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<Label htmlFor=\"price\">Price</Label>\n\t\t\t\t\t\t\t<div className=\"relative\">\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid=\"price\"\n\t\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\t\tstep=\"0.00000001\"\n\t\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"0.00000000\"\n\t\t\t\t\t\t\t\t\tvalue={price}\n\t\t\t\t\t\t\t\t\tonChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n\t\t\t\t\t\t\t\t\t\tsetPrice(e.target.value)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t\t\t\tclassName=\"pr-12\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<span className=\"absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tBSV\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t{price && (\n\t\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t={\" \"}\n\t\t\t\t\t\t\t\t\t{Math.floor(\n\t\t\t\t\t\t\t\t\t\tNumber.parseFloat(price) * 100000000,\n\t\t\t\t\t\t\t\t\t).toLocaleString()}{\" \"}\n\t\t\t\t\t\t\t\t\tsatoshis\n\t\t\t\t\t\t\t\t</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{/* Actions */}\n\t\t\t\t\t\t<div className=\"flex gap-3 mt-4 justify-end\">\n\t\t\t\t\t\t\t<DialogClose asChild>\n\t\t\t\t\t\t\t\t<Button variant=\"secondary\">Cancel</Button>\n\t\t\t\t\t\t\t</DialogClose>\n\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\tdisabled={\n\t\t\t\t\t\t\t\t\tisCreating || !price || (isTokenListing && !tokenAmount)\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{isCreating ? \"Creating...\" : \"Create Listing\"}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</form>\n\n\t\t\t\t<DialogClose className=\"absolute top-2 right-2\">\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" className=\"h-8 w-8 p-0\">\n\t\t\t\t\t\t<Cross2Icon className=\"h-4 w-4\" />\n\t\t\t\t\t</Button>\n\t\t\t\t</DialogClose>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n\n// Simplified version for quick listing\nexport function QuickListButton({\n\tasset,\n\tonSuccess,\n\tonError,\n\t...props\n}: Omit<CreateListingButtonProps, \"buttonText\" | \"dialogTitle\">) {\n\treturn (\n\t\t<CreateListingButton\n\t\t\tasset={asset}\n\t\t\tonSuccess={onSuccess}\n\t\t\tonError={onError}\n\t\t\tbuttonText=\"List\"\n\t\t\tdialogTitle={`List ${asset?.symbol || \"Asset\"}`}\n\t\t\tsize=\"sm\"\n\t\t\tvariant=\"secondary\"\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/createlistingbutton.tsx"
}
]
}