From 0491ebfcd8d91ef4ca0763deb04b13a9181cc466 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 10:21:39 +0000 Subject: [PATCH] fix: improve Cashu wallet transaction display and relays - Subscribe to WalletHistory.meta$ observable for actual amounts and direction - Create HistoryEntryRow component that properly shows +/- amounts - Show effective relays (wallet + user outbox) instead of just wallet relays - Remove settings dropdown (feature deferred) - Add colored amount display (+green for in, -red for out) - Show mint in transaction detail dialog when available --- src/components/Nip61WalletViewer.tsx | 410 ++++++++++++++------------- src/hooks/useNip61Wallet.ts | 18 +- 2 files changed, 219 insertions(+), 209 deletions(-) diff --git a/src/components/Nip61WalletViewer.tsx b/src/components/Nip61WalletViewer.tsx index cb2023e..2d5f70b 100644 --- a/src/components/Nip61WalletViewer.tsx +++ b/src/components/Nip61WalletViewer.tsx @@ -13,7 +13,6 @@ import { Send, Download, RefreshCw, - Settings, Coins, Loader2, Wallet, @@ -21,11 +20,13 @@ import { ChevronRight, ChevronDown, ArrowDownLeft, + ArrowUpRight, Search, Landmark, Radio, } from "lucide-react"; import { toast } from "sonner"; +import { use$ } from "applesauce-react/hooks"; import { Button } from "@/components/ui/button"; import { Tooltip, @@ -46,12 +47,10 @@ import { DialogFooter, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; import { WalletBalance, WalletHeader, WalletHistoryList, - TransactionRow, type HistoryItem, } from "@/components/wallet"; import { CodeCopyButton } from "@/components/CodeCopyButton"; @@ -206,6 +205,57 @@ function historyToItem(entry: WalletHistory): HistoryItem { }; } +/** + * History entry row that subscribes to meta$ for amounts + */ +function HistoryEntryRow({ + entry, + blurred, + onClick, +}: { + entry: WalletHistory; + blurred: boolean; + onClick: () => void; +}) { + // Subscribe to meta$ observable to get direction and amount + const meta = use$(() => entry.meta$, [entry]); + + const direction = meta?.direction || "in"; + const amount = meta?.amount || 0; + + return ( +
+
+ {direction === "in" ? ( + + ) : ( + + )} +
+ + {formatTimestamp(entry.event.created_at, "datetime")} + + {!entry.unlocked && ( + (locked) + )} +
+
+
+

+ {blurred + ? "✦✦✦✦" + : `${direction === "in" ? "+" : "-"}${amount.toLocaleString()}`} +

+
+
+ ); +} + export default function Nip61WalletViewer() { const { state, toggleWalletBalancesBlur } = useGrimoire(); const { isLoggedIn } = useAccount(); @@ -221,8 +271,6 @@ export default function Nip61WalletViewer() { unlock, unlocking, error, - syncEnabled, - toggleSyncEnabled, } = useNip61Wallet(); const [refreshing, setRefreshing] = useState(false); @@ -231,7 +279,6 @@ export default function Nip61WalletViewer() { const [detailDialogOpen, setDetailDialogOpen] = useState(false); const [showRawTransaction, setShowRawTransaction] = useState(false); const [copiedRawTx, setCopiedRawTx] = useState(false); - const [settingsOpen, setSettingsOpen] = useState(false); const blurred = state.walletBalancesBlurred ?? false; @@ -265,34 +312,11 @@ export default function Nip61WalletViewer() { const renderHistoryEntry = useCallback( (item: HistoryItem) => { const entry = item.data as WalletHistory; - - // If not unlocked, show placeholder - if (!entry.unlocked) { - return ( - Locked - } - onClick={() => handleTransactionClick(entry)} - /> - ); - } - return ( - - {formatTimestamp(entry.event.created_at, "datetime")} - - } onClick={() => handleTransactionClick(entry)} /> ); @@ -424,19 +448,6 @@ export default function Nip61WalletViewer() { )} - - - - - - Settings - } /> @@ -498,7 +509,8 @@ export default function Nip61WalletViewer() { {/* Transaction Detail Dialog */} - { setDetailDialogOpen(open); @@ -507,159 +519,161 @@ export default function Nip61WalletViewer() { setCopiedRawTx(false); } }} - > - - - Transaction Details - - -
- {selectedTransaction && ( -
-
- -
-

Transaction

-

- {formatTimestamp( - selectedTransaction.event.created_at, - "datetime", - )} -

-
-
- -
-
- -

- {selectedTransaction.event.id} -

-
- -
- -

- {selectedTransaction.unlocked ? "Unlocked" : "Locked"} -

-
- -
- -

- {formatTimestamp( - selectedTransaction.event.created_at, - "absolute", - )} -

-
-
- - {/* Raw Transaction (expandable) */} -
- - - {showRawTransaction && ( -
-
-
-                          {JSON.stringify(selectedTransaction.event, null, 2)}
-                        
- { - navigator.clipboard.writeText( - JSON.stringify( - selectedTransaction.event, - null, - 2, - ), - ); - setCopiedRawTx(true); - setTimeout(() => setCopiedRawTx(false), 2000); - }} - label="Copy event JSON" - /> -
-
- )} -
-
- )} -
- - - - -
-
- - {/* Settings Dialog */} - - - - Wallet Settings - - -
-
-
- -

- Keep wallet unlocked and sync history automatically -

-
- -
- -
-
- -

- Hide balance amounts for privacy -

-
- -
-
- - - - -
-
+ showRaw={showRawTransaction} + onToggleRaw={() => setShowRawTransaction(!showRawTransaction)} + copiedRaw={copiedRawTx} + onCopyRaw={() => { + if (selectedTransaction) { + navigator.clipboard.writeText( + JSON.stringify(selectedTransaction.event, null, 2), + ); + setCopiedRawTx(true); + setTimeout(() => setCopiedRawTx(false), 2000); + } + }} + blurred={blurred} + /> ); } + +/** + * Transaction detail dialog component + */ +function TransactionDetailDialog({ + transaction, + open, + onOpenChange, + showRaw, + onToggleRaw, + copiedRaw, + onCopyRaw, + blurred, +}: { + transaction: WalletHistory | null; + open: boolean; + onOpenChange: (open: boolean) => void; + showRaw: boolean; + onToggleRaw: () => void; + copiedRaw: boolean; + onCopyRaw: () => void; + blurred: boolean; +}) { + // Subscribe to meta$ for transaction details + const meta = use$(() => transaction?.meta$, [transaction]); + + const direction = meta?.direction || "in"; + const amount = meta?.amount || 0; + + return ( + + + + Transaction Details + + +
+ {transaction && ( +
+
+ {direction === "in" ? ( + + ) : ( + + )} +
+

+ {direction === "in" ? "Received" : "Sent"} +

+

+ {blurred + ? "✦✦✦✦✦✦" + : `${direction === "in" ? "+" : "-"}${amount.toLocaleString()}`} +

+
+
+ +
+
+ +

+ {transaction.event.id} +

+
+ + {meta?.mint && ( +
+ +

+ {new URL(meta.mint).hostname} +

+
+ )} + +
+ +

+ {transaction.unlocked ? "Unlocked" : "Locked"} +

+
+ +
+ +

+ {formatTimestamp(transaction.event.created_at, "absolute")} +

+
+
+ + {/* Raw Transaction (expandable) */} +
+ + + {showRaw && ( +
+
+
+                        {JSON.stringify(transaction.event, null, 2)}
+                      
+ +
+
+ )} +
+
+ )} +
+ + + + +
+
+ ); +} diff --git a/src/hooks/useNip61Wallet.ts b/src/hooks/useNip61Wallet.ts index a1af725..de8f51a 100644 --- a/src/hooks/useNip61Wallet.ts +++ b/src/hooks/useNip61Wallet.ts @@ -24,7 +24,6 @@ import { WALLET_HISTORY_KIND, } from "@/services/nip61-wallet"; import { kinds, relaySet } from "applesauce-core/helpers"; -import { useGrimoire } from "@/core/state"; // Import casts to enable user.wallet$ property import "applesauce-wallet/casts"; @@ -44,7 +43,6 @@ export type WalletState = */ export function useNip61Wallet() { const { pubkey, canSign } = useAccount(); - const { state: appState, setCashuWalletSyncEnabled } = useGrimoire(); const [unlocking, setUnlocking] = useState(false); const [error, setError] = useState(null); const [discoveryComplete, setDiscoveryComplete] = useState(false); @@ -190,10 +188,12 @@ export function useNip61Wallet() { return Object.fromEntries(entries); }, [balance]); - // Toggle sync setting - const toggleSyncEnabled = useCallback(() => { - setCashuWalletSyncEnabled(!appState.cashuWalletSyncEnabled); - }, [appState.cashuWalletSyncEnabled, setCashuWalletSyncEnabled]); + // Compute effective relays (wallet relays + user outbox relays) + const effectiveRelays = useMemo(() => { + const walletRelays = relays || []; + const userOutboxes = outboxes || []; + return relaySet(walletRelays, userOutboxes); + }, [relays, outboxes]); return { // State machine @@ -214,17 +214,13 @@ export function useNip61Wallet() { tokens, history, mints, - relays, + relays: effectiveRelays, // Combined wallet + outbox relays received, // Received nutzap IDs // Actions unlock, unlocking, - // Sync setting - syncEnabled: appState.cashuWalletSyncEnabled ?? false, - toggleSyncEnabled, - // Error state error,