diff options
| -rw-r--r-- | src/app.js | 7 | ||||
| -rw-r--r-- | src/features/ark.js | 23 | ||||
| -rw-r--r-- | src/features/zaps.js | 19 | ||||
| -rw-r--r-- | src/i18n.js | 2 |
4 files changed, 35 insertions, 16 deletions
@@ -2812,13 +2812,6 @@ function recipientRow(s, r, i) { !single && h('button', { type: 'button', class: 'btn-sm', title: t('remove'), onClick: () => { s.recipients.splice(i, 1); render(); } }, '✕') ), check, - i === 0 ? (() => { - const note = featureHook('resolvingNote'); - return note - ? h('div', { class: 'row gap6', style: 'align-items:center;padding:2px 0' }, - h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, note)) - : null; - })() : null, i === 0 && sendSearch.rows && sendSearch.rows.length && searchable(r.address) ? h('div', { class: 'list send-suggest' }, resultRows(h, sendSearch.rows, pickRecipient)) : null, diff --git a/src/features/ark.js b/src/features/ark.js index 7fda8ed..5c5e0a8 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -929,6 +929,20 @@ export function arkFeature(ctx) { })().catch((e) => { if (live()) { z.status = 'noark'; ui.sendError = e.message; render(); } }); } + // shared by the ark and lightning zap flows (see zaps.js for the twin) + function zapSkeleton(pk, fallback) { + return h('div', { class: 'card col', style: 'gap:12px' }, + h('h3', {}, '⚡ ' + t('zapTitle')), + (pk && ctx.hook('profileChip', pk, 'lg')) + || h('div', { class: 'small muted break' }, fallback || ''), + h('div', { class: 'row gap6', style: 'align-items:center;padding:6px 0' }, + h('span', { class: 'spinner sm' }), + h('span', { class: 'small muted' }, t('zapFinding'))), + h('button', { class: 'btn-ghost btn-block', onClick: () => { + ui.arkZap = null; ui.zap = null; ui.sendError = ''; ui.send = blankSend(); render(); + } }, t('back'))); + } + async function lookupArkZapTarget(pk) { const events = await wallet.nostrFetch({ kinds: [ARK_INFO_KIND], authors: [pk] }, 6000); const ev = (events || []).sort((a, b) => b.created_at - a.created_at)[0]; @@ -967,8 +981,10 @@ export function arkFeature(ctx) { } function arkZapView() { - // the npub lookup is a background step — no takeover card for it - if (ui.arkZap && ui.arkZap.status === 'lookup') return null; + // While resolving, show the SHAPE of the card that's coming — same + // title, same recipient chip, a spinner where the form will be. The + // screen fills in rather than being replaced. + if (ui.arkZap && ui.arkZap.status === 'lookup') return zapSkeleton(ui.arkZap.pk, ui.arkZap.npub); if (ui.arkZapped) { return h('div', { class: 'card col', @@ -983,7 +999,7 @@ export function arkFeature(ctx) { const z = ui.arkZap; if (!z) return null; return h('div', { class: 'card col', style: 'gap:12px' }, - h('h3', {}, '⚡ ' + t('arkZapTitle')), + h('h3', {}, '⚡ ' + t('zapTitle')), ctx.hook('profileChip', z.pk, 'lg') || h('div', { class: 'small muted', style: 'word-break:break-all' }, z.npub), z.status === 'lookup' ? h('div', { class: 'row gap6', style: 'align-items:center' }, h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('arkZapLookup'))) : null, z.status === 'noark' ? h('div', { class: 'notice err' }, t('arkZapNoArk')) : null, @@ -1636,7 +1652,6 @@ export function arkFeature(ctx) { // swaps feature): the ASP pays invoices natively over ark. canLnPay() { return arkAvailable() && !wallet.watchOnly && !!(ark && ark.info); }, lnSpendableSat() { const b = arkBalance(); return b ? b.spendableSat : 0; }, - resolvingNote() { return ui.arkZap && ui.arkZap.status === 'lookup' ? t('arkZapLookup') : null; }, startLnPay(invoice, meta) { return startArkLnPay(invoice, meta); }, // ---- headless seam for the NWC wallet service ---- arkPayInvoice(invoice, opts) { return payInvoiceHeadless(invoice, opts); }, diff --git a/src/features/zaps.js b/src/features/zaps.js index 3298eaf..18676de 100644 --- a/src/features/zaps.js +++ b/src/features/zaps.js @@ -93,7 +93,17 @@ export function zapsFeature(ctx) { // Resolution is a background step, not a screen: the send form stays put // and shows one inline line (resolvingNote below) until we can render // something the user can act on. - if (z.status === 'resolving') return null; + if (z.status === 'resolving') { + // twin of ark's skeleton: identical shape, so the handoff is invisible + return h('div', { class: 'card col', style: 'gap:12px' }, + h('h3', {}, '⚡ ' + t('zapTitle')), + (z.target && z.target.pk && hook('profileChip', z.target.pk, 'lg')) + || h('div', { class: 'small muted break' }, z.name || z.address || ''), + h('div', { class: 'row gap6', style: 'align-items:center;padding:6px 0' }, + h('span', { class: 'spinner sm' }), + h('span', { class: 'small muted' }, t('zapFinding'))), + h('button', { class: 'btn-ghost btn-block', onClick: back }, t('back'))); + } if (z.status === 'invoicing') { return h('div', { class: 'card col', style: 'align-items:center;gap:14px;padding:32px 14px' }, h('span', { class: 'spinner' }), @@ -101,7 +111,7 @@ export function zapsFeature(ctx) { } if (z.status === 'error') { return h('div', { class: 'card col', style: 'gap:12px' }, - h('h3', {}, '⚡ ' + t('lnZapTitle')), + h('h3', {}, '⚡ ' + t('zapTitle')), (z.target && z.target.pk && hook('profileChip', z.target.pk, 'lg')) || 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'))); @@ -118,13 +128,13 @@ export function zapsFeature(ctx) { const commentOk = p.allowsNostr || p.commentAllowed > 0; if (broke) { return h('div', { class: 'card col', style: 'gap:12px' }, - h('h3', {}, '⚡ ' + t('lnZapTitle')), + h('h3', {}, '⚡ ' + t('zapTitle')), (z.target && z.target.pk && hook('profileChip', z.target.pk, 'lg')) || 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('h3', {}, '⚡ ' + t('zapTitle')), (z.target && z.target.pk && hook('profileChip', z.target.pk, 'lg')) || h('div', { class: 'small muted', style: 'word-break:break-all' }, z.name || z.address || ''), h('div', { class: 'col gap6' }, h('div', { class: 'input-group' }, @@ -186,7 +196,6 @@ export function zapsFeature(ctx) { // True when an npub can be zapped over Lightning from this build/wallet — // ark's "no Ark address" screen checks this before offering the fallback. canLnZap() { return canPay() && !!wallet.nostrProfile; }, - resolvingNote() { return ui.zap && ui.zap.status === 'resolving' ? t('lnZapResolving') : null; }, // Ark's "no Ark address published" screen offers a Lightning fallback, // which lands here with the recipient's pubkey. lnZapNpub(pk, npub) { diff --git a/src/i18n.js b/src/i18n.js index b387285..e87f097 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -463,6 +463,8 @@ const STR = { arkUniDesc: 'Your escape hatch: claim the money yourself without anyone’s help. Takes about a day and costs mining fees, but works even if the server disappears.', arkExitNoBalance: 'Nothing in Spending to move.', arkZapTitle: 'Send a zap', + zapTitle: 'Send a zap', + zapFinding: 'Finding the best way to pay them…', arkZapLookup: 'Looking them up…', arkZapNoArk: 'No way to pay this account was found — no Ark address, Lightning address, or payment record.', arkZapHint: 'Instant and free, with a zap receipt on nostr.', |
