From 30274eaf7fd0fe258950813cc698e370006f0778 Mon Sep 17 00:00:00 2001 From: Van Minh Date: Thu, 4 Sep 2025 23:35:49 +0700 Subject: [PATCH] feat: add pending transaction indicator component and documentation --- components/txn-pending-indication-preview.tsx | 180 ++++++++++++++++ .../Txn-Feedback/txn-pending-indicator.tsx | 120 +++++++++++ components/ui/murphy/index.tsx | 6 +- .../Txn-Feedback/txn-pending-indicator.mdx | 199 ++++++++++++++++++ content/docs/onchainkit/meta.json | 3 +- 5 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 components/txn-pending-indication-preview.tsx create mode 100644 components/ui/murphy/Txn-Feedback/txn-pending-indicator.tsx create mode 100644 content/docs/onchainkit/Txn-Feedback/txn-pending-indicator.mdx diff --git a/components/txn-pending-indication-preview.tsx b/components/txn-pending-indication-preview.tsx new file mode 100644 index 0000000..75f47cd --- /dev/null +++ b/components/txn-pending-indication-preview.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Plus, X } from "lucide-react"; +import { TxnPendingIndicator } from "./ui/murphy/Txn-Feedback/txn-pending-indicator"; + +interface PendingTransaction { + id: string; + signature?: string; + description: string; + startTime: number; +} + +export default function TxnPendingIndicatorPreview() { + const [pendingTransactions, setPendingTransactions] = useState< + PendingTransaction[] + >([]); + const [position, setPosition] = useState< + "top-left" | "top-right" | "bottom-left" | "bottom-right" + >("bottom-right"); + + const addTransaction = (description: string) => { + const newTransaction: PendingTransaction = { + id: Date.now().toString(), + description, + startTime: Date.now(), + }; + setPendingTransactions((prev) => [...prev, newTransaction]); + + setTimeout(() => { + setPendingTransactions((prev) => + prev.filter((txn) => txn.id !== newTransaction.id) + ); + }, 15000); + }; + + const removeTransaction = (id: string) => { + setPendingTransactions((prev) => prev.filter((txn) => txn.id !== id)); + }; + + const clearAllTransactions = () => { + setPendingTransactions([]); + }; + + const addBatchTransactions = () => { + const batchTransactions = [ + "Transfer to Alice", + "Transfer to Bob", + "Transfer to Charlie", + ].map((desc, index) => ({ + id: `batch_${Date.now()}_${index}`, + description: desc, + startTime: Date.now(), + })); + + setPendingTransactions((prev) => [...prev, ...batchTransactions]); + + setTimeout(() => { + batchTransactions.forEach((txn) => { + setPendingTransactions((prev) => prev.filter((t) => t.id !== txn.id)); + }); + }, 20000); + }; + + return ( +
+
+ + + + Example Usage + + + Add transactions to see the pending indicator appear. It will show + in the {position} corner. + + + +
+ + + +
+ +
+ + +
+ +
+ + Current pending: + + {pendingTransactions.length} +
+
+
+ + + + + Position Options + + + Change the position of the pending indicator on screen + + + +
+ {( + [ + "top-left", + "top-right", + "bottom-left", + "bottom-right", + ] as const + ).map((pos) => ( + + ))} +
+
+
+
+ + +
+ ); +} diff --git a/components/ui/murphy/Txn-Feedback/txn-pending-indicator.tsx b/components/ui/murphy/Txn-Feedback/txn-pending-indicator.tsx new file mode 100644 index 0000000..2691f41 --- /dev/null +++ b/components/ui/murphy/Txn-Feedback/txn-pending-indicator.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Loader2, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { cn } from "@/lib/utils"; + +interface PendingTransaction { + id: string; + signature?: string; + description: string; + startTime: number; +} + +interface TxnPendingIndicatorProps { + transactions: PendingTransaction[]; + onCancel?: (id: string) => void; + position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; + className?: string; +} + +export function TxnPendingIndicator({ + transactions, + onCancel, + position = "bottom-right", + className, +}: TxnPendingIndicatorProps) { + const [isExpanded, setIsExpanded] = useState(false); + + useEffect(() => { + if (transactions.length === 0) { + setIsExpanded(false); + } + }, [transactions.length]); + + if (transactions.length === 0) return null; + + const positionClasses = { + "top-left": "top-4 left-4", + "top-right": "top-4 right-4", + "bottom-left": "bottom-4 left-4", + "bottom-right": "bottom-4 right-4", + }; + + const getElapsedTime = (startTime: number) => { + const elapsed = Math.floor((Date.now() - startTime) / 1000); + if (elapsed < 60) return `${elapsed}s`; + return `${Math.floor(elapsed / 60)}m ${elapsed % 60}s`; + }; + + return ( +
+ {!isExpanded ? ( + + ) : ( +
+
+

Pending Transactions

+ +
+ +
+ {transactions.map((tx) => ( +
+
+ +
+

+ {tx.description} +

+

+ {getElapsedTime(tx.startTime)} +

+
+
+ {onCancel && ( + + )} +
+ ))} +
+
+ )} +
+ ); +} diff --git a/components/ui/murphy/index.tsx b/components/ui/murphy/index.tsx index 987b191..4678eb0 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 { TxnPendingIndicator } from "./Txn-Feedback/txn-pending-indicator"; export { ConnectWalletButton, @@ -78,5 +79,6 @@ export { TMLaunchpadForm, HydraFanoutForm, MPLHybridForm, - TokenMetadataViewer + TokenMetadataViewer, + TxnPendingIndicator, }; diff --git a/content/docs/onchainkit/Txn-Feedback/txn-pending-indicator.mdx b/content/docs/onchainkit/Txn-Feedback/txn-pending-indicator.mdx new file mode 100644 index 0000000..fd8d37a --- /dev/null +++ b/content/docs/onchainkit/Txn-Feedback/txn-pending-indicator.mdx @@ -0,0 +1,199 @@ +--- +title: Pending Transactions Indicator +description: Global indicator showing pending transactions with elapsed time +icon: Clock +--- + +import TxnPendingIndicatorPreview from "@/components/txn-pending-indication-preview"; + + + + + +## Installation + + + + + Install Pending Transactions Indicator + + + + + +## Basic Usage + +```tsx +"use client"; + +import { useState } from "react"; +import { TxnPendingIndicator } from "@/components/ui/murphy/Txn-Feedback/txn-pending-indicator"; +import { Button } from "@/components/ui/button"; + +interface PendingTransaction { + id: string; + description: string; + startTime: number; +} + +export default function MyPage() { + const [pendingTxns, setPendingTxns] = useState([]); + + const addTxn = () => { + const id = Date.now().toString(); + const newTxn: PendingTransaction = { + id, + description: "Simulated Transaction", + startTime: Date.now(), + }; + + setPendingTxns((prev) => [...prev, newTxn]); + }; + + const cancelTxn = (id: string) => { + setPendingTxns((prev) => prev.filter((t) => t.id !== id)); + }; + + return ( +
+ + + +
+ ); +} +``` + +## Props + + void", + default: "undefined", + }, + position: { + description: "Position of the indicator on screen", + type: "'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'", + default: "'bottom-right'", + }, + className: { + description: "Additional CSS classes", + type: "string", + default: "undefined", + }, + }} +/> + +### PendingTransaction Interface + + + +## Indicator Features + +### Collapsible Interface + +- Collapsed: Shows count badge with pending transactions +- Expanded: Shows detailed list of all pending transactions +- Click to toggle between states + +### Elapsed Time Tracking + +- Real-time elapsed time display +- Format: seconds (30s), minutes (2m 30s) +- Updates automatically every second + +### Individual Transaction Management + +- Cancel individual transactions +- Transaction descriptions +- Unique transaction IDs + +### Positioning Options + +- **top-left**: Upper left corner +- **top-right**: Upper right corner +- **bottom-left**: Lower left corner +- **bottom-right**: Lower right corner (default) + +### Auto-hide Behavior + +- Automatically hides when no pending transactions +- Smooth fade in/out animations +- Responsive design for mobile devices + +## Customization + +### Custom Positioning + +```tsx + +``` + +### Custom Styling + +```tsx + +``` + +### Custom Transaction Display + +```tsx +// Extend the component for custom transaction rendering +const CustomPendingIndicator = ({ transactions, onCancel }) => { + return ( + ({ + ...txn, + description: `🔄 ${txn.description} (${txn.type})`, + }))} + onCancel={onCancel} + /> + ); +}; +``` 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" ] }