From 3b0a07f0fcca31bce4984175b85259c5ee34f645 Mon Sep 17 00:00:00 2001 From: Van Minh Date: Tue, 19 Aug 2025 17:47:58 +0700 Subject: [PATCH] feat: add SuccessDialog component for transaction completion --- components/success-dialog-preview.tsx | 36 +++ .../ui/murphy/Txn-Feedback/success-dialog.tsx | 135 +++++++++++ components/ui/murphy/index.tsx | 6 +- .../Txn-Feedback/success-dialog.mdx | 210 ++++++++++++++++++ content/docs/onchainkit/meta.json | 3 +- public/r/success-dialog.json | 19 ++ registry.json | 17 ++ registry/components/success-dialog.json | 14 ++ 8 files changed, 437 insertions(+), 3 deletions(-) create mode 100644 components/success-dialog-preview.tsx create mode 100644 components/ui/murphy/Txn-Feedback/success-dialog.tsx create mode 100644 content/docs/onchainkit/Txn-Feedback/success-dialog.mdx create mode 100644 public/r/success-dialog.json create mode 100644 registry/components/success-dialog.json diff --git a/components/success-dialog-preview.tsx b/components/success-dialog-preview.tsx new file mode 100644 index 0000000..6a30f8c --- /dev/null +++ b/components/success-dialog-preview.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { SuccessDialog } from "./ui/murphy"; + +export default function SuccessDialogPreview() { + const [open, setOpen] = useState(false); + + return ( +
+ + + window.open( + "https://explorer.solana.com/tx/5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM7n", + "_blank" + ) + } + /> +
+ ); +} diff --git a/components/ui/murphy/Txn-Feedback/success-dialog.tsx b/components/ui/murphy/Txn-Feedback/success-dialog.tsx new file mode 100644 index 0000000..03cd242 --- /dev/null +++ b/components/ui/murphy/Txn-Feedback/success-dialog.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { CheckCircle, ExternalLink, Copy } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { useState } from "react"; + +interface SuccessDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title?: string; + description?: string; + signature?: string; + amount?: string; + token?: string; + recipient?: string; + onViewExplorer?: () => void; + onClose?: () => void; +} + +export function SuccessDialog({ + open, + onOpenChange, + title = "Transaction Successful! 🎉", + description = "Your transaction has been confirmed on the blockchain", + signature, + amount, + token, + recipient, + onViewExplorer, + onClose, +}: SuccessDialogProps) { + const [copied, setCopied] = useState(false); + + const copySignature = async () => { + if (!signature) return; + await navigator.clipboard.writeText(signature); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const handleClose = () => { + onOpenChange(false); + onClose?.(); + }; + + return ( + + + +
+ +
+ + {title} + + + {description} + +
+ +
+ {(amount || token) && ( +
+
+

+ {amount} {token} +

+ {recipient && ( +

+ Sent to {recipient.slice(0, 8)}...{recipient.slice(-8)} +

+ )} +
+
+ )} + + {signature && ( +
+
+
+

+ Transaction Signature +

+

+ {signature.slice(0, 12)}...{signature.slice(-12)} +

+
+
+ + {onViewExplorer && ( + + )} +
+
+ {copied && ( + + Copied! + + )} +
+ )} +
+ + + + +
+
+ ); +} diff --git a/components/ui/murphy/index.tsx b/components/ui/murphy/index.tsx index 987b191..d7e3c36 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,7 @@ 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 { SuccessDialog } from "./Txn-Feedback/success-dialog"; export { ConnectWalletButton, @@ -78,5 +79,6 @@ export { TMLaunchpadForm, HydraFanoutForm, MPLHybridForm, - TokenMetadataViewer + TokenMetadataViewer, + SuccessDialog, }; diff --git a/content/docs/onchainkit/Txn-Feedback/success-dialog.mdx b/content/docs/onchainkit/Txn-Feedback/success-dialog.mdx new file mode 100644 index 0000000..6543e94 --- /dev/null +++ b/content/docs/onchainkit/Txn-Feedback/success-dialog.mdx @@ -0,0 +1,210 @@ +--- +title: Success Dialog +description: Celebration modal for successful transaction completion +icon: Check +--- + +import SuccessDialogPreview from "@/components/success-dialog-preview"; + + + + + +## Installation + + + +{" "} + + + Install Success Dialog + + + + + +## Basic Usage + +```tsx +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { SuccessDialog } from "@/components/ui/murphy/success-dialog"; + +export default function MyPage() { + const [showSuccess, setShowSuccess] = useState(false); + + const handleTransaction = async () => { + try { + // Your transaction logic here + const signature = await executeTransaction(); + + // Show success dialog + setShowSuccess(true); + } catch (error) { + console.error("Transaction failed:", error); + } + }; + + return ( +
+

Transaction Demo

+ + + + + window.open(`https://explorer.solana.com/tx/${signature}`, "_blank") + } + onClose={() => setShowSuccess(false)} + /> +
+ ); +} +``` + +## Props + + void", + default: "required", + }, + title: { + description: "Dialog title text", + type: "string", + default: "'Transaction Successful! 🎉'", + }, + description: { + description: "Dialog description text", + type: "string", + default: "'Your transaction has been confirmed on the blockchain'", + }, + signature: { + description: "Transaction signature to display", + type: "string", + default: "undefined", + }, + amount: { + description: "Transaction amount", + type: "string", + default: "undefined", + }, + token: { + description: "Token symbol or name", + type: "string", + default: "undefined", + }, + recipient: { + description: "Recipient wallet address", + type: "string", + default: "undefined", + }, + onViewExplorer: { + description: "Callback when explorer link is clicked", + type: "() => void", + default: "undefined", + }, + onClose: { + description: "Callback when dialog is closed", + type: "() => void", + default: "undefined", + }, + }} +/> + +## Dialog Features + +### Celebratory Design + +- Large success icon with green color scheme +- Celebratory emojis in titles +- Positive messaging and visual feedback + +### Transaction Information + +- Transaction signature display with copy functionality +- Amount and token information prominently displayed +- Recipient address when applicable + +### Explorer Integration + +- Direct links to Solana Explorer +- Support for different clusters (mainnet, devnet, testnet) +- One-click transaction verification + +### Copy Functionality + +- Copy transaction signature to clipboard +- Visual feedback when copied +- Automatic clipboard API detection + +## Customization + +### Custom Titles and Messages + +```tsx +const getCustomTitle = (transactionType: string, success: boolean) => { + if (!success) return "Transaction Failed"; + + const titles = { + transfer: "Money Sent! 💰", + mint: "NFT Created! 🎨", + stake: "Earning Rewards! 📈", + swap: "Tokens Swapped! 🔄", + }; + + return titles[transactionType] || "Success! ✅"; +}; + +; +``` + +### Custom Styling + +```tsx + +``` + +### Conditional Content + +```tsx + +``` 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/success-dialog.json b/public/r/success-dialog.json new file mode 100644 index 0000000..5770fa9 --- /dev/null +++ b/public/r/success-dialog.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "success-dialog", + "type": "registry:block", + "title": "Modal shown when a transaction completes successfully.", + "dependencies": [], + "registryDependencies": [ + "dialog", + "button" + ], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/success-dialog.tsx", + "content": "\"use client\";\r\n\r\nimport { CheckCircle, ExternalLink, Copy } from \"lucide-react\";\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogDescription,\r\n DialogFooter,\r\n DialogHeader,\r\n DialogTitle,\r\n} from \"@/components/ui/dialog\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { useState } from \"react\";\r\n\r\ninterface SuccessDialogProps {\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n title?: string;\r\n description?: string;\r\n signature?: string;\r\n amount?: string;\r\n token?: string;\r\n recipient?: string;\r\n onViewExplorer?: () => void;\r\n onClose?: () => void;\r\n}\r\n\r\nexport function SuccessDialog({\r\n open,\r\n onOpenChange,\r\n title = \"Transaction Successful! 🎉\",\r\n description = \"Your transaction has been confirmed on the blockchain\",\r\n signature,\r\n amount,\r\n token,\r\n recipient,\r\n onViewExplorer,\r\n onClose,\r\n}: SuccessDialogProps) {\r\n const [copied, setCopied] = useState(false);\r\n\r\n const copySignature = async () => {\r\n if (!signature) return;\r\n await navigator.clipboard.writeText(signature);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n };\r\n\r\n const handleClose = () => {\r\n onOpenChange(false);\r\n onClose?.();\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n
\r\n \r\n
\r\n \r\n {title}\r\n \r\n \r\n {description}\r\n \r\n
\r\n\r\n
\r\n {(amount || token) && (\r\n
\r\n
\r\n

\r\n {amount} {token}\r\n

\r\n {recipient && (\r\n

\r\n Sent to {recipient.slice(0, 8)}...{recipient.slice(-8)}\r\n

\r\n )}\r\n
\r\n
\r\n )}\r\n\r\n {signature && (\r\n
\r\n
\r\n
\r\n

\r\n Transaction Signature\r\n

\r\n

\r\n {signature.slice(0, 12)}...{signature.slice(-12)}\r\n

\r\n
\r\n
\r\n \r\n \r\n \r\n {onViewExplorer && (\r\n \r\n \r\n \r\n )}\r\n
\r\n
\r\n {copied && (\r\n \r\n Copied!\r\n \r\n )}\r\n
\r\n )}\r\n
\r\n\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/success-dialog.tsx" + } + ] +} \ No newline at end of file diff --git a/registry.json b/registry.json index bac943b..88a0373 100644 --- a/registry.json +++ b/registry.json @@ -1036,6 +1036,23 @@ } ] }, + { + "name": "success-dialog", + "type": "registry:block", + "title": "Modal shown when a transaction completes successfully.", + "registryDependencies": [ + "dialog", + "button" + ], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/success-dialog.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/success-dialog.tsx" + } + ] + }, { "name": "swap-token-form", "type": "registry:block", diff --git a/registry/components/success-dialog.json b/registry/components/success-dialog.json new file mode 100644 index 0000000..1537249 --- /dev/null +++ b/registry/components/success-dialog.json @@ -0,0 +1,14 @@ +{ + "name": "success-dialog", + "type": "registry:block", + "title": "Modal shown when a transaction completes successfully.", + "registryDependencies": ["dialog", "button"], + "dependencies": [], + "files": [ + { + "path": "components/ui/murphy/Txn-Feedback/success-dialog.tsx", + "type": "registry:file", + "target": "components/ui/murphy/Txn-Feedback/success-dialog.tsx" + } + ] +}