/** * Transaction details page. * Auto-refreshes when transaction is unconfirmed to detect confirmation. */ import { useEffect, useState } from 'react'; import { useParams, Link } from 'react-router-dom'; import { ArrowRight, Clock, Hash, Box, Coins, CheckCircle, AlertCircle, Gift, RefreshCw, Radio, } from 'lucide-react'; import { useTransaction } from '../hooks/useApi'; import { useWebSocket } from '../contexts/WebSocketContext'; import CopyButton from '../components/CopyButton'; import TransactionFlowDiagram from '../components/TransactionFlowDiagram'; import ConnectionStatus from '../components/ConnectionStatus'; import { formatDateTime, formatSynor, truncateHash } from '../lib/utils'; const UNCONFIRMED_REFRESH_INTERVAL = 5000; // 5 seconds export default function Transaction() { const { txId } = useParams<{ txId: string }>(); const { data: tx, isLoading, error, refetch } = useTransaction(txId || ''); const { isConnected } = useWebSocket(); const [isRefreshing, setIsRefreshing] = useState(false); const [justConfirmed, setJustConfirmed] = useState(false); // Auto-refresh for unconfirmed transactions useEffect(() => { if (!tx || tx.blockHash) return; // Already confirmed or no data const interval = setInterval(async () => { setIsRefreshing(true); await refetch(); setIsRefreshing(false); }, UNCONFIRMED_REFRESH_INTERVAL); return () => clearInterval(interval); }, [tx, refetch]); // Detect when transaction gets confirmed useEffect(() => { if (tx?.blockHash && !justConfirmed) { // Check if it was previously unconfirmed (first load with blockHash won't trigger) const wasUnconfirmed = sessionStorage.getItem(`tx-${txId}-unconfirmed`); if (wasUnconfirmed === 'true') { setJustConfirmed(true); sessionStorage.removeItem(`tx-${txId}-unconfirmed`); // Auto-dismiss after 5 seconds setTimeout(() => setJustConfirmed(false), 5000); } } else if (tx && !tx.blockHash) { // Mark as unconfirmed for later detection sessionStorage.setItem(`tx-${txId}-unconfirmed`, 'true'); } }, [tx, txId, justConfirmed]); if (!txId) { return
Transaction ID is required
; } if (isLoading) { return ; } if (error) { return (
Error loading transaction: {error.message}
); } if (!tx) { return
Transaction not found
; } const isUnconfirmed = !tx.blockHash; return (
{/* Just confirmed banner */} {justConfirmed && (
Transaction Confirmed!
This transaction has been included in a block.
)} {/* Modern Header */}
{/* Background glow */}

Transaction Details

{tx.isCoinbase && ( Coinbase )} {tx.blockHash ? ( Confirmed ) : ( {isRefreshing ? ( ) : ( )} Unconfirmed )}
{/* Quick stats and status */}
{/* Waiting for confirmation indicator */} {isUnconfirmed && isConnected && (
Waiting for confirmation...
)}
Total Value
{formatSynor(tx.totalOutput, 4)}
{!tx.isCoinbase && tx.fee > 0 && (
Fee
{formatSynor(tx.fee, 4)}
)}
{/* Unconfirmed notice */} {isUnconfirmed && (
Pending Confirmation
This transaction is in the mempool and waiting to be included in a block. {isConnected && ' Page will auto-update when confirmed.'}
{isRefreshing && ( )}
)} {/* Transaction Flow Diagram */} {/* Transaction Info */}

Transaction Information

{tx.id}
{tx.blockHash && ( {truncateHash(tx.blockHash, 12, 12)} )} {tx.blockTime && (
{formatDateTime(tx.blockTime)}
)} {formatSynor(tx.totalOutput)} {!tx.isCoinbase && ( {formatSynor(tx.fee)} )} {tx.mass.toLocaleString()} {tx.version}
{/* Inputs & Outputs */}
{/* Inputs */}

Inputs ({tx.inputs.length})

{tx.isCoinbase ? (
Coinbase (Block Reward)
) : ( tx.inputs.map((input, i) => (
{input.address ? ( {truncateHash(input.address, 12, 12)} ) : ( Unknown )} {input.value !== undefined && ( -{formatSynor(input.value, 4)} )}
{truncateHash(input.previousTxId)}:{input.previousIndex}
)) )}
{/* Outputs */}

Outputs ({tx.outputs.length})

{tx.outputs.map((output, i) => (
{output.address ? ( {truncateHash(output.address, 12, 12)} ) : ( {output.scriptType} )} +{formatSynor(output.value, 4)}
))}
); } function InfoRow({ label, children }: { label: string; children: React.ReactNode }) { return (
{label} {children}
); } function TransactionSkeleton() { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
); }