From 0fd54abde8001f482f3aca5052bb6041cb498dbc Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 5 Oct 2025 18:24:32 -0400 Subject: [PATCH 1/2] Fix zap dialog positioning and gating --- components/video-modal.html | 213 ++++++++++++++++++--------------- css/style.css | 13 ++ js/app.js | 34 +++++- js/channelProfile.js | 168 ++++++++++++++++++++++++-- js/ui/components/VideoModal.js | 32 +++++ views/channel-profile.html | 132 +++++++++++++------- 6 files changed, 447 insertions(+), 145 deletions(-) diff --git a/components/video-modal.html b/components/video-modal.html index 39339e5f..b48e26e9 100644 --- a/components/video-modal.html +++ b/components/video-modal.html @@ -35,18 +35,124 @@ class="icon-image" /> - +
+ + +
- -
-
- - -
-
- - -
-

- Enter an amount to view the split. -

-

- -
- -
-
- - -
diff --git a/css/style.css b/css/style.css index 456ca6c6..dea54de6 100644 --- a/css/style.css +++ b/css/style.css @@ -1016,6 +1016,19 @@ body.modal-open #app { flex-wrap: nowrap; } +.zap-popover { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + z-index: 80; + width: max-content; +} + +.zap-popover__content { + width: 18rem; + max-width: calc(100vw - 2rem); +} + .modal-actions__overflow { position: relative; overflow: visible; diff --git a/js/app.js b/js/app.js index 95ebb19c..fd850e14 100644 --- a/js/app.js +++ b/js/app.js @@ -198,6 +198,7 @@ class Application { this.boundVideoModalShareHandler = null; this.boundVideoModalCreatorHandler = null; this.boundVideoModalZapHandler = null; + this.boundVideoModalZapWalletHandler = null; this.videoListViewPlaybackHandler = null; this.videoListViewEditHandler = null; this.videoListViewRevertHandler = null; @@ -529,6 +530,15 @@ class Application { "zap:comment-change", this.boundVideoModalZapCommentHandler ); + this.boundVideoModalZapWalletHandler = () => { + if (typeof this.openWalletPane === "function") { + this.openWalletPane(); + } + }; + this.videoModal.addEventListener( + "zap:wallet-link", + this.boundVideoModalZapWalletHandler + ); // Hide/Show Subscriptions Link this.subscriptionsLink = null; @@ -4512,6 +4522,17 @@ class Application { return cached ? { ...cached } : createDefaultNwcSettings(); } + hasActiveWalletConnection() { + const settings = this.getActiveNwcSettings(); + const candidate = + typeof settings?.nwcUri === "string" ? settings.nwcUri.trim() : ""; + return candidate.length > 0; + } + + isUserLoggedIn() { + return Boolean(this.normalizeHexPubkey(this.pubkey)); + } + async updateActiveNwcSettings(partial = {}) { const normalized = this.normalizeHexPubkey(this.pubkey); if (!normalized) { @@ -5726,8 +5747,12 @@ class Application { } setModalZapVisibility(visible) { + const lightningVisible = !!visible; + const shouldShow = lightningVisible && this.isUserLoggedIn(); + const hasWallet = this.hasActiveWalletConnection(); if (this.videoModal) { - this.videoModal.setZapVisibility(visible); + this.videoModal.setZapVisibility(shouldShow); + this.videoModal.setWalletPromptVisible(shouldShow && !hasWallet); } } @@ -9479,6 +9504,13 @@ class Application { ); this.boundVideoModalZapCommentHandler = null; } + if (this.boundVideoModalZapWalletHandler) { + this.videoModal.removeEventListener( + "zap:wallet-link", + this.boundVideoModalZapWalletHandler + ); + this.boundVideoModalZapWalletHandler = null; + } if (typeof this.videoModal.destroy === "function") { try { this.videoModal.destroy(); diff --git a/js/channelProfile.js b/js/channelProfile.js index 88b39528..7d634d17 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -109,9 +109,13 @@ let cachedZapAmountInput = null; let cachedZapSplitSummary = null; let cachedZapStatus = null; let cachedZapReceipts = null; +let cachedZapWalletPrompt = null; +let cachedZapWalletLink = null; +let cachedZapCloseBtn = null; let pendingZapRetry = null; let zapInFlight = false; +let zapControlsOpen = false; function getChannelZapButton() { if (cachedZapButton && !document.body.contains(cachedZapButton)) { @@ -126,28 +130,53 @@ function getChannelZapButton() { function setChannelZapVisibility(visible) { const zapButton = getChannelZapButton(); const controls = getZapControlsContainer(); + const amountInput = getZapAmountInput(); if (!zapButton) { return; } - const shouldShow = !!visible; + const app = getApp(); + const isLoggedIn = + typeof app?.isUserLoggedIn === "function" + ? app.isUserLoggedIn() + : Boolean(app?.normalizeHexPubkey?.(app?.pubkey)); + const shouldShow = !!visible && isLoggedIn; zapButton.classList.toggle("hidden", !shouldShow); zapButton.disabled = !shouldShow; zapButton.setAttribute("aria-disabled", (!shouldShow).toString()); zapButton.setAttribute("aria-hidden", (!shouldShow).toString()); + zapButton.setAttribute("aria-expanded", "false"); if (shouldShow) { zapButton.removeAttribute("tabindex"); } else { zapButton.setAttribute("tabindex", "-1"); - if (controls) { - controls.classList.add("hidden"); - } resetZapRetryState(); zapInFlight = false; clearZapReceipts(); setZapStatus("", "neutral"); } + closeZapControls(); if (controls) { - controls.classList.toggle("hidden", !shouldShow); + controls.setAttribute("aria-hidden", "true"); + } + if (amountInput) { + amountInput.disabled = !shouldShow; + } + + const hasWallet = + typeof app?.hasActiveWalletConnection === "function" + ? app.hasActiveWalletConnection() + : (() => { + const settings = + typeof app?.getActiveNwcSettings === "function" + ? app.getActiveNwcSettings() + : {}; + const uri = + typeof settings?.nwcUri === "string" ? settings.nwcUri.trim() : ""; + return uri.length > 0; + })(); + setZapWalletPromptVisible(shouldShow && !hasWallet); + if (shouldShow) { + setupZapWalletLink(); } } @@ -221,6 +250,114 @@ function getZapReceiptsList() { return cachedZapReceipts; } +function getZapWalletPrompt() { + if (cachedZapWalletPrompt && !document.body.contains(cachedZapWalletPrompt)) { + cachedZapWalletPrompt = null; + } + if (!cachedZapWalletPrompt) { + cachedZapWalletPrompt = document.getElementById("zapWalletPrompt"); + } + return cachedZapWalletPrompt; +} + +function getZapWalletLink() { + if (cachedZapWalletLink && !document.body.contains(cachedZapWalletLink)) { + cachedZapWalletLink = null; + } + if (!cachedZapWalletLink) { + cachedZapWalletLink = document.getElementById("zapWalletLink"); + } + return cachedZapWalletLink; +} + +function getZapCloseButton() { + if (cachedZapCloseBtn && !document.body.contains(cachedZapCloseBtn)) { + cachedZapCloseBtn = null; + } + if (!cachedZapCloseBtn) { + cachedZapCloseBtn = document.getElementById("zapCloseBtn"); + } + return cachedZapCloseBtn; +} + +function setZapWalletPromptVisible(visible) { + const prompt = getZapWalletPrompt(); + if (!prompt) { + return; + } + const shouldShow = !!visible; + prompt.classList.toggle("hidden", !shouldShow); + prompt.setAttribute("aria-hidden", (!shouldShow).toString()); +} + +function setupZapWalletLink() { + const link = getZapWalletLink(); + if (!link) { + return; + } + if (link.dataset.initialized === "true") { + return; + } + link.addEventListener("click", (event) => { + event?.preventDefault?.(); + const app = getApp(); + if (typeof app?.openWalletPane === "function") { + app.openWalletPane(); + } + }); + link.dataset.initialized = "true"; +} + +function focusZapAmountField() { + const amountInput = getZapAmountInput(); + if (amountInput && typeof amountInput.focus === "function") { + try { + amountInput.focus({ preventScroll: true }); + } catch (error) { + amountInput.focus(); + } + } +} + +function isZapControlsOpen() { + return zapControlsOpen; +} + +function openZapControls({ focus = false } = {}) { + const controls = getZapControlsContainer(); + const zapButton = getChannelZapButton(); + if (!controls || !zapButton) { + return false; + } + if (!zapControlsOpen) { + controls.classList.remove("hidden"); + controls.setAttribute("aria-hidden", "false"); + zapButton.setAttribute("aria-expanded", "true"); + zapControlsOpen = true; + } + if (focus) { + focusZapAmountField(); + } + return true; +} + +function closeZapControls({ focusButton = false } = {}) { + const controls = getZapControlsContainer(); + const zapButton = getChannelZapButton(); + if (controls) { + controls.classList.add("hidden"); + controls.setAttribute("aria-hidden", "true"); + } + if (zapButton) { + zapButton.setAttribute("aria-expanded", "false"); + if (focusButton && typeof zapButton.focus === "function") { + zapButton.focus(); + } + } + zapControlsOpen = false; + return Boolean(controls && zapButton); +} + function updateZapSplitSummary({ overrideFee = null } = {}) { const summaryEl = getZapSplitSummaryElement(); if (!summaryEl) { @@ -388,8 +525,8 @@ function resetZapRetryState() { const zapButton = getChannelZapButton(); if (zapButton) { delete zapButton.dataset.retryPending; - zapButton.setAttribute("aria-label", "Send a zap"); - zapButton.title = "Send a zap"; + zapButton.setAttribute("aria-label", "Open zap dialog"); + zapButton.title = "Open zap dialog"; } } @@ -823,6 +960,11 @@ async function handleZapButtonClick(event) { return; } + if (!isZapControlsOpen()) { + openZapControls({ focus: true }); + return; + } + if (!currentChannelLightningAddress) { setZapStatus("This creator has not configured a Lightning address yet.", "error"); app?.showError?.("This creator has not configured a Lightning address yet."); @@ -1160,6 +1302,18 @@ function setupZapButton() { } setChannelZapVisibility(false); + controls.classList.add("hidden"); + controls.setAttribute("aria-hidden", "true"); + zapButton.setAttribute("aria-expanded", "false"); + setupZapWalletLink(); + const closeBtn = getZapCloseButton(); + if (closeBtn && closeBtn.dataset.initialized !== "true") { + closeBtn.addEventListener("click", (event) => { + event?.preventDefault?.(); + closeZapControls({ focusButton: true }); + }); + closeBtn.dataset.initialized = "true"; + } if (zapButton.dataset.initialized === "true") { updateZapSplitSummary(); diff --git a/js/ui/components/VideoModal.js b/js/ui/components/VideoModal.js index cb05fd1b..701db469 100644 --- a/js/ui/components/VideoModal.js +++ b/js/ui/components/VideoModal.js @@ -54,6 +54,8 @@ export class VideoModal { this.modalZapReceipts = null; this.modalZapSendBtn = null; this.modalZapCloseBtn = null; + this.modalZapWalletPrompt = null; + this.modalZapWalletLink = null; this.modalZapDialogOpen = false; this.modalZapPending = false; @@ -208,6 +210,10 @@ export class VideoModal { playerModal.querySelector("#modalZapSendBtn") || null; this.modalZapCloseBtn = playerModal.querySelector("#modalZapCloseBtn") || null; + this.modalZapWalletPrompt = + playerModal.querySelector("#modalZapWalletPrompt") || null; + this.modalZapWalletLink = + playerModal.querySelector("#modalZapWalletLink") || null; const closeButton = playerModal.querySelector("#closeModal"); if (closeButton) { @@ -307,6 +313,13 @@ export class VideoModal { }); } + if (this.modalZapWalletLink) { + this.modalZapWalletLink.addEventListener("click", (event) => { + event?.preventDefault?.(); + this.dispatch("zap:wallet-link", { video: this.activeVideo }); + }); + } + if (this.modalZapForm) { this.modalZapForm.addEventListener("submit", (event) => { event?.preventDefault?.(); @@ -539,6 +552,7 @@ export class VideoModal { this.modalZapBtn.disabled = disableButton; this.modalZapBtn.setAttribute("aria-disabled", (!shouldShow).toString()); this.modalZapBtn.setAttribute("aria-hidden", (!shouldShow).toString()); + this.modalZapBtn.setAttribute("aria-expanded", "false"); if (shouldShow) { this.modalZapBtn.removeAttribute("tabindex"); } else { @@ -558,6 +572,18 @@ export class VideoModal { } } + setWalletPromptVisible(visible) { + if (!this.modalZapWalletPrompt) { + return; + } + const shouldShow = !!visible; + this.modalZapWalletPrompt.classList.toggle("hidden", !shouldShow); + this.modalZapWalletPrompt.setAttribute( + "aria-hidden", + (!shouldShow).toString() + ); + } + openZapDialog() { if (!this.modalZapDialog) { return; @@ -565,6 +591,9 @@ export class VideoModal { this.modalZapDialog.classList.remove("hidden"); this.modalZapDialogOpen = true; this.modalZapDialog.setAttribute("aria-hidden", "false"); + if (this.modalZapBtn) { + this.modalZapBtn.setAttribute("aria-expanded", "true"); + } this.focusZapAmount(); } @@ -576,6 +605,9 @@ export class VideoModal { this.modalZapDialog.classList.add("hidden"); this.modalZapDialog.setAttribute("aria-hidden", "true"); this.modalZapDialogOpen = false; + if (this.modalZapBtn) { + this.modalZapBtn.setAttribute("aria-expanded", "false"); + } if (!silent) { this.dispatch("zap:close", { video: this.activeVideo }); } diff --git a/views/channel-profile.html b/views/channel-profile.html index c0940877..9ba77323 100644 --- a/views/channel-profile.html +++ b/views/channel-profile.html @@ -29,18 +29,96 @@
- +
+ + +
-
From 8fd5ce3c880dc34ef136d9067e43f0e46e780269 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 5 Oct 2025 18:45:03 -0400 Subject: [PATCH 2/2] Darken zap dialog and add send control --- components/video-modal.html | 4 +- css/style.css | 6 ++ js/app.js | 4 +- js/channelProfile.js | 113 +++++++++++++++++++++++++++++---- js/ui/components/VideoModal.js | 4 +- views/channel-profile.html | 78 +++++++++++++---------- 6 files changed, 157 insertions(+), 52 deletions(-) diff --git a/components/video-modal.html b/components/video-modal.html index b48e26e9..3045c03f 100644 --- a/components/video-modal.html +++ b/components/video-modal.html @@ -58,7 +58,7 @@ aria-hidden="true" >

