From db438d1d6feb6edccf782e0e22793cf8e4602cb1 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 16 Jun 2026 00:06:09 -0700 Subject: Add "Payment received!" celebration on the Receive tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a deposit lands on the shown address, the Receive tab now shows a "Payment received!" dialog (with the amount and a green check) and waits for a tap before revealing the next address/QR — instead of silently swapping it. Tracks the acknowledged fresh index so only payments that arrive while watching trigger it. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.js | 30 ++++++++++++++++++++++++++++++ src/style.css | 5 +++++ 2 files changed, 35 insertions(+) (limited to 'src') diff --git a/src/app.js b/src/app.js index a017841..3f24c1c 100644 --- a/src/app.js +++ b/src/app.js @@ -33,6 +33,7 @@ const ui = { unlockError: '', tab: 'receive', // receive | send | history | settings + receiveSeenIndex: null, // fresh receive index the user has acknowledged send: blankSend(), draft: null, // built tx summary awaiting review sendError: '', @@ -411,6 +412,9 @@ async function enterWallet(mnemonic, passphrase) { // a brand-new wallet does one full scan. if (hadCache || hadNostr) await wallet.refreshLive(); else await wallet.scan(); + // Align the acknowledged index after load so we only celebrate payments + // that land while watching (not historical reconciliation). + ui.receiveSeenIndex = wallet.nextReceiveIndex; wallet.startRealtime(); } catch { enterOfflineFallback(); @@ -477,6 +481,7 @@ function lock() { ui.passphrase = ''; ui.confirm = []; ui.revealShown = false; + ui.receiveSeenIndex = null; render(); } @@ -638,6 +643,30 @@ function tabContent() { // ---------------------------------------------------------------- Receive function receiveTab() { + if (ui.receiveSeenIndex == null) ui.receiveSeenIndex = wallet.nextReceiveIndex; + + // A payment landed on the shown address (the fresh index advanced past what + // the user last saw) — celebrate, and wait for a tap before showing the next. + if (wallet.nextReceiveIndex > ui.receiveSeenIndex) { + let amt = 0; + for (let i = ui.receiveSeenIndex; i < wallet.nextReceiveIndex; i++) { + const e = wallet._addrInfo(0, i); + if (e) amt += (e.confirmed || 0) + (e.pending || 0); + } + return h( + 'div', + { + class: 'card col', + style: 'align-items:center;text-align:center;gap:14px;cursor:pointer;padding:48px 20px', + onClick: () => { ui.receiveSeenIndex = wallet.nextReceiveIndex; render(); }, + }, + h('div', { class: 'check-badge' }, '✓'), + h('h2', { style: 'margin:0' }, 'Payment received!'), + amt ? h('div', { class: 'amount-pos', style: 'font-size:18px' }, '+' + fmtAmount(amt) + ' ' + unitLabel()) : null, + h('div', { class: 'small muted' }, 'Tap anywhere to proceed') + ); + } + const fresh = wallet.freshReceive(); return h( 'div', @@ -1044,3 +1073,4 @@ async function importSnapshotFile(e) { // ================================================================ start // Restore a wallet left open in this tab; otherwise show the unlock screen. if (!restoreSession()) render(); + diff --git a/src/style.css b/src/style.css index 160cca2..ed1d542 100644 --- a/src/style.css +++ b/src/style.css @@ -208,6 +208,11 @@ textarea { resize: vertical; min-height: 84px; font-family: var(--mono); font-si .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } .words .w.masked .t { color: var(--faint); letter-spacing: 2px; } +.check-badge { + width: 64px; height: 64px; border-radius: 50%; flex: none; + background: var(--green-soft); color: var(--green); + display: grid; place-items: center; font-size: 34px; font-weight: 700; +} .footer { margin-top: 28px; padding-bottom: 8px; } .footer a { color: var(--accent-ink); font-weight: 600; } -- cgit v1.2.3