aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/app.js7
-rw-r--r--src/features/ark.js23
-rw-r--r--src/features/zaps.js19
-rw-r--r--src/i18n.js2
4 files changed, 35 insertions, 16 deletions
diff --git a/src/app.js b/src/app.js
index feb9a6a..9830800 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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.',