diff --git a/src/components/common/CopySuccessAnnouncement.tsx b/src/components/common/CopySuccessAnnouncement.tsx new file mode 100644 index 0000000..ada4ba2 --- /dev/null +++ b/src/components/common/CopySuccessAnnouncement.tsx @@ -0,0 +1,18 @@ +interface CopySuccessAnnouncementProps { + message: string; +} + +function CopySuccessAnnouncement({ message }: CopySuccessAnnouncementProps) { + return ( + + {message} + + ); +} + +export default CopySuccessAnnouncement; diff --git a/src/components/common/CreatorProfileHeader.tsx b/src/components/common/CreatorProfileHeader.tsx index 886795c..6bbf0cd 100644 --- a/src/components/common/CreatorProfileHeader.tsx +++ b/src/components/common/CreatorProfileHeader.tsx @@ -148,4 +148,4 @@ const CreatorProfileHeader: React.FC = ({ ); }; -export default CreatorProfileHeader; +export default CreatorProfileHeader; \ No newline at end of file diff --git a/src/components/common/EmptyTransactionTimelineState.tsx b/src/components/common/EmptyTransactionTimelineState.tsx index 5d7e635..88f4c36 100644 --- a/src/components/common/EmptyTransactionTimelineState.tsx +++ b/src/components/common/EmptyTransactionTimelineState.tsx @@ -2,6 +2,8 @@ import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Check, Clock3, Copy, XCircle } from 'lucide-react'; import { formatRecentActivityCompactTimestamp } from '@/utils/recentActivityTimestamp.utils'; +import CopySuccessAnnouncement from '@/components/common/CopySuccessAnnouncement'; +import { useCopySuccessAnnouncement } from '@/hooks/useCopySuccessAnnouncement'; type CopyState = 'idle' | 'success' | 'error'; @@ -19,7 +21,8 @@ const DEFAULT_TIMELINE_ENTRIES: TimelineEntry[] = [ id: 'entry-1', action: 'Buy', amount: '+2 keys', - txHash: '0x2a43bcfdef77ca4c50ef7d38148dd5d7f0149a6e2e20f70f04ce1f4b66fe55dd', + txHash: + '0x2a43bcfdef77ca4c50ef7d38148dd5d7f0149a6e2e20f70f04ce1f4b66fe55dd', compactTimestamp: '2m ago', status: 'confirmed', }, @@ -27,7 +30,8 @@ const DEFAULT_TIMELINE_ENTRIES: TimelineEntry[] = [ id: 'entry-2', action: 'Sell', amount: '-1 key', - txHash: '0x90c82ac01478b42fcbf9db73a26ed32bd8e50a8917e2408c31c95e9f6a59fc19', + txHash: + '0x90c82ac01478b42fcbf9db73a26ed32bd8e50a8917e2408c31c95e9f6a59fc19', compactTimestamp: '18m ago', status: 'pending', }, @@ -35,23 +39,28 @@ const DEFAULT_TIMELINE_ENTRIES: TimelineEntry[] = [ id: 'entry-3', action: 'Buy', amount: '+3 keys', - txHash: '0x16d2ffbc4297a8c2c3086e07c16e66f47287df0d5a1ce1aef9e448e2f0f3ab51', + txHash: + '0x16d2ffbc4297a8c2c3086e07c16e66f47287df0d5a1ce1aef9e448e2f0f3ab51', compactTimestamp: '51m ago', status: 'failed', }, ]; -const shortenTxHash = (hash: string) => `${hash.slice(0, 8)}...${hash.slice(-6)}`; +const shortenTxHash = (hash: string) => + `${hash.slice(0, 8)}...${hash.slice(-6)}`; interface EmptyTransactionTimelineStateProps { /** Optional transaction data. If provided and empty, the component returns null. */ data?: TimelineEntry[]; } -const EmptyTransactionTimelineState: React.FC = ({ - data = DEFAULT_TIMELINE_ENTRIES, -}) => { - const [copyStateById, setCopyStateById] = useState>({}); +const EmptyTransactionTimelineState: React.FC< + EmptyTransactionTimelineStateProps +> = ({ data = DEFAULT_TIMELINE_ENTRIES }) => { + const [copyStateById, setCopyStateById] = useState< + Record + >({}); + const { announcement, announceCopySuccess } = useCopySuccessAnnouncement(); if (!data || data.length === 0) { return null; @@ -60,6 +69,7 @@ const EmptyTransactionTimelineState: React.FC { try { await navigator.clipboard.writeText(txHash); + announceCopySuccess('Transaction hash copied.'); setCopyStateById(current => ({ ...current, [entryId]: 'success' })); } catch { setCopyStateById(current => ({ ...current, [entryId]: 'error' })); @@ -105,7 +115,9 @@ const EmptyTransactionTimelineState: React.FC {entry.action} - {entry.amount} + + {entry.amount} +
@@ -161,6 +171,8 @@ const EmptyTransactionTimelineState: React.FC + +
diff --git a/src/components/common/TransactionFailureDrawer.tsx b/src/components/common/TransactionFailureDrawer.tsx index 21c63ec..6ccce35 100644 --- a/src/components/common/TransactionFailureDrawer.tsx +++ b/src/components/common/TransactionFailureDrawer.tsx @@ -11,6 +11,11 @@ import { Button } from '@/components/ui/button'; import { AlertCircle, Copy, Check } from 'lucide-react'; import showToast from '@/utils/toast.util'; import { formatTimestampTooltip } from '@/utils/time.utils'; +import CopySuccessAnnouncement from '@/components/common/CopySuccessAnnouncement'; +import { + COPY_SUCCESS_TOAST_ARIA_PROPS, + useCopySuccessAnnouncement, +} from '@/hooks/useCopySuccessAnnouncement'; export interface TransactionFailureDetails { txHash?: string; @@ -38,6 +43,7 @@ const TransactionFailureDrawer: React.FC = ({ const [copiedField, setCopiedField] = useState< 'errorCode' | 'txHash' | null >(null); + const { announcement, announceCopySuccess } = useCopySuccessAnnouncement(); const copyToClipboard = async ( text: string, @@ -45,7 +51,14 @@ const TransactionFailureDrawer: React.FC = ({ ) => { try { await navigator.clipboard.writeText(text); - showToast.success('Copied to clipboard'); + showToast.success('Copied to clipboard', { + ariaProps: COPY_SUCCESS_TOAST_ARIA_PROPS, + }); + announceCopySuccess( + field === 'errorCode' + ? 'Error code copied.' + : 'Transaction hash copied.' + ); setCopiedField(field); window.setTimeout(() => setCopiedField(null), 2000); } catch { @@ -148,16 +161,6 @@ const TransactionFailureDrawer: React.FC = ({ )}
- - {copiedField === 'errorCode' - ? 'Error code copied to clipboard' - : ''} -
)} @@ -193,19 +196,11 @@ const TransactionFailureDrawer: React.FC = ({ )} - - {copiedField === 'txHash' - ? 'Transaction hash copied to clipboard' - : ''} - )} + + {failureDetails.developerDetails && Object.keys(failureDetails.developerDetails).length > 0 && (
diff --git a/src/components/common/TransactionHashRow.tsx b/src/components/common/TransactionHashRow.tsx index c4d67c0..275dad7 100644 --- a/src/components/common/TransactionHashRow.tsx +++ b/src/components/common/TransactionHashRow.tsx @@ -2,6 +2,8 @@ import React, { useState } from 'react'; import { Copy, Check, ExternalLink } from 'lucide-react'; import { cn } from '@/lib/utils'; import { shortenAddress } from '@/lib/web3/format'; +import CopySuccessAnnouncement from '@/components/common/CopySuccessAnnouncement'; +import { useCopySuccessAnnouncement } from '@/hooks/useCopySuccessAnnouncement'; interface TransactionHashRowProps { hash: string; @@ -17,10 +19,12 @@ const TransactionHashRow: React.FC = ({ className, }) => { const [copied, setCopied] = useState(false); + const { announcement, announceCopySuccess } = useCopySuccessAnnouncement(); const handleCopy = async (e: React.MouseEvent) => { e.stopPropagation(); await navigator.clipboard.writeText(hash); + announceCopySuccess('Transaction hash copied.'); setCopied(true); setTimeout(() => setCopied(false), 2000); }; @@ -46,22 +50,22 @@ const TransactionHashRow: React.FC = ({ - - {copied ? 'Transaction hash copied to clipboard' : ''} - + {explorerUrl && ( = ({ className, }) => { const [copied, setCopied] = useState(false); + const { announcement, announceCopySuccess } = useCopySuccessAnnouncement(); const handleCopy = async () => { await navigator.clipboard.writeText(address); + announceCopySuccess('Address copied.'); setCopied(true); setTimeout(() => setCopied(false), 2000); }; @@ -54,14 +58,7 @@ const TruncatedAddress: React.FC = ({