From 57fbe5cdd3e8057f58fe761b320f358e77d9733f Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 18 Jan 2026 22:00:08 +0000
Subject: [PATCH] feat: add tap-to-blur privacy feature for wallet balances
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Implement privacy toggle for wallet balances and transaction amounts.
Tapping any balance display toggles a global blur effect across all
wallet UIs. Persisted to localStorage for consistent privacy.
- Add walletBalancesBlurred state to GrimoireState
- Add toggleWalletBalancesBlur pure function in core/logic
- Make big balance in WalletViewer clickable with eye icon indicator
- Apply blur to all transaction amounts in list and detail views
- Add blur to send/receive dialog amounts
- Make balance in user menu wallet info clickable with eye icon
- Apply blur to balance in dropdown menu item
UX matches common financial app pattern: tap balance → blur on/off
---
src/components/WalletViewer.tsx | 45 ++++++++++++++++++++++++------
src/components/nostr/user-menu.tsx | 37 ++++++++++++++++++++----
src/core/logic.ts | 9 ++++++
src/core/state.ts | 5 ++++
src/types/app.ts | 1 +
5 files changed, 82 insertions(+), 15 deletions(-)
diff --git a/src/components/WalletViewer.tsx b/src/components/WalletViewer.tsx
index 7034f14..2d37ad2 100644
--- a/src/components/WalletViewer.tsx
+++ b/src/components/WalletViewer.tsx
@@ -20,6 +20,8 @@ import {
LogOut,
ChevronDown,
ChevronRight,
+ Eye,
+ EyeOff,
} from "lucide-react";
import { Virtuoso } from "react-virtuoso";
import { useWallet } from "@/hooks/useWallet";
@@ -341,7 +343,11 @@ function TransactionLabel({ transaction }: { transaction: Transaction }) {
}
export default function WalletViewer() {
- const { state, disconnectNWC: disconnectNWCFromState } = useGrimoire();
+ const {
+ state,
+ disconnectNWC: disconnectNWCFromState,
+ toggleWalletBalancesBlur,
+ } = useGrimoire();
const {
wallet,
balance,
@@ -1053,9 +1059,20 @@ export default function WalletViewer() {
{/* Big Centered Balance */}
-
- {formatSats(balance)}
-
+
{/* Send / Receive Buttons */}
@@ -1144,7 +1161,9 @@ export default function WalletViewer() {
-
+
{formatSats(tx.amount)}
@@ -1232,7 +1251,9 @@ export default function WalletViewer() {
? "Received"
: "Sent"}
-
+
{formatSats(selectedTransaction.amount)} sats
@@ -1266,7 +1287,9 @@ export default function WalletViewer() {
-
+
{formatSats(selectedTransaction.fees_paid)} sats
@@ -1423,7 +1446,9 @@ export default function WalletViewer() {
{invoiceDetails?.amount && !sendAmount && (
Amount:
-
+
{Math.floor(invoiceDetails.amount).toLocaleString()}{" "}
sats
@@ -1432,7 +1457,9 @@ export default function WalletViewer() {
{sendAmount && (
Amount:
-
+
{parseInt(sendAmount).toLocaleString()} sats
diff --git a/src/components/nostr/user-menu.tsx b/src/components/nostr/user-menu.tsx
index 3b04c00..ca93faf 100644
--- a/src/components/nostr/user-menu.tsx
+++ b/src/components/nostr/user-menu.tsx
@@ -1,4 +1,13 @@
-import { User, HardDrive, Palette, Wallet, X, RefreshCw } from "lucide-react";
+import {
+ User,
+ HardDrive,
+ Palette,
+ Wallet,
+ X,
+ RefreshCw,
+ Eye,
+ EyeOff,
+} from "lucide-react";
import accounts from "@/services/accounts";
import { useProfile } from "@/hooks/useProfile";
import { use$ } from "applesauce-react/hooks";
@@ -66,7 +75,8 @@ function UserLabel({ pubkey }: { pubkey: string }) {
export default function UserMenu() {
const account = use$(accounts.active$);
- const { state, addWindow, disconnectNWC } = useGrimoire();
+ const { state, addWindow, disconnectNWC, toggleWalletBalancesBlur } =
+ useGrimoire();
const relays = state.activeAccount?.relays;
const blossomServers = state.activeAccount?.blossomServers;
const nwcConnection = state.nwcConnection;
@@ -182,9 +192,22 @@ export default function UserMenu() {
Balance:
-
- {formatBalance(balance ?? nwcConnection.balance)}
-
+
{balance !== undefined ||
nwcConnection.balance !== undefined ? (
-
+
{formatBalance(balance ?? nwcConnection.balance)}
) : null}
diff --git a/src/core/logic.ts b/src/core/logic.ts
index 328d004..07527c1 100644
--- a/src/core/logic.ts
+++ b/src/core/logic.ts
@@ -593,3 +593,12 @@ export const disconnectNWC = (state: GrimoireState): GrimoireState => {
nwcConnection: undefined,
};
};
+
+export const toggleWalletBalancesBlur = (
+ state: GrimoireState,
+): GrimoireState => {
+ return {
+ ...state,
+ walletBalancesBlurred: !state.walletBalancesBlurred,
+ };
+};
diff --git a/src/core/state.ts b/src/core/state.ts
index 6c84040..98f8618 100644
--- a/src/core/state.ts
+++ b/src/core/state.ts
@@ -370,6 +370,10 @@ export const useGrimoire = () => {
setState((prev) => Logic.disconnectNWC(prev));
}, [setState]);
+ const toggleWalletBalancesBlur = useCallback(() => {
+ setState((prev) => Logic.toggleWalletBalancesBlur(prev));
+ }, [setState]);
+
return {
state,
isTemporary,
@@ -400,5 +404,6 @@ export const useGrimoire = () => {
updateNWCBalance,
updateNWCInfo,
disconnectNWC,
+ toggleWalletBalancesBlur,
};
};
diff --git a/src/types/app.ts b/src/types/app.ts
index 681b678..bc924c3 100644
--- a/src/types/app.ts
+++ b/src/types/app.ts
@@ -136,4 +136,5 @@ export interface GrimoireState {
isPublished?: boolean; // Whether it has been published to Nostr
};
nwcConnection?: NWCConnection;
+ walletBalancesBlurred?: boolean; // Privacy: blur balances and transaction amounts
}