UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

136 lines (135 loc) 8.37 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { CopyIcon, ExclamationTriangleIcon, GlobeIcon, InfoCircledIcon, LockClosedIcon, QuestionMarkCircledIcon, ReloadIcon, StopwatchIcon, TargetIcon, TokensIcon, } from "@radix-ui/react-icons"; import React from "react"; import { cn } from "../../lib/utils.js"; import { Alert, AlertDescription } from "../ui/alert.js"; import { Button } from "../ui/button.js"; import { Card, CardContent } from "../ui/card.js"; // Common error recovery suggestions export const commonSuggestions = { INSUFFICIENT_FUNDS: [ { title: "Add funds to your wallet", description: "You need more BSV to complete this transaction. Try reducing the amount or adding funds.", link: { label: "Get BSV from an exchange", href: "https://www.coinbase.com/how-to-buy/bitcoin-sv", }, }, { title: "Use a faucet for small amounts", description: "For testing purposes, you can get small amounts of BSV from a faucet.", action: { label: "Open Faucet", onClick: () => window.open("https://faucet.bitcoinsv.com", "_blank"), }, }, ], NETWORK_ERROR: [ { title: "Check your internet connection", description: "Make sure you have a stable internet connection and try again.", }, { title: "Try a different network", description: "The Bitcoin node might be temporarily unavailable. Wait a moment and retry.", action: { label: "Retry Now", onClick: () => window.location.reload(), }, }, ], INVALID_ADDRESS: [ { title: "Verify the recipient address", description: 'Bitcoin addresses start with "1" or "3". Make sure you copied the complete address.', }, { title: "Check for typos", description: "Bitcoin addresses are case-sensitive. Double-check each character.", }, ], TRANSACTION_FAILED: [ { title: "Check transaction details", description: "Review the transaction on a blockchain explorer to understand what happened.", link: { label: "View on Explorer", href: "https://whatsonchain.com", }, }, { title: "Contact support", description: "If the problem persists, reach out to our support team for assistance.", link: { label: "Get Help", href: "mailto:support@example.com", }, }, ], SIGNATURE_INVALID: [ { title: "Re-authenticate", description: "Your session may have expired. Try signing in again.", action: { label: "Sign In", onClick: () => { window.location.href = "/login"; }, }, }, { title: "Check your wallet", description: "Make sure your wallet is unlocked and you have the correct keys.", }, ], RATE_LIMITED: [ { title: "Wait before retrying", description: "You've made too many requests. Please wait a few minutes before trying again.", }, { title: "Upgrade your account", description: "Premium accounts have higher rate limits for API access.", link: { label: "View Plans", href: "/pricing", }, }, ], }; export function ErrorRecovery({ error, suggestions = [], showTechnicalDetails = false, onRetry, onDismiss, className = "", }) { const [copiedDetails, setCopiedDetails] = React.useState(false); // Get automatic suggestions based on error code const autoSuggestions = (error.code && commonSuggestions[error.code]) || []; const allSuggestions = [...autoSuggestions, ...suggestions]; const copyErrorDetails = () => { const details = JSON.stringify(error, null, 2); navigator.clipboard.writeText(details); setCopiedDetails(true); setTimeout(() => setCopiedDetails(false), 2000); }; const getErrorIcon = () => { switch (error.code) { case "INSUFFICIENT_FUNDS": return _jsx(TokensIcon, { width: "24", height: "24" }); case "NETWORK_ERROR": return _jsx(GlobeIcon, { width: "24", height: "24" }); case "INVALID_ADDRESS": return _jsx(TargetIcon, { width: "24", height: "24" }); case "SIGNATURE_INVALID": return _jsx(LockClosedIcon, { width: "24", height: "24" }); case "RATE_LIMITED": return _jsx(StopwatchIcon, { width: "24", height: "24" }); default: return _jsx(ExclamationTriangleIcon, { width: "24", height: "24" }); } }; return (_jsx(Card, { className: cn("border-destructive", className), children: _jsx(CardContent, { className: "p-6", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "text-destructive", children: getErrorIcon() }), _jsxs("div", { className: "flex flex-col gap-2 flex-1", children: [_jsx("h3", { className: "text-lg font-semibold text-destructive", children: error.code ? error.code .replace(/_/g, " ") .toLowerCase() .replace(/\b\w/g, (l) => l.toUpperCase()) : "Error" }), _jsx("p", { className: "text-sm text-muted-foreground", children: error.message })] })] }), allSuggestions.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(QuestionMarkCircledIcon, { className: "h-4 w-4" }), _jsx("p", { className: "text-sm font-medium", children: "How to fix this:" })] }), allSuggestions.map((suggestion, index) => (_jsx(Card, { className: "bg-muted/50", children: _jsx(CardContent, { className: "p-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-medium", children: suggestion.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: suggestion.description }), (suggestion.action || suggestion.link) && (_jsxs("div", { className: "flex gap-2 mt-2", children: [suggestion.action && (_jsx(Button, { size: "sm", variant: "secondary", onClick: suggestion.action.onClick, children: suggestion.action.label })), suggestion.link && (_jsxs("a", { href: suggestion.link.href, target: "_blank", rel: "noopener noreferrer", className: "text-sm text-primary hover:underline", children: [suggestion.link.label, " \u2192"] }))] }))] }) }) }, suggestion.title)))] })), showTechnicalDetails && (_jsxs(Alert, { className: "bg-muted", children: [_jsx(InfoCircledIcon, { className: "h-4 w-4" }), _jsx(AlertDescription, { className: "w-full", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium", children: "Technical Details" }), _jsxs(Button, { size: "sm", variant: "ghost", onClick: copyErrorDetails, children: [_jsx(CopyIcon, { className: "h-3 w-3 mr-1" }), copiedDetails ? "Copied!" : "Copy"] })] }), _jsx("code", { className: "text-xs bg-background p-2 rounded border whitespace-pre-wrap", children: JSON.stringify(error, null, 2) })] }) })] })), _jsxs("div", { className: "flex gap-2 justify-end", children: [onDismiss && (_jsx(Button, { variant: "secondary", onClick: onDismiss, children: "Dismiss" })), onRetry && (_jsxs(Button, { onClick: onRetry, children: [_jsx(ReloadIcon, { className: "h-4 w-4 mr-2" }), "Try Again"] }))] }), _jsx("div", { className: "flex justify-center pt-2", children: _jsx("a", { href: "/help", className: "text-xs text-muted-foreground hover:text-foreground", children: "Need more help? Visit our support center" }) })] }) }) })); }