- Send zap + Send

diff --git a/css/style.css b/css/style.css index dea54de6..aad9283e 100644 --- a/css/style.css +++ b/css/style.css @@ -1027,6 +1027,12 @@ body.modal-open #app { .zap-popover__content { width: 18rem; max-width: calc(100vw - 2rem); + background: rgba(11, 17, 32, 0.96); + color: #e5e7eb; + border: 1px solid rgba(148, 163, 184, 0.35); + box-shadow: 0 22px 48px -18px rgba(8, 14, 28, 0.7); + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); } .modal-actions__overflow { diff --git a/js/app.js b/js/app.js index fd850e14..008aadf6 100644 --- a/js/app.js +++ b/js/app.js @@ -6603,8 +6603,8 @@ class Application { const tone = tracker.length > failureShares.length ? "warning" : "error"; const statusMessage = tracker.length > failureShares.length - ? `Partial zap failure. Tap zap again to retry: ${summary}.` - : `Zap failed. Tap zap again to retry: ${summary}.`; + ? `Partial zap failure. Press Send again to retry: ${summary}.` + : `Zap failed. Press Send again to retry: ${summary}.`; this.videoModal?.setZapStatus(statusMessage, tone); this.showError(error?.message || statusMessage); } else { diff --git a/js/channelProfile.js b/js/channelProfile.js index 7d634d17..20cb7549 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -105,6 +105,7 @@ let cachedZapButton = null; let cachedChannelShareButton = null; let cachedChannelMenu = null; let cachedZapControls = null; +let cachedZapForm = null; let cachedZapAmountInput = null; let cachedZapSplitSummary = null; let cachedZapStatus = null; @@ -112,6 +113,7 @@ let cachedZapReceipts = null; let cachedZapWalletPrompt = null; let cachedZapWalletLink = null; let cachedZapCloseBtn = null; +let cachedZapSendBtn = null; let pendingZapRetry = null; let zapInFlight = false; @@ -131,6 +133,7 @@ function setChannelZapVisibility(visible) { const zapButton = getChannelZapButton(); const controls = getZapControlsContainer(); const amountInput = getZapAmountInput(); + const sendButton = getZapSendButton(); if (!zapButton) { return; } @@ -161,6 +164,17 @@ function setChannelZapVisibility(visible) { if (amountInput) { amountInput.disabled = !shouldShow; } + if (sendButton) { + sendButton.disabled = !shouldShow; + sendButton.setAttribute("aria-hidden", (!shouldShow).toString()); + if (shouldShow) { + sendButton.removeAttribute("tabindex"); + sendButton.removeAttribute("aria-busy"); + sendButton.classList.remove("opacity-50", "pointer-events-none"); + } else { + sendButton.setAttribute("tabindex", "-1"); + } + } const hasWallet = typeof app?.hasActiveWalletConnection === "function" @@ -210,6 +224,16 @@ function getZapControlsContainer() { return cachedZapControls; } +function getZapFormElement() { + if (cachedZapForm && !document.body.contains(cachedZapForm)) { + cachedZapForm = null; + } + if (!cachedZapForm) { + cachedZapForm = document.getElementById("zapForm"); + } + return cachedZapForm; +} + function getZapAmountInput() { if (cachedZapAmountInput && !document.body.contains(cachedZapAmountInput)) { cachedZapAmountInput = null; @@ -250,6 +274,16 @@ function getZapReceiptsList() { return cachedZapReceipts; } +function getZapSendButton() { + if (cachedZapSendBtn && !document.body.contains(cachedZapSendBtn)) { + cachedZapSendBtn = null; + } + if (!cachedZapSendBtn) { + cachedZapSendBtn = document.getElementById("zapSendBtn"); + } + return cachedZapSendBtn; +} + function getZapWalletPrompt() { if (cachedZapWalletPrompt && !document.body.contains(cachedZapWalletPrompt)) { cachedZapWalletPrompt = null; @@ -528,6 +562,13 @@ function resetZapRetryState() { zapButton.setAttribute("aria-label", "Open zap dialog"); zapButton.title = "Open zap dialog"; } + const sendButton = getZapSendButton(); + if (sendButton) { + delete sendButton.dataset.retryPending; + sendButton.textContent = "Send"; + sendButton.setAttribute("aria-label", "Send a zap"); + sendButton.removeAttribute("title"); + } } function markZapRetryPending(shares) { @@ -546,6 +587,13 @@ function markZapRetryPending(shares) { zapButton.setAttribute("aria-label", "Retry failed zap shares"); zapButton.title = "Retry failed zap shares"; } + const sendButton = getZapSendButton(); + if (sendButton) { + sendButton.dataset.retryPending = "true"; + sendButton.textContent = "Retry"; + sendButton.setAttribute("aria-label", "Retry failed zap shares"); + sendButton.title = "Retry failed zap shares"; + } } function getZapVideoEvent() { @@ -947,16 +995,39 @@ async function executePendingRetry({ walletSettings }) { resetZapRetryState(); } -async function handleZapButtonClick(event) { +function handleZapButtonClick(event) { if (event) { event.preventDefault(); } + const zapButton = getChannelZapButton(); + if (!zapButton) { + return; + } + + if (!isZapControlsOpen()) { + openZapControls({ focus: true }); + return; + } + + if (zapInFlight) { + return; + } + + closeZapControls({ focusButton: true }); +} + +async function handleZapSend(event) { + if (event?.preventDefault) { + event.preventDefault(); + } + const zapButton = getChannelZapButton(); const amountInput = getZapAmountInput(); + const sendButton = getZapSendButton(); const app = getApp(); - if (!zapButton || !amountInput) { + if (!amountInput || !sendButton) { return; } @@ -966,8 +1037,9 @@ async function handleZapButtonClick(event) { } if (!currentChannelLightningAddress) { - setZapStatus("This creator has not configured a Lightning address yet.", "error"); - app?.showError?.("This creator has not configured a Lightning address yet."); + const message = "This creator has not configured a Lightning address yet."; + setZapStatus(message, "error"); + app?.showError?.(message); return; } @@ -981,9 +1053,13 @@ async function handleZapButtonClick(event) { } zapInFlight = true; - zapButton.disabled = true; - zapButton.setAttribute("aria-busy", "true"); - zapButton.classList.add("opacity-50", "pointer-events-none"); + if (zapButton) { + zapButton.disabled = true; + zapButton.setAttribute("aria-disabled", "true"); + } + sendButton.disabled = true; + sendButton.setAttribute("aria-busy", "true"); + sendButton.classList.add("opacity-50", "pointer-events-none"); amountInput.disabled = true; let attemptedAmount = null; @@ -1028,7 +1104,7 @@ async function handleZapButtonClick(event) { ? error.__zapShareTracker : []; logZapError( - "handleZapButtonClick", + "handleZapSend", { amount: attemptedAmount, walletSettings, @@ -1055,8 +1131,8 @@ async function handleZapButtonClick(event) { const tone = tracker.length > failureShares.length ? "warning" : "error"; const statusMessage = tracker.length > failureShares.length - ? `Partial zap failure. Tap zap again to retry: ${summary}.` - : `Zap failed. Tap zap again to retry: ${summary}.`; + ? `Partial zap failure. Press Send again to retry: ${summary}.` + : `Zap failed. Press Send again to retry: ${summary}.`; setZapStatus(statusMessage, tone); app?.showError?.(error?.message || statusMessage); } else { @@ -1067,9 +1143,13 @@ async function handleZapButtonClick(event) { } } finally { zapInFlight = false; - zapButton.disabled = false; - zapButton.removeAttribute("aria-busy"); - zapButton.classList.remove("opacity-50", "pointer-events-none"); + if (zapButton) { + zapButton.disabled = false; + zapButton.removeAttribute("aria-disabled"); + } + sendButton.disabled = false; + sendButton.removeAttribute("aria-busy"); + sendButton.classList.remove("opacity-50", "pointer-events-none"); amountInput.disabled = false; } } @@ -1297,7 +1377,8 @@ function setupZapButton() { const zapButton = getChannelZapButton(); const amountInput = getZapAmountInput(); const controls = getZapControlsContainer(); - if (!zapButton || !amountInput || !controls) { + const zapForm = getZapFormElement(); + if (!zapButton || !amountInput || !controls || !zapForm || !getZapSendButton()) { return; } @@ -1332,6 +1413,10 @@ function setupZapButton() { updateZapSplitSummary(); amountInput.addEventListener("input", handleZapAmountChange); amountInput.addEventListener("change", handleZapAmountChange); + if (zapForm.dataset.initialized !== "true") { + zapForm.addEventListener("submit", handleZapSend); + zapForm.dataset.initialized = "true"; + } zapButton.addEventListener("click", handleZapButtonClick); zapButton.dataset.initialized = "true"; } diff --git a/js/ui/components/VideoModal.js b/js/ui/components/VideoModal.js index 701db469..4993d2fd 100644 --- a/js/ui/components/VideoModal.js +++ b/js/ui/components/VideoModal.js @@ -857,7 +857,7 @@ export class VideoModal { if (pending) { this.modalZapSendBtn.dataset.retryPending = "true"; - this.modalZapSendBtn.textContent = "Retry zap"; + this.modalZapSendBtn.textContent = "Retry"; this.modalZapSendBtn.setAttribute( "aria-label", "Retry failed zap shares" @@ -867,7 +867,7 @@ export class VideoModal { } } else { delete this.modalZapSendBtn.dataset.retryPending; - this.modalZapSendBtn.textContent = "Send zap"; + this.modalZapSendBtn.textContent = "Send"; this.modalZapSendBtn.setAttribute("aria-label", "Send a zap"); this.modalZapSendBtn.removeAttribute("title"); } diff --git a/views/channel-profile.html b/views/channel-profile.html index 9ba77323..90747dd6 100644 --- a/views/channel-profile.html +++ b/views/channel-profile.html @@ -53,7 +53,7 @@ aria-label="Channel zap dialog" >

to send zaps.

- - -

- Enter an amount to view the split. -

-

-
    +
    +
    + + +
    +

    + Enter an amount to view the split. +

    +

    +
      +
      + +
      +