diff options
| author | Adam Soltys <[email protected]> | 2026-08-03 07:48:31 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-03 07:48:31 -0700 |
| commit | 51e0a583ca29019bced1e4e6349afd1c7572a988 (patch) | |
| tree | 6408a352c9a8109ff6cfd2a69a4e6a9ef53cc37f /src/features/zaps.js | |
| parent | 161adb250e4273d0bcaa160516bc950f5f46310f (diff) | |
| download | coinosv3-51e0a583ca29019bced1e4e6349afd1c7572a988.tar.gz coinosv3-51e0a583ca29019bced1e4e6349afd1c7572a988.zip | |
Zap screens show the recipient, cap amounts to Spending, faster search
The zap flows (ark-direct and Lightning) show the recipient's avatar
chip instead of a bare name, and their LNURL ceiling is now capped by
our own Spending balance — when Spending can't cover their minimum the
amount field is replaced by a plain "not enough in Spending" notice
rather than an impossible "min 1 · max 0" range. The confirm path
enforces the same cap.
Recipient search feels instant now: the first short query fires after
120ms (the wait users actually notice) and longer mid-word queries wait
500ms to skip characters still being typed; cached prefixes stay
instant.
Onboarding success screen: check below the title and larger.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
Diffstat (limited to 'src/features/zaps.js')
| -rw-r--r-- | src/features/zaps.js | 26 |
1 files changed, 20 insertions, 6 deletions
diff --git a/src/features/zaps.js b/src/features/zaps.js index 9aabf54..2b93a30 100644 --- a/src/features/zaps.js +++ b/src/features/zaps.js @@ -58,8 +58,9 @@ export function zapsFeature(ctx) { const p = z.params; if (!sats || sats <= 0) { z.error = t('enterValidAmtForN', { n: 1 }); return render(); } const msat = sats * 1000; - if (msat < p.minSendable || msat > p.maxSendable) { - z.error = t('lnZapRange', { min: Math.ceil(p.minSendable / 1000).toLocaleString(), max: Math.floor(p.maxSendable / 1000).toLocaleString() }); + const capSat = Math.min(Math.floor(p.maxSendable / 1000), hook('lnSpendableSat') ?? Infinity); + if (msat < p.minSendable || sats > capSat) { + z.error = t('lnZapRange', { min: Math.ceil(p.minSendable / 1000).toLocaleString(), max: capSat.toLocaleString() }); return render(); } z.error = ''; z.status = 'invoicing'; render(); @@ -97,17 +98,30 @@ export function zapsFeature(ctx) { if (z.status === 'error') { return h('div', { class: 'card col', style: 'gap:12px' }, h('h3', {}, '⚡ ' + t('lnZapTitle')), - h('div', { class: 'small muted', style: 'word-break:break-all' }, z.name || ''), + (z.target && z.target.pk && hook('profileChip', z.target.pk)) || h('div', { class: 'small muted', style: 'word-break:break-all' }, z.name || ''), h('div', { class: 'notice err' }, z.error || t('lnZapFailed')), h('button', { class: 'btn-ghost btn-block', onClick: back }, t('back'))); } - // ready: amount + optional comment + // ready: amount + optional comment. Their LNURL ceiling is meaningless + // past our own Spending balance — cap to the lower of the two, and when + // Spending can't even cover their minimum, say so instead of rendering + // an impossible range. const p = z.params; - const min = Math.ceil(p.minSendable / 1000), max = Math.floor(p.maxSendable / 1000); + const ours = hook('lnSpendableSat'); + const min = Math.ceil(p.minSendable / 1000); + const max = Math.min(Math.floor(p.maxSendable / 1000), ours != null ? ours : Infinity); + const broke = ours != null && (ours <= 0 || max < min); const commentOk = p.allowsNostr || p.commentAllowed > 0; + if (broke) { + return h('div', { class: 'card col', style: 'gap:12px' }, + h('h3', {}, '⚡ ' + t('lnZapTitle')), + (z.target && z.target.pk && hook('profileChip', z.target.pk)) || h('div', { class: 'small muted', style: 'word-break:break-all' }, z.name || z.address || ''), + h('div', { class: 'notice info' }, t('zapNoBalance')), + h('button', { class: 'btn-ghost btn-block', onClick: back }, t('back'))); + } return h('div', { class: 'card col', style: 'gap:12px' }, h('h3', {}, '⚡ ' + t('lnZapTitle')), - h('div', { class: 'small muted', style: 'word-break:break-all' }, z.name || z.address || ''), + (z.target && z.target.pk && hook('profileChip', z.target.pk)) || h('div', { class: 'small muted', style: 'word-break:break-all' }, z.name || z.address || ''), h('div', { class: 'col gap6' }, h('div', { class: 'input-group' }, h('input', { type: 'number', min: '0', inputmode: 'decimal', placeholder: t('lnPayAmount'), value: z.amount, |
