From b78b9a1c7e4e42b7f673b0e3f92424a762bd0986 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 24 Jun 2026 13:53:29 -0700 Subject: Offer passive reclaim vs immediate revoke for gifts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reserved-coin button is now "Reclaim". Tapping it prompts a choice: - Reclaim (free): just un-reserves the coin so a future payment can spend it — no fee, but the link stays claimable until then. - Revoke now (pays a fee): sweeps the coin back immediately, killing the link once it confirms. New/updated strings translated across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'src/app.js') diff --git a/src/app.js b/src/app.js index b112f02..0ef0787 100644 --- a/src/app.js +++ b/src/app.js @@ -755,18 +755,19 @@ function giftCard() { const label = u ? fmtAmount(u.value) + ' ' + unitLabel() : id.slice(0, 12) + '…'; if (ui.revokeId === id) { return h('div', { class: 'col gap6' }, - h('span', { class: 'small muted' }, t('giftRevokeConfirm')), - h('div', { class: 'row gap6' }, - ui.busy - ? h('button', { class: 'btn-primary grow', disabled: true }, h('span', { class: 'spinner' })) - : h('button', { class: 'btn-primary grow', onClick: () => doRevoke(id) }, t('giftRevoke')), - h('button', { class: 'btn-ghost grow', onClick: () => { ui.revokeId = null; render(); } }, t('back')) - ) + h('span', { class: 'small muted' }, t('giftReclaimPrompt')), + ui.busy + ? h('button', { class: 'btn-primary btn-block', disabled: true }, h('span', { class: 'spinner' })) + : h('div', { class: 'row gap6' }, + h('button', { class: 'btn-ghost grow', onClick: () => doReclaim(id) }, t('giftReclaim')), + h('button', { class: 'btn-primary grow', onClick: () => doRevoke(id) }, t('giftRevoke')) + ), + ui.busy ? null : h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.revokeId = null; render(); } }, t('back')) ); } return h('div', { class: 'row between' }, h('span', { class: 'small mono' }, label), - h('button', { class: 'btn-sm', onClick: () => { ui.revokeId = id; render(); } }, t('giftRevoke')) + h('button', { class: 'btn-sm', onClick: () => { ui.revokeId = id; render(); } }, t('giftReclaim')) ); }) ) @@ -774,6 +775,16 @@ function giftCard() { ); } +// Passive reclaim: just free the coin for a future payment (no fee, no +// broadcast). The link stays claimable until the coin is actually spent. +function doReclaim(id) { + wallet.unreserve(id); + ui.revokeId = null; + toast(t('giftReclaimed')); + render(); +} + +// Active revoke: spend the coin back now (pays a fee), killing the link. async function doRevoke(id) { if (wallet.offline) { toast(t('scanOffline')); return; } ui.busy = true; -- cgit v1.2.3