bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
136 lines (135 loc) • 8.37 kB
JavaScript
"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" }) })] }) }) }));
}