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] 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 @@
- +
+ + +
-