bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
15 lines • 10.6 kB
JSON
{
"name": "ui-components-errorrecovery",
"type": "registry:component",
"dependencies": [],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/ui-components/ErrorRecovery.tsx",
"type": "registry:component",
"content": "\"use client\";\n\nimport {\n\tCopyIcon,\n\tExclamationTriangleIcon,\n\tGlobeIcon,\n\tInfoCircledIcon,\n\tLockClosedIcon,\n\tQuestionMarkCircledIcon,\n\tReloadIcon,\n\tStopwatchIcon,\n\tTargetIcon,\n\tTokensIcon,\n} from \"@radix-ui/react-icons\";\nimport React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\nimport { Alert, AlertDescription } from \"../ui/alert.js\";\nimport { Button } from \"../ui/button.js\";\nimport { Card, CardContent } from \"../ui/card.js\";\n\nexport interface ErrorDetails {\n\tcode: string;\n\tmessage: string;\n\tdetails?: Error | Record<string, unknown>;\n\ttxid?: string;\n\ttimestamp?: number;\n}\n\nexport interface RecoverySuggestion {\n\ttitle: string;\n\tdescription: string;\n\taction?: {\n\t\tlabel: string;\n\t\tonClick: () => void;\n\t};\n\tlink?: {\n\t\tlabel: string;\n\t\thref: string;\n\t};\n}\n\nexport interface ErrorRecoveryProps {\n\terror: ErrorDetails;\n\tsuggestions?: RecoverySuggestion[];\n\tshowTechnicalDetails?: boolean;\n\tonRetry?: () => void;\n\tonDismiss?: () => void;\n\tclassName?: string;\n}\n\n// Common error recovery suggestions\nexport const commonSuggestions: Record<string, RecoverySuggestion[]> = {\n\tINSUFFICIENT_FUNDS: [\n\t\t{\n\t\t\ttitle: \"Add funds to your wallet\",\n\t\t\tdescription:\n\t\t\t\t\"You need more BSV to complete this transaction. Try reducing the amount or adding funds.\",\n\t\t\tlink: {\n\t\t\t\tlabel: \"Get BSV from an exchange\",\n\t\t\t\thref: \"https://www.coinbase.com/how-to-buy/bitcoin-sv\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: \"Use a faucet for small amounts\",\n\t\t\tdescription:\n\t\t\t\t\"For testing purposes, you can get small amounts of BSV from a faucet.\",\n\t\t\taction: {\n\t\t\t\tlabel: \"Open Faucet\",\n\t\t\t\tonClick: () => window.open(\"https://faucet.bitcoinsv.com\", \"_blank\"),\n\t\t\t},\n\t\t},\n\t],\n\tNETWORK_ERROR: [\n\t\t{\n\t\t\ttitle: \"Check your internet connection\",\n\t\t\tdescription:\n\t\t\t\t\"Make sure you have a stable internet connection and try again.\",\n\t\t},\n\t\t{\n\t\t\ttitle: \"Try a different network\",\n\t\t\tdescription:\n\t\t\t\t\"The Bitcoin node might be temporarily unavailable. Wait a moment and retry.\",\n\t\t\taction: {\n\t\t\t\tlabel: \"Retry Now\",\n\t\t\t\tonClick: () => window.location.reload(),\n\t\t\t},\n\t\t},\n\t],\n\tINVALID_ADDRESS: [\n\t\t{\n\t\t\ttitle: \"Verify the recipient address\",\n\t\t\tdescription:\n\t\t\t\t'Bitcoin addresses start with \"1\" or \"3\". Make sure you copied the complete address.',\n\t\t},\n\t\t{\n\t\t\ttitle: \"Check for typos\",\n\t\t\tdescription:\n\t\t\t\t\"Bitcoin addresses are case-sensitive. Double-check each character.\",\n\t\t},\n\t],\n\tTRANSACTION_FAILED: [\n\t\t{\n\t\t\ttitle: \"Check transaction details\",\n\t\t\tdescription:\n\t\t\t\t\"Review the transaction on a blockchain explorer to understand what happened.\",\n\t\t\tlink: {\n\t\t\t\tlabel: \"View on Explorer\",\n\t\t\t\thref: \"https://whatsonchain.com\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: \"Contact support\",\n\t\t\tdescription:\n\t\t\t\t\"If the problem persists, reach out to our support team for assistance.\",\n\t\t\tlink: {\n\t\t\t\tlabel: \"Get Help\",\n\t\t\t\thref: \"mailto:support@example.com\",\n\t\t\t},\n\t\t},\n\t],\n\tSIGNATURE_INVALID: [\n\t\t{\n\t\t\ttitle: \"Re-authenticate\",\n\t\t\tdescription: \"Your session may have expired. Try signing in again.\",\n\t\t\taction: {\n\t\t\t\tlabel: \"Sign In\",\n\t\t\t\tonClick: () => {\n\t\t\t\t\twindow.location.href = \"/login\";\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttitle: \"Check your wallet\",\n\t\t\tdescription:\n\t\t\t\t\"Make sure your wallet is unlocked and you have the correct keys.\",\n\t\t},\n\t],\n\tRATE_LIMITED: [\n\t\t{\n\t\t\ttitle: \"Wait before retrying\",\n\t\t\tdescription:\n\t\t\t\t\"You've made too many requests. Please wait a few minutes before trying again.\",\n\t\t},\n\t\t{\n\t\t\ttitle: \"Upgrade your account\",\n\t\t\tdescription: \"Premium accounts have higher rate limits for API access.\",\n\t\t\tlink: {\n\t\t\t\tlabel: \"View Plans\",\n\t\t\t\thref: \"/pricing\",\n\t\t\t},\n\t\t},\n\t],\n};\n\nexport function ErrorRecovery({\n\terror,\n\tsuggestions = [],\n\tshowTechnicalDetails = false,\n\tonRetry,\n\tonDismiss,\n\tclassName = \"\",\n}: ErrorRecoveryProps) {\n\tconst [copiedDetails, setCopiedDetails] = React.useState(false);\n\n\t// Get automatic suggestions based on error code\n\tconst autoSuggestions = (error.code && commonSuggestions[error.code]) || [];\n\n\tconst allSuggestions = [...autoSuggestions, ...suggestions];\n\n\tconst copyErrorDetails = () => {\n\t\tconst details = JSON.stringify(error, null, 2);\n\t\tnavigator.clipboard.writeText(details);\n\t\tsetCopiedDetails(true);\n\t\tsetTimeout(() => setCopiedDetails(false), 2000);\n\t};\n\n\tconst getErrorIcon = () => {\n\t\tswitch (error.code) {\n\t\t\tcase \"INSUFFICIENT_FUNDS\":\n\t\t\t\treturn <TokensIcon width=\"24\" height=\"24\" />;\n\t\t\tcase \"NETWORK_ERROR\":\n\t\t\t\treturn <GlobeIcon width=\"24\" height=\"24\" />;\n\t\t\tcase \"INVALID_ADDRESS\":\n\t\t\t\treturn <TargetIcon width=\"24\" height=\"24\" />;\n\t\t\tcase \"SIGNATURE_INVALID\":\n\t\t\t\treturn <LockClosedIcon width=\"24\" height=\"24\" />;\n\t\t\tcase \"RATE_LIMITED\":\n\t\t\t\treturn <StopwatchIcon width=\"24\" height=\"24\" />;\n\t\t\tdefault:\n\t\t\t\treturn <ExclamationTriangleIcon width=\"24\" height=\"24\" />;\n\t\t}\n\t};\n\n\treturn (\n\t\t<Card className={cn(\"border-destructive\", className)}>\n\t\t\t<CardContent className=\"p-6\">\n\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t{/* Header */}\n\t\t\t\t\t<div className=\"flex items-start gap-3\">\n\t\t\t\t\t\t<div className=\"text-destructive\">{getErrorIcon()}</div>\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2 flex-1\">\n\t\t\t\t\t\t\t<h3 className=\"text-lg font-semibold text-destructive\">\n\t\t\t\t\t\t\t\t{error.code\n\t\t\t\t\t\t\t\t\t? error.code\n\t\t\t\t\t\t\t\t\t\t\t.replace(/_/g, \" \")\n\t\t\t\t\t\t\t\t\t\t\t.toLowerCase()\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\b\\w/g, (l) => l.toUpperCase())\n\t\t\t\t\t\t\t\t\t: \"Error\"}\n\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">{error.message}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Recovery Suggestions */}\n\t\t\t\t\t{allSuggestions.length > 0 && (\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 items-center gap-2\">\n\t\t\t\t\t\t\t\t<QuestionMarkCircledIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">How to fix this:</p>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{allSuggestions.map((suggestion, index) => (\n\t\t\t\t\t\t\t\t<Card key={suggestion.title} className=\"bg-muted/50\">\n\t\t\t\t\t\t\t\t\t<CardContent className=\"p-4\">\n\t\t\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\t\t<p className=\"text-sm font-medium\">{suggestion.title}</p>\n\t\t\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\t\t\t{suggestion.description}\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t\t\t\t\t{(suggestion.action || suggestion.link) && (\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2 mt-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{suggestion.action && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={suggestion.action.onClick}\n\t\t\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\t\t\t{suggestion.action.label}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\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\t{suggestion.link && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={suggestion.link.href}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-sm text-primary hover:underline\"\n\t\t\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\t\t\t{suggestion.link.label} →\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\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</div>\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</CardContent>\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Technical Details */}\n\t\t\t\t\t{showTechnicalDetails && (\n\t\t\t\t\t\t<Alert className=\"bg-muted\">\n\t\t\t\t\t\t\t<InfoCircledIcon className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t<AlertDescription className=\"w-full\">\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 className=\"flex items-center justify-between\">\n\t\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\t\tTechnical Details\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=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={copyErrorDetails}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<CopyIcon className=\"h-3 w-3 mr-1\" />\n\t\t\t\t\t\t\t\t\t\t\t{copiedDetails ? \"Copied!\" : \"Copy\"}\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<code className=\"text-xs bg-background p-2 rounded border whitespace-pre-wrap\">\n\t\t\t\t\t\t\t\t\t\t{JSON.stringify(error, null, 2)}\n\t\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t\t</Alert>\n\t\t\t\t\t)}\n\n\t\t\t\t\t{/* Actions */}\n\t\t\t\t\t<div className=\"flex gap-2 justify-end\">\n\t\t\t\t\t\t{onDismiss && (\n\t\t\t\t\t\t\t<Button variant=\"secondary\" onClick={onDismiss}>\n\t\t\t\t\t\t\t\tDismiss\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{onRetry && (\n\t\t\t\t\t\t\t<Button onClick={onRetry}>\n\t\t\t\t\t\t\t\t<ReloadIcon className=\"h-4 w-4 mr-2\" />\n\t\t\t\t\t\t\t\tTry Again\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{/* Help Link */}\n\t\t\t\t\t<div className=\"flex justify-center pt-2\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref=\"/help\"\n\t\t\t\t\t\t\tclassName=\"text-xs text-muted-foreground hover:text-foreground\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tNeed more help? Visit our support center\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n}\n",
"target": "<%- config.aliases.components %>/errorrecovery.tsx"
}
]
}