bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
17 lines • 9.52 kB
JSON
{
"name": "market-components-cancellistingbutton",
"type": "registry:component",
"dependencies": [
"@radix-ui/react-icons"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/market/components/CancelListingButton.tsx",
"type": "registry:component",
"content": "import { Cross2Icon, InfoCircledIcon } from \"@radix-ui/react-icons\";\n// CancelListingButton Component - Cancel 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 { Separator } from \"../../ui/separator.js\";\nimport {\n\ttype WalletUser,\n\tgetWalletProps,\n} from \"../../wallet/types/extended-user.js\";\nimport { formatBSV, formatTokenAmount } from \"../../wallet/utils/currency.js\";\nimport { useCancelListing } from \"../hooks/useCancelListing.js\";\nimport type { MarketListing } from \"../types/listings.js\";\nimport { AssetType } from \"../types/market.js\";\n\nexport interface CancelListingButtonProps {\n\tlisting: MarketListing;\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\tshowDetails?: boolean;\n}\n\nexport function CancelListingButton({\n\tlisting,\n\tdisabled = false,\n\tloading = false,\n\tclassName,\n\tvariant = \"outline\",\n\tsize = \"default\",\n\tonSuccess,\n\tonError,\n\tbuttonText = \"Cancel Listing\",\n\tdialogTitle = \"Cancel Listing\",\n\tshowDetails = true,\n}: CancelListingButtonProps) {\n\tconst [open, setOpen] = useState(false);\n\n\tconst { user, isAuthenticated } = useBitcoinAuth();\n\tconst { cancelListing, isLoading: isCancelling } = useCancelListing({\n\t\tonSuccess: (result) => {\n\t\t\tsetOpen(false);\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 handleConfirmCancel = async () => {\n\t\tif (!isAuthenticated || !user) {\n\t\t\tonError?.(new Error(\"Authentication required\"));\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tcancelListing(listing);\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 Cancel\n\t\t\t</Button>\n\t\t);\n\t}\n\n\t// Check if user has sufficient balance for fees\n\tconst walletProps = getWalletProps(user as WalletUser);\n\tconst availableBalance = walletProps.balance.confirmed || 0;\n\tconst estimatedFee = 300; // Lower fee estimate for cancellation\n\tconst canAffordFee = estimatedFee <= availableBalance;\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 || !canAffordFee}\n\t\t\t\t\tclassName={cn(\"text-destructive\", className)}\n\t\t\t\t>\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<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t{/* Listing Details */}\n\t\t\t\t\t{showDetails && (\n\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\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\">Listing to Cancel</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\tlisting.assetType === 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: listing.assetType === 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{listing.assetType.toUpperCase()}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t{listing.symbol && (\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm\">{listing.symbol}</p>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t{listing.tokenId && (\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{listing.tokenId.slice(0, 8)}...\n\t\t\t\t\t\t\t\t\t\t\t{listing.tokenId.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\n\t\t\t\t\t\t\t\t{listing.tokenAmount && (\n\t\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tAmount:{\" \"}\n\t\t\t\t\t\t\t\t\t\t{formatTokenAmount(\n\t\t\t\t\t\t\t\t\t\t\tlisting.tokenAmount,\n\t\t\t\t\t\t\t\t\t\t\tlisting.decimals || 0,\n\t\t\t\t\t\t\t\t\t\t)}{\" \"}\n\t\t\t\t\t\t\t\t\t\t{listing.symbol}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tListed Price: {formatBSV(listing.priceSats)} BSV\n\t\t\t\t\t\t\t\t</p>\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{/* Transaction 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<p className=\"text-sm font-medium\">Transaction Details</p>\n\t\t\t\t\t\t\t\t<p className=\"text-xs font-mono text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{listing.txid?.slice(0, 16) || \"Unknown\"}...\n\t\t\t\t\t\t\t\t\t{listing.txid?.slice(-16) || \"\"}:{listing.vout}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<Separator />\n\n\t\t\t\t\t{/* Fee Breakdown */}\n\t\t\t\t\t<div className=\"flex flex-col gap-2 p-3 bg-muted rounded-md\">\n\t\t\t\t\t\t<p className=\"text-sm font-medium\">Cancellation Summary</p>\n\n\t\t\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\tNetwork Fee (est.)\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<p className=\"text-xs\">{formatBSV(estimatedFee)} BSV</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<Separator className=\"my-1\" />\n\n\t\t\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Total Cost</p>\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t\t{formatBSV(estimatedFee)} BSV\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{!canAffordFee && (\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2 mt-2\">\n\t\t\t\t\t\t\t\t<InfoCircledIcon className=\"text-destructive\" />\n\t\t\t\t\t\t\t\t<p className=\"text-xs text-destructive\">\n\t\t\t\t\t\t\t\t\tInsufficient balance for transaction fee\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Balance Info */}\n\t\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">Your Balance</p>\n\t\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t{formatBSV(availableBalance)} BSV\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Warning */}\n\t\t\t\t\t<div className=\"flex flex-col gap-2 p-3 bg-red-50 rounded-md border border-red-200\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<InfoCircledIcon className=\"text-destructive\" />\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium text-destructive\">\n\t\t\t\t\t\t\t\tCancellation Confirmation\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p className=\"text-xs text-destructive\">\n\t\t\t\t\t\t\tThis will permanently remove your listing from the marketplace.\n\t\t\t\t\t\t\tThe item will be returned to your wallet.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Actions */}\n\t\t\t\t\t<div className=\"flex gap-3 mt-4 justify-end\">\n\t\t\t\t\t\t<DialogClose asChild>\n\t\t\t\t\t\t\t<Button variant=\"secondary\">Keep Listing</Button>\n\t\t\t\t\t\t</DialogClose>\n\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={handleConfirmCancel}\n\t\t\t\t\t\t\tdisabled={isCancelling || !canAffordFee}\n\t\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{isCancelling ? \"Cancelling...\" : \"Cancel Listing\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\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// Quick cancel button with minimal UI\nexport function QuickCancelButton({\n\tlisting,\n\tonSuccess,\n\tonError,\n\t...props\n}: Omit<\n\tCancelListingButtonProps,\n\t\"buttonText\" | \"dialogTitle\" | \"showDetails\"\n>) {\n\treturn (\n\t\t<CancelListingButton\n\t\t\tlisting={listing}\n\t\t\tonSuccess={onSuccess}\n\t\t\tonError={onError}\n\t\t\tbuttonText=\"Cancel\"\n\t\t\tdialogTitle={`Cancel ${listing.symbol || \"Listing\"}`}\n\t\t\tshowDetails={false}\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 %>/cancellistingbutton.tsx"
}
]
}