From 05cbb6317c4639b2fa0e20cd2db4196de6b38680 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 11:35:51 +0000 Subject: [PATCH 1/2] fix(transactions): prevent overlapping columns in mobile view (BIT-137) On narrow screens the transaction history table squeezed its rigid columns (whitespace-nowrap amounts/status, default p-4 padding, px-0 content, max-w-[80px] address cap) until content overlapped before the horizontal scrollbar engaged. Replace the cramped table-on-mobile with a responsive layout: - Below sm, render each transaction as a stacked card (details on top, amount + status on a second row), eliminating sideways scroll. - Keep the existing table for sm+ (Fiat column still shows at md+). - Extract shared logic/markup (getTxDerived, TxIdentity, StatusBadge) so the row and card stay DRY; fix address truncation with min-w-0 + truncate instead of a fixed-width cap. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q4xBbTDDCCxzmFGzGpo1PC --- src/app/(app)/transactions/page.tsx | 181 +++++++++++++++++++--------- 1 file changed, 125 insertions(+), 56 deletions(-) diff --git a/src/app/(app)/transactions/page.tsx b/src/app/(app)/transactions/page.tsx index e2fd0db..9dc0cd9 100644 --- a/src/app/(app)/transactions/page.tsx +++ b/src/app/(app)/transactions/page.tsx @@ -30,89 +30,144 @@ 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 +284,29 @@ 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 From 3af6899512dbc4f8f33c56e63a8a48872243ba56 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 11:47:36 +0000 Subject: [PATCH 2/2] fix(transactions): preserve link semantics on mobile transaction cards Code review follow-up: role="listitem" was set directly on the card's (an ), which overrides the anchor's implicit "link" role so screen readers no longer announce each card as a link. Move role="listitem" to a wrapper div so the list semantics and the link semantics both survive. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q4xBbTDDCCxzmFGzGpo1PC --- src/app/(app)/transactions/page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/(app)/transactions/page.tsx b/src/app/(app)/transactions/page.tsx index 9dc0cd9..b45e627 100644 --- a/src/app/(app)/transactions/page.tsx +++ b/src/app/(app)/transactions/page.tsx @@ -148,7 +148,6 @@ const TransactionCard = React.memo(({ tx, fiatPrice, currency }: { tx: Transacti return ( @@ -289,7 +288,9 @@ export default function TransactionsPage() {
{transactionsToShow.length > 0 ? ( transactionsToShow.map((tx) => ( - +
+ +
)) ) : (