diff --git a/src/app/(app)/transactions/page.tsx b/src/app/(app)/transactions/page.tsx index e2fd0db..b45e627 100644 --- a/src/app/(app)/transactions/page.tsx +++ b/src/app/(app)/transactions/page.tsx @@ -30,89 +30,143 @@ import { useState } from 'react'; import { useToast } from '@/hooks/use-toast'; import { Progress } from '@/components/ui/progress'; -const TransactionRow = React.memo(({ tx, fiatPrice, currency }: { tx: Transaction, fiatPrice: number, currency: string }) => { +// Shared per-transaction derived values, computed once per renderer. +function getTxDerived(tx: Transaction, fiatPrice: number, currency: string) { const isReceived = tx.type === 'Received'; - const addressToShow = isReceived - ? tx.fromAddress[0] - : tx.toAddress[0]; + const addressToShow = isReceived ? tx.fromAddress[0] : tx.toAddress[0]; const fiatAmount = Math.abs(tx.btc * fiatPrice); - const formatCurrency = (value: number) => { - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: currency, + const formatCurrency = (value: number) => + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: currency, }).format(value); - } // Ensure unique labels are displayed (e.g., if multiple addresses belong to the same exchange) const uniqueLabels = tx.labels ? [...new Map(tx.labels.map(item => [item.label, item])).values()] : []; + const shortAddress = + addressToShow && addressToShow.length > 10 + ? `${addressToShow.substring(0, 10)}...` + : addressToShow; + + return { isReceived, addressToShow, shortAddress, fiatAmount, formatCurrency, uniqueLabels }; +} + +type TxDerived = ReturnType; + +// Status pill, shared by the desktop table row and the mobile card. +function StatusBadge({ status }: { status: Transaction['status'] }) { + return ( + + {status} + + ); +} + +// Icon + "Sent to/Received from" + truncated address + labels + date. +// Truncation uses min-w-0 + truncate so the address yields width gracefully +// instead of overlapping adjacent content on small screens. +function TxIdentity({ tx, d }: { tx: Transaction; d: TxDerived }) { + return ( +
+ + {d.isReceived ? : } + +
+
+ {d.isReceived ? 'Received from' : 'Sent to'} + {d.shortAddress} + {d.uniqueLabels.map(label => ( + + + {label.label} + + ))} +
+
+ {new Date(tx.date).toLocaleString()} +
+
+
+ ); +} + +// Desktop layout (sm+): a table row. +const TransactionRow = React.memo(({ tx, fiatPrice, currency }: { tx: Transaction, fiatPrice: number, currency: string }) => { + const d = getTxDerived(tx, fiatPrice, currency); + return ( - + -
- - {isReceived ? : } - -
-
- {isReceived ? 'Received from' : 'Sent to'} - {addressToShow && addressToShow.length > 10 ? `${addressToShow.substring(0, 10)}...` : addressToShow} - {uniqueLabels.map(label => ( - - - {label.label} - - ))} -
-
- {new Date(tx.date).toLocaleString()} -
-
-
+
{tx.btc > 0 ? '+' : ''}{tx.btc.toFixed(6)} BTC - - {isReceived ? '+' : '-'}{formatCurrency(fiatAmount)} + {d.isReceived ? '+' : '-'}{d.formatCurrency(d.fiatAmount)} - - - {tx.status} - + +
); }); TransactionRow.displayName = 'TransactionRow'; +// Mobile layout (below sm): a stacked card so columns never overlap. +const TransactionCard = React.memo(({ tx, fiatPrice, currency }: { tx: Transaction, fiatPrice: number, currency: string }) => { + const d = getTxDerived(tx, fiatPrice, currency); + + return ( + + +
+ + {tx.btc > 0 ? '+' : ''}{tx.btc.toFixed(6)} BTC + + +
+ + ); +}); +TransactionCard.displayName = 'TransactionCard'; + const TRANSACTIONS_PER_PAGE = 20; @@ -229,15 +283,31 @@ export default function TransactionsPage() { - -
+ + {/* Mobile: stacked cards so columns never overlap on small screens */} +
+ {transactionsToShow.length > 0 ? ( + transactionsToShow.map((tx) => ( +
+ +
+ )) + ) : ( +

+ No transactions found. +

+ )} +
+ + {/* Desktop (sm+): table layout */} +
- Details + Details Amount (BTC) Amount (Fiat) - Status + Status