diff options
| author | Adam Soltys <[email protected]> | 2026-06-24 21:41:52 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-24 21:41:52 -0700 |
| commit | 8a905acce3fe8f796768dd895097370b09a1239c (patch) | |
| tree | 90611c4ddcdc59163fea8bee8f2301b585cbdbb2 | |
| parent | 96e91a2cca47f4eae3514d6827a29354e507dadd (diff) | |
| download | coinosv3-8a905acce3fe8f796768dd895097370b09a1239c.tar.gz coinosv3-8a905acce3fe8f796768dd895097370b09a1239c.zip | |
Show outstanding sent gifts in History, cancellable there
Unclaimed gift links now appear at the top of the History list (they aren't
on-chain transactions until claimed or revoked). Each is tappable to cancel
via the same reclaim/revoke flow as the Send → gift card, reusing ui.revokeId
and the doReclaim/doRevoke handlers.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | src/app.js | 38 | ||||
| -rw-r--r-- | src/i18n.js | 3 |
2 files changed, 39 insertions, 2 deletions
@@ -2200,6 +2200,36 @@ function sendResultView() { } // ---------------------------------------------------------------- History +// An outstanding sent gift shown in History: tappable to cancel (reclaim the +// coin for a future payment, or revoke the link on-chain) without going through +// the Send → gift card. Reuses the same confirm state and handlers. +function giftHistoryItem(g) { + if (ui.revokeId === g.id) { + return h('div', { class: 'item col', style: 'align-items:stretch;gap:8px' }, + h('span', { class: 'small muted' }, g.reserved ? t('giftReclaimPrompt') : t('giftRevokeConfirm')), + ui.busy + ? h('button', { class: 'btn-primary btn-block', disabled: true }, h('span', { class: 'spinner' })) + : h('div', { class: 'row gap6' }, + g.reserved ? h('button', { class: 'btn-ghost grow', onClick: () => doReclaim(g.id) }, t('giftReclaim')) : null, + h('button', { class: 'btn-primary grow', onClick: () => doRevoke(g.id) }, t('giftRevoke')) + ), + ui.busy ? null : h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.revokeId = null; render(); } }, t('back')) + ); + } + return h('div', { class: 'item' }, + h('div', { class: 'ico out' }, '🎁'), + h('div', { class: 'grow' }, + h('div', { class: 'row gap6' }, t('giftHistoryTitle'), + h('span', { class: 'tag pending' }, g.reserved ? t('giftUnclaimedTag') : t('giftReclaimedTag'))), + g.reserved ? h('div', { class: 'small faint' }, t('lockedInGifts')) : null + ), + h('div', { style: 'text-align:right' }, + g.value != null ? h('div', { class: 'amount' }, fmtAmount(g.value)) : null, + h('button', { class: 'btn-sm', style: 'margin-top:4px', onClick: () => { ui.revokeId = g.id; render(); } }, t('giftCancel')) + ) + ); +} + function historyTab() { if (ui.bump) return bumpView(); if (ui.txDetail) { @@ -2216,7 +2246,10 @@ function historyTab() { h('span', { class: 'spinner' }), wallet.historyLoading ? h('p', { class: 'small muted', style: 'margin:0' }, t('loadingHistory')) : null ); - if (!wallet.txs.length) + // Outstanding sent gifts (reserved/reclaimed but unclaimed) sit above the + // on-chain history; they aren't transactions until claimed or revoked. + const gifts = wallet.loaded ? wallet.outstandingGifts() : []; + if (!wallet.txs.length && !gifts.length) return h('div', { class: 'card' }, h('p', { class: 'muted center', style: 'margin:0' }, t('noTxYet'))); return h( 'div', @@ -2224,7 +2257,8 @@ function historyTab() { h( 'div', { class: 'list' }, - wallet.txs.map((tx) => { + ...gifts.map(giftHistoryItem), + ...wallet.txs.map((tx) => { const incoming = tx.net >= 0; return h( 'div', diff --git a/src/i18n.js b/src/i18n.js index f164b0b..4ac9752 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -200,6 +200,9 @@ const STR = { giftSplitFirst: 'Split a coin first', giftLockWhole: 'Lock the whole coin ({n})', giftSplitFailed: 'Could not split the coin.', + giftHistoryTitle: 'Gift link', + giftUnclaimedTag: 'unclaimed', + giftCancel: 'Cancel', lockedInGifts: 'Locked in gifts', giftReserved: 'Unclaimed gift links ({n})', giftReclaim: 'Reclaim', |
