diff --git a/js/app.js b/js/app.js index f1959c2d..6f95ad60 100644 --- a/js/app.js +++ b/js/app.js @@ -586,14 +586,18 @@ class Application { getCurrentVideo: () => this.currentVideo, nwcSettings: this.nwcSettingsService, isUserLoggedIn: () => this.isUserLoggedIn(), + hasSessionActor: () => + Boolean( + typeof nostrClient?.sessionActor?.pubkey === "string" && + nostrClient.sessionActor.pubkey.trim() + ), splitAndZap: (...args) => this.splitAndZap(...args), payments: this.payments, callbacks: { onSuccess: (message) => this.showSuccess(message), onError: (message) => this.showError(message), }, - requestWalletPane: () => - this.profileController?.showWalletPane?.(), + requestWalletPane: () => this.openWalletPane(), }); this.boundVideoModalCloseHandler = () => { this.hideModal(); @@ -1308,6 +1312,32 @@ class Application { } } + openWalletPane() { + if ( + !this.profileController || + typeof this.profileController.showWalletPane !== "function" + ) { + devLogger.warn( + "[Application] Wallet pane requested before profile controller initialized.", + ); + return Promise.resolve(false); + } + + try { + const result = this.profileController.showWalletPane(); + if (result && typeof result.then === "function") { + return result.catch((error) => { + devLogger.error("[Application] Failed to open wallet pane:", error); + throw error; + }); + } + return Promise.resolve(result); + } catch (error) { + devLogger.error("[Application] Failed to open wallet pane:", error); + return Promise.reject(error); + } + } + normalizeModalTrigger(candidate) { if (!candidate) { return null; diff --git a/js/channelProfile.js b/js/channelProfile.js index e1dbe67e..92a1461e 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -499,7 +499,10 @@ function setChannelZapVisibility(visible) { typeof app?.isUserLoggedIn === "function" ? app.isUserLoggedIn() : Boolean(app?.normalizeHexPubkey?.(app?.pubkey)); - const shouldShow = !!visible; + const sessionActorPubkey = getSessionActorPubkey(); + const hasSessionActor = sessionActorPubkey.length > 0; + const sessionOnly = !isLoggedIn && hasSessionActor; + const shouldShow = !!visible && (isLoggedIn || sessionOnly); const requiresLogin = shouldShow && !isLoggedIn; if ( @@ -581,12 +584,13 @@ function isSessionActorWithoutLogin() { return false; } - const sessionPubkey = - typeof nostrClient?.sessionActor?.pubkey === "string" - ? nostrClient.sessionActor.pubkey.trim() - : ""; + return getSessionActorPubkey().length > 0; +} - return sessionPubkey.length > 0; +function getSessionActorPubkey() { + return typeof nostrClient?.sessionActor?.pubkey === "string" + ? nostrClient.sessionActor.pubkey.trim() + : ""; } function getChannelShareButton() { @@ -970,7 +974,20 @@ function setupZapWalletLink() { event?.preventDefault?.(); const app = getApp(); if (typeof app?.openWalletPane === "function") { - app.openWalletPane(); + Promise.resolve() + .then(() => app.openWalletPane()) + .catch((error) => { + devLogger.warn( + "[zap] Failed to open wallet pane from channel view:", + error + ); + app?.showError?.("Wallet settings are not available right now."); + }); + } else { + devLogger.warn( + "[zap] Wallet pane requested but application did not expose openWalletPane()." + ); + app?.showError?.("Wallet settings are not available right now."); } }); link.dataset.initialized = "true"; diff --git a/js/ui/components/VideoModal.js b/js/ui/components/VideoModal.js index 7f1264cb..b536062a 100644 --- a/js/ui/components/VideoModal.js +++ b/js/ui/components/VideoModal.js @@ -77,6 +77,7 @@ export class VideoModal { this.modalZapWalletLink = null; this.modalZapDialogOpen = false; this.modalZapPending = false; + this.modalZapRequiresLogin = false; this.modalZapPopover = null; this.modalMorePopover = null; @@ -414,6 +415,14 @@ export class VideoModal { return; } + if (this.modalZapRequiresLogin) { + this.dispatch("zap:open", { + video: this.activeVideo, + requiresLogin: true, + }); + return; + } + const popoverIsOpen = typeof this.modalZapPopover?.isOpen === "function" ? this.modalZapPopover.isOpen() @@ -966,13 +975,29 @@ export class VideoModal { this.shareBtn.classList.toggle("cursor-not-allowed", !enabled); } - setZapVisibility(visible) { - const shouldShow = !!visible; + setZapVisibility(visible, options = {}) { + let config; + if (typeof visible === "object" && visible !== null) { + config = { ...visible }; + } else { + config = { + visible, + ...(options && typeof options === "object" ? options : {}), + }; + } + + const shouldShow = !!config.visible; + const requiresLogin = shouldShow && !!config.requiresLogin; + this.modalZapRequiresLogin = requiresLogin; + if (this.modalZapBtn) { this.modalZapBtn.toggleAttribute("hidden", !shouldShow); - const disableButton = !shouldShow || this.modalZapPending; + const disableButton = + !shouldShow || (this.modalZapPending && !requiresLogin); this.modalZapBtn.disabled = disableButton; - this.modalZapBtn.setAttribute("aria-disabled", (!shouldShow).toString()); + const ariaDisabledValue = + requiresLogin || !shouldShow ? "true" : "false"; + this.modalZapBtn.setAttribute("aria-disabled", ariaDisabledValue); this.modalZapBtn.setAttribute("aria-hidden", (!shouldShow).toString()); this.modalZapBtn.setAttribute("aria-expanded", "false"); if (shouldShow) { @@ -980,16 +1005,26 @@ export class VideoModal { } else { this.modalZapBtn.setAttribute("tabindex", "-1"); } - if (this.modalZapPending) { - this.modalZapBtn.setAttribute("aria-busy", "true"); - this.modalZapBtn.classList.add("opacity-50", "pointer-events-none"); - } else { + if (requiresLogin) { + this.modalZapBtn.dataset.requiresLogin = "true"; this.modalZapBtn.removeAttribute("aria-busy"); this.modalZapBtn.classList.remove("opacity-50", "pointer-events-none"); + } else { + delete this.modalZapBtn.dataset.requiresLogin; + if (this.modalZapPending) { + this.modalZapBtn.setAttribute("aria-busy", "true"); + this.modalZapBtn.classList.add("opacity-50", "pointer-events-none"); + } else { + this.modalZapBtn.removeAttribute("aria-busy"); + this.modalZapBtn.classList.remove( + "opacity-50", + "pointer-events-none", + ); + } } } - if (!shouldShow) { + if (!shouldShow || requiresLogin) { this.closeZapDialog({ silent: true, restoreFocus: false }); } } @@ -1292,11 +1327,12 @@ export class VideoModal { } if (this.modalZapBtn) { - if (isPending) { + const buttonHidden = this.modalZapBtn.hasAttribute("hidden"); + if (isPending && !this.modalZapRequiresLogin) { this.modalZapBtn.disabled = true; this.modalZapBtn.setAttribute("aria-busy", "true"); this.modalZapBtn.classList.add("opacity-50", "pointer-events-none"); - } else if (!this.modalZapBtn.hasAttribute("hidden")) { + } else if (!buttonHidden) { this.modalZapBtn.disabled = false; this.modalZapBtn.removeAttribute("aria-busy"); this.modalZapBtn.classList.remove("opacity-50", "pointer-events-none"); diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index b5dfc0c5..fc119e35 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -4016,6 +4016,15 @@ export class ProfileModalController { return; } + const parentElement = modalRoot.parentElement; + if (parentElement) { + if (parentElement.lastElementChild !== modalRoot) { + parentElement.appendChild(modalRoot); + } + } else if (typeof document !== "undefined" && document.body) { + document.body.appendChild(modalRoot); + } + modalRoot.classList.remove("hidden"); modalRoot.setAttribute("aria-hidden", "false"); this.setGlobalModalState("profile", true); diff --git a/js/ui/zapController.js b/js/ui/zapController.js index fca1cd71..f7b9541b 100644 --- a/js/ui/zapController.js +++ b/js/ui/zapController.js @@ -33,6 +33,7 @@ export default class ZapController { nwcSettings, getActiveNwcSettings, isUserLoggedIn, + hasSessionActor, hasActiveWalletConnection, splitAndZap, payments = {}, @@ -58,6 +59,8 @@ export default class ZapController { } this.isUserLoggedIn = typeof isUserLoggedIn === "function" ? isUserLoggedIn : () => false; + this.hasSessionActor = + typeof hasSessionActor === "function" ? hasSessionActor : () => false; if ( this.nwcSettings && typeof this.nwcSettings.hasActiveWalletConnection === "function" @@ -89,11 +92,20 @@ export default class ZapController { setVisibility(visible) { const lightningVisible = !!visible; - const shouldShow = lightningVisible && this.isUserLoggedIn(); + const loggedIn = this.isUserLoggedIn(); + const sessionActorAvailable = this.hasSessionActor(); + const shouldShow = + lightningVisible && (loggedIn || sessionActorAvailable); + const requiresLogin = shouldShow && !loggedIn; const hasWallet = this.hasActiveWalletConnection(); if (this.videoModal) { - this.videoModal.setZapVisibility(shouldShow); - this.videoModal.setWalletPromptVisible(shouldShow && !hasWallet); + this.videoModal.setZapVisibility({ + visible: shouldShow, + requiresLogin, + }); + this.videoModal.setWalletPromptVisible( + shouldShow && !requiresLogin && !hasWallet, + ); } }