/**
* 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) => (
))}
);
}