diff --git a/components/txn-feedback-toast-preview.tsx b/components/txn-feedback-toast-preview.tsx new file mode 100644 index 0000000..d0d8db7 --- /dev/null +++ b/components/txn-feedback-toast-preview.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import type { TransactionStatus } from "@/types/transaction"; +import { TxnFeedbackToast } from "@/components/ui/murphy"; + +export default function TxnFeedbackToastPreview() { + const [txStatus, setTxStatus] = useState({ + status: "idle", + }); + + const showToast = ( + status: TransactionStatus["status"], + error?: string, + signature?: string + ) => { + setTxStatus({ status, error, signature }); + }; + + const simulateTransaction = async () => { + const statuses: TransactionStatus["status"][] = [ + "preparing", + "signing", + "sending", + "confirming", + ]; + + for (const status of statuses) { + setTxStatus({ status }); + await new Promise((resolve) => setTimeout(resolve, 1500)); + } + + if (Math.random() > 0.3) { + setTxStatus({ + status: "success", + signature: + "5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM7n", + }); + } else { + setTxStatus({ + status: "error", + error: "Transaction failed: Insufficient funds for transaction fees", + }); + } + }; + + return ( +
+ {/* Controls */} +
+
+ {["preparing", "signing", "sending", "confirming"].map((status) => ( + + ))} +
+ +
+ + +
+ + + + +
+ + {/* Toast */} + setTxStatus({ status: "idle" })} + /> +
+ ); +} diff --git a/components/txn-retry-button-preview.tsx b/components/txn-retry-button-preview.tsx new file mode 100644 index 0000000..5ab345d --- /dev/null +++ b/components/txn-retry-button-preview.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { useState } from "react"; +import { TxnFeedbackToast } from "./ui/murphy"; +import type { TransactionStatus } from "@/types/transaction"; +import { TxnRetryButton } from "./ui/murphy/Txn-Feedback/txn-retry-button"; + +export default function TxnRetryButtonPreview() { + const [toastStatus, setToastStatus] = useState({ + status: "idle", + }); + + const simulateTransaction = async () => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + const success = Math.random() < 0.6; + + if (success) { + setToastStatus({ + status: "success", + }); + } else { + setToastStatus({ + status: "error", + }); + throw new Error("Simulated transaction failure"); + } + }; + + const closeToast = () => { + setToastStatus({ status: "idle" }); + }; + + return ( +
+
+
+

+ Standard Retry (3 attempts) +

+ + Retry Transaction + +
+ +
+

+ Quick Retry (5 attempts, 500ms delay) +

+ + Quick Retry + +
+ +
+

+ Single Retry (1 attempt) +

+ + Single Retry + +
+
+ + +
+ ); +} diff --git a/components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx b/components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx new file mode 100644 index 0000000..a06a31f --- /dev/null +++ b/components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + X, + CheckCircle, + AlertCircle, + Loader2, + Send, + Clock, + FileSignature, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { TransactionStatus } from "@/types/transaction"; + +interface TxnFeedbackToastProps { + status: TransactionStatus; + onRetry?: () => void; + onClose: () => void; +} + +export function TxnFeedbackToast({ + status, + onRetry, + onClose, +}: TxnFeedbackToastProps) { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + if (status.status !== "idle") { + setIsVisible(true); + } else { + setIsVisible(false); + } + }, [status.status]); + + if (!isVisible || status.status === "idle") { + return null; + } + + const getStatusConfig = () => { + switch (status.status) { + case "preparing": + return { + icon: , + title: "Preparing Transaction", + description: "Setting up your transaction...", + bgColor: "bg-blue-50 dark:bg-blue-950", + borderColor: "border-blue-200 dark:border-blue-800", + textColor: "text-blue-900 dark:text-blue-100", + }; + case "signing": + return { + icon: , + title: "Signing Transaction", + description: "Please sign the transaction in your wallet...", + bgColor: "bg-yellow-50 dark:bg-yellow-950", + borderColor: "border-yellow-200 dark:border-yellow-800", + textColor: "text-yellow-900 dark:text-yellow-100", + }; + case "sending": + return { + icon: , + title: "Sending Transaction", + description: "Broadcasting to the network...", + bgColor: "bg-purple-50 dark:bg-purple-950", + borderColor: "border-purple-200 dark:border-purple-800", + textColor: "text-purple-900 dark:text-purple-100", + }; + case "confirming": + return { + icon: , + title: "Confirming Transaction", + description: "Waiting for network confirmation...", + bgColor: "bg-orange-50 dark:bg-orange-950", + borderColor: "border-orange-200 dark:border-orange-800", + textColor: "text-orange-900 dark:text-orange-100", + }; + case "success": + return { + icon: ( + + ), + title: "Transaction Successful", + description: status.signature + ? `Signature: ${status.signature.slice( + 0, + 8 + )}...${status.signature.slice(-8)}` + : "Your transaction has been completed successfully.", + bgColor: "bg-green-50 dark:bg-green-950", + borderColor: "border-green-200 dark:border-green-800", + textColor: "text-green-900 dark:text-green-100", + }; + case "error": + return { + icon: , + title: "Transaction Failed", + description: + status.error || + "An error occurred while processing your transaction.", + bgColor: "bg-red-50 dark:bg-red-950", + borderColor: "border-red-200 dark:border-red-800", + textColor: "text-red-900 dark:text-red-100", + }; + default: + return null; + } + }; + + const config = getStatusConfig(); + if (!config) return null; + + return ( +
+
+
+ {/* Icon container with proper alignment */} +
+ {config.icon} +
+ + {/* Content container */} +
+
+ {config.title} +
+
+ {config.description} +
+ + {/* Action buttons */} + {status.status === "error" && onRetry && ( +
+ +
+ )} + + {status.status === "success" && status.signature && ( +
+ +
+ )} +
+ + {/* Close button with proper alignment */} +
+ +
+
+
+
+ ); +} diff --git a/components/ui/murphy/Txn-Feedback/txn-retry-button.tsx b/components/ui/murphy/Txn-Feedback/txn-retry-button.tsx new file mode 100644 index 0000000..61a1d74 --- /dev/null +++ b/components/ui/murphy/Txn-Feedback/txn-retry-button.tsx @@ -0,0 +1,75 @@ +"use client"; + +import type React from "react"; +import { useState } from "react"; +import { RefreshCw } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +interface TxnRetryButtonProps { + onRetry: () => Promise | void; + disabled?: boolean; + maxRetries?: number; + retryDelay?: number; + children?: React.ReactNode; + className?: string; + variant?: + | "default" + | "destructive" + | "outline" + | "secondary" + | "ghost" + | "link"; + size?: "default" | "sm" | "lg" | "icon"; +} + +export function TxnRetryButton({ + onRetry, + disabled = false, + maxRetries = 3, + retryDelay = 1000, + children = "Retry", + className, + variant = "default", + size = "default", +}: TxnRetryButtonProps) { + const [isRetrying, setIsRetrying] = useState(false); + const [retryCount, setRetryCount] = useState(0); + + const handleRetry = async () => { + if (retryCount >= maxRetries || isRetrying) return; + + setIsRetrying(true); + + try { + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + await onRetry(); + setRetryCount(0); + } catch (error) { + setRetryCount((prev) => prev + 1); + console.error("Retry failed:", error); + } finally { + setIsRetrying(false); + } + }; + + const isDisabled = disabled || isRetrying || retryCount >= maxRetries; + + return ( + + ); +} diff --git a/components/ui/murphy/index.tsx b/components/ui/murphy/index.tsx index 987b191..cf7cebd 100644 --- a/components/ui/murphy/index.tsx +++ b/components/ui/murphy/index.tsx @@ -14,7 +14,7 @@ import CandyMachineForm from "./candy-machine-form"; import CoreCandyMachineForm from "./core-candy-machine-form"; import BubblegumLegacyForm from "./bubblegum-legacy-form"; import ImprovedCNFTManager from "./improved-cnft-manager"; -import CompressedNFTViewer from "./compressed-nft-viewer" +import CompressedNFTViewer from "./compressed-nft-viewer"; import { CreateMerkleTree } from "./create-merkleTree-form"; import { TokenList } from "./token-list"; import { StakeForm } from "./stake-token-form"; @@ -37,6 +37,8 @@ import { CoreAssetLaunchpad } from "./core-asset-launchpad"; import { HydraFanoutForm } from "./hydra-fanout-form"; import { MPLHybridForm } from "./mpl-hybrid-form"; import { TokenMetadataViewer } from "./token-metadata-viewer"; +import { TxnFeedbackToast } from "./Txn-Feedback/txn-feedback-toast"; +import { TxnRetryButton } from "./Txn-Feedback/txn-retry-button"; export { ConnectWalletButton, @@ -78,5 +80,7 @@ export { TMLaunchpadForm, HydraFanoutForm, MPLHybridForm, - TokenMetadataViewer + TokenMetadataViewer, + TxnFeedbackToast, + TxnRetryButton, }; diff --git a/content/docs/onchainkit/Txn-Feedback/txn-feedback-toast.mdx b/content/docs/onchainkit/Txn-Feedback/txn-feedback-toast.mdx new file mode 100644 index 0000000..cdcd48c --- /dev/null +++ b/content/docs/onchainkit/Txn-Feedback/txn-feedback-toast.mdx @@ -0,0 +1,145 @@ +--- +title: Transaction Feedback Toast +description: Real-time toast notifications for transaction status updates +icon: Bell +--- + +import TxnFeedbackToastPreview from "@/components/txn-feedback-toast-preview"; + + +
+ +

+ Click buttons above to see toast notifications in the top-right corner +

+
+
+ +## Installation + + + + + Install Transaction Feedback Toast + + + + + +## Basic Usage + +```tsx +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { TxnFeedbackToast } from "@/components/ui/murphy/Txn-Feedback/txn-feedback-toast"; +import type { TransactionStatus } from "@/types/transaction"; + +export default function MyPage() { + const [txnStatus, setTxnStatus] = useState({ + status: "idle", + }); + + const simulateTransaction = async () => { + const statuses: TransactionStatus["status"][] = [ + "preparing", + "signing", + "sending", + "confirming", + ]; + + for (const status of statuses) { + setTxnStatus({ status }); + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + + // Simulate success or error + if (Math.random() > 0.3) { + setTxnStatus({ + status: "success", + signature: + "5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM7n", + }); + } else { + setTxnStatus({ + status: "error", + error: "Transaction failed: Insufficient funds for transaction fees", + }); + } + }; + + return ( +
+

Transaction Demo

+ + + + simulateTransaction()} + onClose={() => setTxnStatus({ status: "idle" })} + /> +
+ ); +} +``` + +## Props + + void", + default: "undefined", + }, + onClose: { + description: "Callback function when toast is closed", + type: "() => void", + default: "undefined", + }, + }} +/> + +### TransactionStatus Interface + + diff --git a/content/docs/onchainkit/Txn-Feedback/txn-retry-button.mdx b/content/docs/onchainkit/Txn-Feedback/txn-retry-button.mdx new file mode 100644 index 0000000..18c4511 --- /dev/null +++ b/content/docs/onchainkit/Txn-Feedback/txn-retry-button.mdx @@ -0,0 +1,190 @@ +--- +title: Transaction Retry Button +description: Smart retry functionality with configurable limits and delays +icon: RefreshCw +--- + +import TxnRetryButtonPreview from "@/components/txn-retry-button-preview"; + + +
+ +
+
+ +## Installation + + + + + Install Transaction Retry Button + + + + + +## Basic Usage + +```tsx +"use client"; + +import { TxnRetryButton } from "@/components/ui/murphy/Txn-Feedback/txn-retry-button"; + +export default function MyPage() { + const executeTransaction = async () => { + // Simulate network delay + await new Promise((resolve) => setTimeout(resolve, 1500)); + + // 60% chance of failure to demonstrate retry logic + if (Math.random() < 0.6) { + throw new Error("Transaction failed: Network timeout"); + } + + console.log("Transaction successful!"); + }; + + return ( +
+

Retry Button Demo

+ + + Execute Transaction + +
+ ); +} +``` + +## Props + + Promise | void", + default: "required", + }, + disabled: { + description: "Whether the button is disabled", + type: "boolean", + default: "false", + }, + maxRetries: { + description: "Maximum number of retry attempts", + type: "number", + default: "3", + }, + retryDelay: { + description: "Delay between retries in milliseconds", + type: "number", + default: "1000", + }, + children: { + description: "Button content/label", + type: "React.ReactNode", + default: "'Retry'", + }, + className: { + description: "Additional CSS classes", + type: "string", + default: "undefined", + }, + variant: { + description: "Button variant style", + type: "'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'", + default: "'default'", + }, + size: { + description: "Button size", + type: "'default' | 'sm' | 'lg' | 'icon'", + default: "'default'", + }, + }} +/> + +## Retry Strategies + +### Network Errors + +For network-related failures, use longer delays and more retries: + +```tsx + + Network Transaction + +``` + +### Program Errors + +For smart contract errors, use fewer retries: + +```tsx + + Program Transaction + +``` + +### Timeout Errors + +For timeout errors, use moderate retries with increasing delays: + +```tsx + + Timeout-Prone Transaction + +``` + +## Button States + +### Loading State + +Shows spinning icon and "Retrying..." text during retry attempts. + +### Disabled State + +Button becomes disabled after reaching maximum retry attempts. + +### Retry Counter + +Displays current retry attempt count: "(2/3)" + +### Success Reset + +Retry counter resets to 0 after successful execution. + +## Customization + +### Custom Labels + +```tsx + + {(isRetrying) => (isRetrying ? "Processing..." : "Try Again")} + +``` + +### Custom Styling + +```tsx + + Custom Styled Retry + +``` diff --git a/content/docs/onchainkit/meta.json b/content/docs/onchainkit/meta.json index d2b1c3b..329e6c2 100644 --- a/content/docs/onchainkit/meta.json +++ b/content/docs/onchainkit/meta.json @@ -31,6 +31,7 @@ "Metaplex", "Meteora-DBC", "ZK-Compression", - "Jupiter-Recurring" + "Jupiter-Recurring", + "Txn-Feedback" ] } diff --git a/public/r/txn-feedback-toast.json b/public/r/txn-feedback-toast.json new file mode 100644 index 0000000..bd8bae5 --- /dev/null +++ b/public/r/txn-feedback-toast.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "txn-feedback-toast", + "type": "registry:block", + "title": "Toast component to display transaction status (loading, success, error).", + "dependencies": [ + "sonner" + ], + "registryDependencies": [ + "toast" + ], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx", + "content": "\"use client\";\r\n\r\nimport { useEffect, useState } from \"react\";\r\nimport {\r\n X,\r\n CheckCircle,\r\n AlertCircle,\r\n Loader2,\r\n Send,\r\n Clock,\r\n FileSignature,\r\n} from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { TransactionStatus } from \"@/types/transaction\";\r\n\r\ninterface TxnFeedbackToastProps {\r\n status: TransactionStatus;\r\n onRetry?: () => void;\r\n onClose: () => void;\r\n}\r\n\r\nexport function TxnFeedbackToast({\r\n status,\r\n onRetry,\r\n onClose,\r\n}: TxnFeedbackToastProps) {\r\n const [isVisible, setIsVisible] = useState(false);\r\n\r\n useEffect(() => {\r\n if (status.status !== \"idle\") {\r\n setIsVisible(true);\r\n } else {\r\n setIsVisible(false);\r\n }\r\n }, [status.status]);\r\n\r\n if (!isVisible || status.status === \"idle\") {\r\n return null;\r\n }\r\n\r\n const getStatusConfig = () => {\r\n switch (status.status) {\r\n case \"preparing\":\r\n return {\r\n icon: ,\r\n title: \"Preparing Transaction\",\r\n description: \"Setting up your transaction...\",\r\n bgColor: \"bg-blue-50 dark:bg-blue-950\",\r\n borderColor: \"border-blue-200 dark:border-blue-800\",\r\n textColor: \"text-blue-900 dark:text-blue-100\",\r\n };\r\n case \"signing\":\r\n return {\r\n icon: ,\r\n title: \"Signing Transaction\",\r\n description: \"Please sign the transaction in your wallet...\",\r\n bgColor: \"bg-yellow-50 dark:bg-yellow-950\",\r\n borderColor: \"border-yellow-200 dark:border-yellow-800\",\r\n textColor: \"text-yellow-900 dark:text-yellow-100\",\r\n };\r\n case \"sending\":\r\n return {\r\n icon: ,\r\n title: \"Sending Transaction\",\r\n description: \"Broadcasting to the network...\",\r\n bgColor: \"bg-purple-50 dark:bg-purple-950\",\r\n borderColor: \"border-purple-200 dark:border-purple-800\",\r\n textColor: \"text-purple-900 dark:text-purple-100\",\r\n };\r\n case \"confirming\":\r\n return {\r\n icon: ,\r\n title: \"Confirming Transaction\",\r\n description: \"Waiting for network confirmation...\",\r\n bgColor: \"bg-orange-50 dark:bg-orange-950\",\r\n borderColor: \"border-orange-200 dark:border-orange-800\",\r\n textColor: \"text-orange-900 dark:text-orange-100\",\r\n };\r\n case \"success\":\r\n return {\r\n icon: (\r\n \r\n ),\r\n title: \"Transaction Successful\",\r\n description: status.signature\r\n ? `Signature: ${status.signature.slice(\r\n 0,\r\n 8\r\n )}...${status.signature.slice(-8)}`\r\n : \"Your transaction has been completed successfully.\",\r\n bgColor: \"bg-green-50 dark:bg-green-950\",\r\n borderColor: \"border-green-200 dark:border-green-800\",\r\n textColor: \"text-green-900 dark:text-green-100\",\r\n };\r\n case \"error\":\r\n return {\r\n icon: ,\r\n title: \"Transaction Failed\",\r\n description:\r\n status.error ||\r\n \"An error occurred while processing your transaction.\",\r\n bgColor: \"bg-red-50 dark:bg-red-950\",\r\n borderColor: \"border-red-200 dark:border-red-800\",\r\n textColor: \"text-red-900 dark:text-red-100\",\r\n };\r\n default:\r\n return null;\r\n }\r\n };\r\n\r\n const config = getStatusConfig();\r\n if (!config) return null;\r\n\r\n return (\r\n
\r\n \r\n
\r\n {/* Icon container with proper alignment */}\r\n
\r\n {config.icon}\r\n
\r\n\r\n {/* Content container */}\r\n
\r\n
\r\n {config.title}\r\n
\r\n \r\n {config.description}\r\n
\r\n\r\n {/* Action buttons */}\r\n {status.status === \"error\" && onRetry && (\r\n
\r\n \r\n Retry\r\n \r\n
\r\n )}\r\n\r\n {status.status === \"success\" && status.signature && (\r\n
\r\n {\r\n navigator.clipboard.writeText(status.signature!);\r\n }}\r\n size=\"sm\"\r\n variant=\"outline\"\r\n className=\"h-7 px-2 text-xs\"\r\n >\r\n Copy Signature\r\n \r\n
\r\n )}\r\n
\r\n\r\n {/* Close button with proper alignment */}\r\n
\r\n \r\n \r\n Close\r\n \r\n
\r\n
\r\n \r\n \r\n );\r\n}\r\n", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx" + } + ] +} \ No newline at end of file diff --git a/public/r/txn-retry-button.json b/public/r/txn-retry-button.json new file mode 100644 index 0000000..151fe9a --- /dev/null +++ b/public/r/txn-retry-button.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "txn-retry-button", + "type": "registry:block", + "title": "A button component with built-in retry handling for transactions.", + "dependencies": [], + "registryDependencies": [ + "button" + ], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-retry-button.tsx", + "content": "\"use client\";\r\n\r\nimport type React from \"react\";\r\nimport { useState } from \"react\";\r\nimport { RefreshCw } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface TxnRetryButtonProps {\r\n onRetry: () => Promise | void;\r\n disabled?: boolean;\r\n maxRetries?: number;\r\n retryDelay?: number;\r\n children?: React.ReactNode;\r\n className?: string;\r\n variant?:\r\n | \"default\"\r\n | \"destructive\"\r\n | \"outline\"\r\n | \"secondary\"\r\n | \"ghost\"\r\n | \"link\";\r\n size?: \"default\" | \"sm\" | \"lg\" | \"icon\";\r\n}\r\n\r\nexport function TxnRetryButton({\r\n onRetry,\r\n disabled = false,\r\n maxRetries = 3,\r\n retryDelay = 1000,\r\n children = \"Retry\",\r\n className,\r\n variant = \"default\",\r\n size = \"default\",\r\n}: TxnRetryButtonProps) {\r\n const [isRetrying, setIsRetrying] = useState(false);\r\n const [retryCount, setRetryCount] = useState(0);\r\n\r\n const handleRetry = async () => {\r\n if (retryCount >= maxRetries || isRetrying) return;\r\n\r\n setIsRetrying(true);\r\n\r\n try {\r\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\r\n await onRetry();\r\n setRetryCount(0);\r\n } catch (error) {\r\n setRetryCount((prev) => prev + 1);\r\n console.error(\"Retry failed:\", error);\r\n } finally {\r\n setIsRetrying(false);\r\n }\r\n };\r\n\r\n const isDisabled = disabled || isRetrying || retryCount >= maxRetries;\r\n\r\n return (\r\n \r\n \r\n {isRetrying ? \"Retrying...\" : children}\r\n {retryCount > 0 && (\r\n \r\n ({retryCount}/{maxRetries})\r\n \r\n )}\r\n \r\n );\r\n}\r\n", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-retry-button.tsx" + } + ] +} \ No newline at end of file diff --git a/registry.json b/registry.json index bac943b..ddbac2e 100644 --- a/registry.json +++ b/registry.json @@ -1386,6 +1386,24 @@ } ] }, + { + "name": "txn-feedback-toast", + "type": "registry:block", + "title": "Toast component to display transaction status (loading, success, error).", + "registryDependencies": [ + "toast" + ], + "dependencies": [ + "sonner" + ], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx" + } + ] + }, { "name": "txn-list", "type": "registry:block", @@ -1412,6 +1430,22 @@ } ] }, + { + "name": "txn-retry-button", + "type": "registry:block", + "title": "A button component with built-in retry handling for transactions.", + "registryDependencies": [ + "button" + ], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-retry-button.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-retry-button.tsx" + } + ] + }, { "name": "txn-settings", "type": "registry:block", diff --git a/registry/components/txn-feedback-toast.json b/registry/components/txn-feedback-toast.json new file mode 100644 index 0000000..f769f5d --- /dev/null +++ b/registry/components/txn-feedback-toast.json @@ -0,0 +1,14 @@ +{ + "name": "txn-feedback-toast", + "type": "registry:block", + "title": "Toast component to display transaction status (loading, success, error).", + "registryDependencies": ["toast"], + "dependencies": ["sonner"], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-feedback-toast.tsx" + } + ] +} diff --git a/registry/components/txn-retry-button.json b/registry/components/txn-retry-button.json new file mode 100644 index 0000000..61e4e42 --- /dev/null +++ b/registry/components/txn-retry-button.json @@ -0,0 +1,14 @@ +{ + "name": "txn-retry-button", + "type": "registry:block", + "title": "A button component with built-in retry handling for transactions.", + "registryDependencies": ["button"], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/txn-retry-button.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/txn-retry-button.tsx" + } + ] +} diff --git a/types/transaction/index.tsx b/types/transaction/index.tsx new file mode 100644 index 0000000..5be4a81 --- /dev/null +++ b/types/transaction/index.tsx @@ -0,0 +1,27 @@ +export interface TransactionStatus { + status: + | "idle" + | "preparing" + | "signing" + | "sending" + | "confirming" + | "success" + | "error"; + signature?: string; + error?: string; + step?: number; + totalSteps?: number; +} + +export interface TxnStep { + id: string; + title: string; + description?: string; + status: "pending" | "active" | "completed" | "error"; +} + +export interface TxnFeedbackProps { + status: TransactionStatus; + onRetry?: () => void; + onClose?: () => void; +}