mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-01 18:32:27 +00:00
Merge pull request #662 from PR0M3TH3AN/codex/fix-notification-for-zap-button-when-not-logged-in
Ensure zap login requirement triggers notification
This commit is contained in:
7 files changed
+581
-41
No files matched your search
@@ -1,2 +1,2 @@
|
||||
:root{--surface-base:var(--color-surface);--surface-alt:var(--color-surface-alt);--surface-raised:var(--color-card);--surface-raised-private:var(--color-card-private);--surface-raised-critical:var(--color-card-critical);--surface-panel:var(--color-panel);--surface-panel-hover:var(--color-panel-hover);--surface-sunken:var(--color-surface-sunken);--surface-muted:var(--color-surface-muted);--surface-veil:var(--color-overlay-veil);--surface-inverse:var(--color-page);--surface-overlay:var(--color-overlay);--surface-overlay-muted:var(--color-overlay-muted);--surface-overlay-strong:var(--color-overlay-strong);--surface-overlay-darker:var(--color-overlay-darker);--surface-overlay-black:var(--color-overlay-black);--surface-overlay-banner:var(--surface-overlay-banner-bottom);--surface-overlay-banner-bottom:var(--color-overlay-banner-bottom);--surface-overlay-banner-transparent:var(--color-overlay-banner-transparent);--surface-overlay-banner-gradient:linear-gradient(to top,var(--surface-overlay-banner-bottom) 0%,var(--surface-overlay-banner-transparent) 60%,var(--surface-overlay-banner-transparent) 100%);--surface-overlay-panel:var(--color-overlay-panel);--surface-overlay-panel-soft:var(--color-overlay-panel-soft);--surface-overlay-panel-tint:var(--color-overlay-panel-tint);--surface-overlay-panel-glass:var(--color-overlay-panel-glass);--surface-overlay-panel-glass-soft:var(--color-overlay-panel-glass-soft);--surface-modal-backdrop:var(--color-modal-backdrop);--surface-modal:var(--color-modal-surface);--blur-modal-backdrop:18px;--blur-modal-alert:14px;--blur-thumbnail-blur:18px;--modal-nav-backdrop-blur:6px;--border-default:var(--color-border);--border-subtle:var(--color-border-subtle);--border-strong:var(--color-border-translucent-stronger);--border-overlay:var(--color-border-translucent);--border-overlay-strong:var(--color-border-translucent-strongest);--border-overlay-glow:var(--color-border-translucent-glow);--border-neutral-soft:var(--color-border-neutral-soft);--border-info-soft:var(--color-border-info-soft);--border-sidebar-strong:var(--color-border-sidebar-strong);--border-width-hairline:1px;--border-width-fine:0.5px;--text-primary:var(--color-text);--text-inverse:var(--color-text-inverse);--text-muted:var(--color-text-muted);--text-muted-strong:var(--color-muted-strong);--text-subtle:var(--color-subtle-text);--text-frosted-primary:var(--color-text-frosted);--text-frosted-strong:var(--color-text-frosted-strong);--text-frosted-soft:var(--color-text-frosted-soft);--status-info:var(--color-info);--status-info-strong:var(--color-info-strong);--status-info-surface:var(--color-info-glass);--status-info-border:var(--color-info-border);--status-info-on:var(--color-info-on);--status-success:var(--color-success);--status-success-strong:var(--color-success-strong);--status-success-surface:var(--color-success-surface);--status-success-border:var(--color-success-border);--status-success-on:var(--color-success-on);--status-warning:var(--color-warning);--status-warning-strong:var(--color-warning-strong);--status-warning-surface:var(--color-warning-surface);--status-warning-border:var(--color-warning-border);--status-warning-on:var(--color-warning-on);--status-danger:var(--color-critical);--status-danger-strong:var(--color-critical-strong);--status-danger-surface:var(--color-danger-surface);--status-danger-border:var(--color-danger-border);--status-danger-on:var(--color-danger-on);--status-private:var(--color-private);--status-private-strong:var(--color-private-strong);--status-private-surface:var(--color-private-surface);--status-private-border:var(--color-private-border);--status-private-on:var(--color-private-on);--status-neutral:var(--color-muted);--status-neutral-strong:var(--color-muted-strong);--status-neutral-surface:var(--color-neutral-surface);--status-neutral-border:var(--color-neutral-border);--status-neutral-on:var(--color-neutral-on);--scrollbar-width:0.5rem;--scrollbar-track:var(--color-white-soft);--scrollbar-thumb:var(--color-white-softer);--scrollbar-thumb-hover:var(--color-white-softest);--z-overlay-nav:60;--z-overlay-floating:70;--z-overlay-popover:140;--z-overlay-toast:180;--z-sidebar-overlay:10;--z-sidebar:20;--z-modal-root:100;--z-modal-overlay:110;--z-modal-content:120;--z-modal-player:130;--z-modal-top-root:140;--z-modal-top-overlay:150;--z-modal-top-content:160;--overlay-backdrop-color:var(--color-overlay-backdrop);--overlay-backdrop-blur:12px;--sidebar-width-expanded:16rem;--sidebar-width-collapsed:4rem;--sidebar-width:var(--sidebar-width-expanded);--sidebar-control-size:2.75rem;--sidebar-content-gap:1rem;--space-sidebar-shell-inline:var(--space-lg);--space-sidebar-shell-block-start:var(--space-lg);--space-sidebar-shell-block-end:1.75rem;--space-sidebar-scroll-gap:1.75rem;--space-sidebar-panel-gap:1.35rem;--space-sidebar-panel-padding-block:1.35rem;--space-sidebar-panel-padding-inline:1.1rem;--space-sidebar-footer-gap:0.85rem;--space-sidebar-trigger-padding-block:0.78rem;--space-sidebar-popover-offLine truncated
|
||||
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{font-size:100%}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-width)}::-webkit-scrollbar-track{background:var(--scrollbar-track)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{font-family:var(--font-family-sans);background-color:var(--color-page);color:var(--color-text-strong);line-height:1.5;margin:0;padding:0;overflow-x:hidden}#uiOverlay{position:fixed;inset:0;pointer-events:none;z-index:var(--z-overlay-popover)}#uiOverlay>*{pointer-events:auto}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar{pointer-events:none;opacity:0;visibility:hidden;z-index:var(--z-sidebar-overlay)}.ng-cloak,.ng-hide:not(.ng-hide-animate),.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}ng\:form{display:block}.\!container{width:100%!important}.container{width:100%}@media (min-width:25rem){.\!container{max-width:25rem!important}.container{max-width:25rem}}@media (min-width:30rem){.\!container{max-width:30rem!important}.container{max-width:30rem}}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}#playerModal.ds-header{display:flex}.ds-header{display:flex;align-items:center;justify-content:flex-start;padding-top:var(--space-sm);padding-bottom:var(--space-sm)}.ds-metric-probe{position:absolute;top:0;left:0;visibility:hidden;pointer-events:none;contain:layout size style;inline-size:0;block-size:var(--ds-metric-probe-length,0);overflow:hidden}.ds-header__logo{height:var(--space-2xl-plus);width:auto;max-width:none}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offsLine truncated
|
||||
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{font-size:100%}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-width)}::-webkit-scrollbar-track{background:var(--scrollbar-track)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{font-family:var(--font-family-sans);background-color:var(--color-page);color:var(--color-text-strong);line-height:1.5;margin:0;padding:0;overflow-x:hidden}#uiOverlay{position:fixed;inset:0;pointer-events:none;z-index:var(--z-overlay-popover)}#uiOverlay>*{pointer-events:auto}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar{pointer-events:none;opacity:0;visibility:hidden;z-index:var(--z-sidebar-overlay)}.ng-cloak,.ng-hide:not(.ng-hide-animate),.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}ng\:form{display:block}.\!container{width:100%!important}.container{width:100%}@media (min-width:25rem){.\!container{max-width:25rem!important}.container{max-width:25rem}}@media (min-width:30rem){.\!container{max-width:30rem!important}.container{max-width:30rem}}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}#playerModal.ds-header{display:flex}.ds-header{display:flex;align-items:center;justify-content:flex-start;padding-top:var(--space-sm);padding-bottom:var(--space-sm)}.ds-metric-probe{position:absolute;top:0;left:0;visibility:hidden;pointer-events:none;contain:layout size style;inline-size:0;block-size:var(--ds-metric-probe-length,0);overflow:hidden}.ds-header__logo{height:var(--space-2xl-plus);width:auto;max-width:none}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offsLine truncated
|
||||
@@ -2055,6 +2055,11 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.notification-portal--active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.notification-banner,
|
||||
.torrent-toast {
|
||||
--notify-accent: var(--color-accent-strong);
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
ingestLocalViewEvent,
|
||||
} from "./viewCounter.js";
|
||||
import { splitAndZap as splitAndZapDefault } from "./payments/zapSplit.js";
|
||||
import { showLoginRequiredToZapNotification } from "./payments/zapNotifications.js";
|
||||
import {
|
||||
formatAbsoluteTimestamp as formatAbsoluteTimestampUtil,
|
||||
formatTimeAgo as formatTimeAgoUtil,
|
||||
@@ -208,6 +209,7 @@ class Application {
|
||||
this.boundVideoModalCreatorHandler = null;
|
||||
this.boundVideoModalZapHandler = null;
|
||||
this.boundVideoModalZapWalletHandler = null;
|
||||
this.pendingModalZapOpen = false;
|
||||
this.videoListViewPlaybackHandler = null;
|
||||
this.videoListViewEditHandler = null;
|
||||
this.videoListViewRevertHandler = null;
|
||||
@@ -337,17 +339,50 @@ class Application {
|
||||
logger: devLogger,
|
||||
});
|
||||
this.authEventUnsubscribes.push(
|
||||
this.authService.on("auth:login", (detail) => this.handleAuthLogin(detail))
|
||||
this.authService.on("auth:login", (detail) => {
|
||||
const maybePromise = this.handleAuthLogin(detail);
|
||||
if (maybePromise && typeof maybePromise.then === "function") {
|
||||
maybePromise.catch((error) => {
|
||||
devLogger.error("Failed to process auth login event:", error);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
this.authEventUnsubscribes.push(
|
||||
this.authService.on("auth:logout", (detail) =>
|
||||
this.handleAuthLogout(detail)
|
||||
)
|
||||
this.authService.on("auth:logout", (detail) => {
|
||||
if (detail && typeof detail === "object") {
|
||||
if (detail.__handled === true) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
detail.__handled = true;
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"Failed to mark auth logout detail as handled:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const maybePromise = this.handleAuthLogout(detail);
|
||||
if (maybePromise && typeof maybePromise.then === "function") {
|
||||
maybePromise.catch((error) => {
|
||||
devLogger.error("Failed to process auth logout event:", error);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
this.authEventUnsubscribes.push(
|
||||
this.authService.on("profile:updated", (detail) =>
|
||||
this.handleProfileUpdated(detail)
|
||||
)
|
||||
this.authService.on("profile:updated", (detail) => {
|
||||
try {
|
||||
this.handleProfileUpdated(detail);
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"Failed to process profile update event:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Optional: a "profile" button or avatar (if used)
|
||||
@@ -502,7 +537,7 @@ class Application {
|
||||
|
||||
const profileModalCallbacks = {
|
||||
onClose: () => this.handleProfileModalClosed(),
|
||||
onLogout: async () => this.authService.logout(),
|
||||
onLogout: async () => this.requestLogout(),
|
||||
onChannelLink: (element) => this.handleProfileChannelLink(element),
|
||||
onAddAccount: (controller) => this.handleAddProfile(controller),
|
||||
onRequestSwitchProfile: (payload) =>
|
||||
@@ -590,6 +625,13 @@ class Application {
|
||||
typeof nostrClient?.sessionActor?.pubkey === "string" &&
|
||||
nostrClient.sessionActor.pubkey.trim()
|
||||
),
|
||||
notifyLoginRequired: () =>
|
||||
showLoginRequiredToZapNotification({
|
||||
app: this,
|
||||
document:
|
||||
this.statusContainer?.ownerDocument ||
|
||||
(typeof document !== "undefined" ? document : null),
|
||||
}),
|
||||
splitAndZap: (...args) => this.splitAndZap(...args),
|
||||
payments: this.payments,
|
||||
callbacks: {
|
||||
@@ -647,8 +689,14 @@ class Application {
|
||||
this.boundVideoModalZapHandler = (event) => {
|
||||
this.zapController?.sendZap(event?.detail || {});
|
||||
};
|
||||
this.boundVideoModalZapOpenHandler = () => {
|
||||
this.zapController?.open();
|
||||
this.boundVideoModalZapOpenHandler = (event) => {
|
||||
const requiresLogin = Boolean(event?.detail?.requiresLogin);
|
||||
if (requiresLogin) {
|
||||
this.pendingModalZapOpen = true;
|
||||
} else {
|
||||
this.pendingModalZapOpen = false;
|
||||
}
|
||||
this.zapController?.open({ requiresLogin });
|
||||
};
|
||||
this.boundVideoModalZapCloseHandler = () => {
|
||||
this.zapController?.close();
|
||||
@@ -740,11 +788,14 @@ class Application {
|
||||
this.subscriptionsLink = null;
|
||||
|
||||
// Notification containers
|
||||
this.notificationPortal =
|
||||
document.getElementById("notificationPortal") || null;
|
||||
this.errorContainer = document.getElementById("errorContainer") || null;
|
||||
this.successContainer = document.getElementById("successContainer") || null;
|
||||
this.statusContainer = document.getElementById("statusContainer") || null;
|
||||
this.statusMessage =
|
||||
this.statusContainer?.querySelector("[data-status-message]") || null;
|
||||
this.statusAutoHideHandle = null;
|
||||
|
||||
// Auth state
|
||||
this.pubkey = null;
|
||||
@@ -1986,7 +2037,7 @@ class Application {
|
||||
if (this.logoutButton) {
|
||||
this.logoutButton.addEventListener("click", async () => {
|
||||
try {
|
||||
await this.authService.logout();
|
||||
await this.requestLogout();
|
||||
} catch (error) {
|
||||
devLogger.error("Logout failed:", error);
|
||||
this.showError("Failed to logout. Please try again.");
|
||||
@@ -3150,6 +3201,27 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
const shouldReopenZap = this.pendingModalZapOpen;
|
||||
this.pendingModalZapOpen = false;
|
||||
if (shouldReopenZap) {
|
||||
const hasLightning = Boolean(this.currentVideo?.lightningAddress);
|
||||
if (hasLightning && this.videoModal?.openZapDialog) {
|
||||
Promise.resolve()
|
||||
.then(() => this.videoModal.openZapDialog())
|
||||
.then((opened) => {
|
||||
if (opened) {
|
||||
this.zapController?.open();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to reopen zap dialog after login:",
|
||||
error,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatchAuthChange({
|
||||
status: "login",
|
||||
loggedIn: true,
|
||||
@@ -3225,8 +3297,36 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
async requestLogout() {
|
||||
const detail = await this.authService.logout();
|
||||
|
||||
if (detail && typeof detail === "object") {
|
||||
if (detail.__handled === true) {
|
||||
return detail;
|
||||
}
|
||||
|
||||
try {
|
||||
detail.__handled = true;
|
||||
} catch (error) {
|
||||
devLogger.warn("Failed to mark logout detail as handled:", error);
|
||||
}
|
||||
}
|
||||
|
||||
await this.handleAuthLogout(detail);
|
||||
return detail ?? null;
|
||||
}
|
||||
|
||||
async handleAuthLogout(detail = {}) {
|
||||
if (detail && typeof detail === "object") {
|
||||
try {
|
||||
detail.__handled = true;
|
||||
} catch (error) {
|
||||
devLogger.warn("Failed to mark logout detail as handled:", error);
|
||||
}
|
||||
}
|
||||
|
||||
this.resetViewLoggingState();
|
||||
this.pendingModalZapOpen = false;
|
||||
|
||||
await this.nwcSettingsService.onLogout({
|
||||
pubkey: detail?.pubkey || this.pubkey,
|
||||
@@ -7211,61 +7311,178 @@ class Application {
|
||||
return escapeHtml(unsafe);
|
||||
}
|
||||
|
||||
updateNotificationPortalVisibility() {
|
||||
const portal = this.notificationPortal;
|
||||
const HTMLElementCtor =
|
||||
portal?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!portal || !HTMLElementCtor || !(portal instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containers = [
|
||||
this.errorContainer,
|
||||
this.statusContainer,
|
||||
this.successContainer,
|
||||
];
|
||||
|
||||
const hasVisibleBanner = containers.some((container) => {
|
||||
if (!container || !(container instanceof HTMLElementCtor)) {
|
||||
return false;
|
||||
}
|
||||
return !container.classList.contains("hidden");
|
||||
});
|
||||
|
||||
portal.classList.toggle("notification-portal--active", hasVisibleBanner);
|
||||
}
|
||||
|
||||
showError(msg) {
|
||||
const container = this.errorContainer;
|
||||
const HTMLElementCtor =
|
||||
container?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
if (msg) {
|
||||
userLogger.error(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
// Remove any content, then hide
|
||||
this.errorContainer.textContent = "";
|
||||
this.errorContainer.classList.add("hidden");
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a message, show it
|
||||
this.errorContainer.textContent = msg;
|
||||
this.errorContainer.classList.remove("hidden");
|
||||
container.textContent = msg;
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
userLogger.error(msg);
|
||||
|
||||
// Optional auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
this.errorContainer.textContent = "";
|
||||
this.errorContainer.classList.add("hidden");
|
||||
if (container !== this.errorContainer) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
showStatus(msg) {
|
||||
if (!(this.statusContainer instanceof HTMLElement)) {
|
||||
showStatus(msg, options = {}) {
|
||||
const container = this.statusContainer;
|
||||
const messageTarget = this.statusMessage;
|
||||
const ownerDocument =
|
||||
container?.ownerDocument || (typeof document !== "undefined" ? document : null);
|
||||
const defaultView =
|
||||
ownerDocument?.defaultView || (typeof window !== "undefined" ? window : null);
|
||||
const clearScheduler =
|
||||
defaultView?.clearTimeout ||
|
||||
(typeof clearTimeout === "function" ? clearTimeout : null);
|
||||
const schedule =
|
||||
defaultView?.setTimeout || (typeof setTimeout === "function" ? setTimeout : null);
|
||||
|
||||
if (this.statusAutoHideHandle && typeof clearScheduler === "function") {
|
||||
clearScheduler(this.statusAutoHideHandle);
|
||||
this.statusAutoHideHandle = null;
|
||||
}
|
||||
|
||||
const HTMLElementCtor =
|
||||
container?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
if (this.statusMessage instanceof HTMLElement) {
|
||||
this.statusMessage.textContent = "";
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = "";
|
||||
}
|
||||
this.statusContainer.classList.add("hidden");
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.statusMessage instanceof HTMLElement) {
|
||||
this.statusMessage.textContent = msg;
|
||||
const { autoHideMs } =
|
||||
options && typeof options === "object" ? options : Object.create(null);
|
||||
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = msg;
|
||||
} else {
|
||||
this.statusContainer.textContent = msg;
|
||||
container.textContent = msg;
|
||||
}
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
if (
|
||||
Number.isFinite(autoHideMs) &&
|
||||
autoHideMs > 0 &&
|
||||
typeof schedule === "function"
|
||||
) {
|
||||
const expectedText =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget.textContent
|
||||
: container.textContent;
|
||||
this.statusAutoHideHandle = schedule(() => {
|
||||
this.statusAutoHideHandle = null;
|
||||
const activeContainer = this.statusContainer;
|
||||
if (activeContainer !== container) {
|
||||
return;
|
||||
}
|
||||
const activeMessageTarget =
|
||||
this.statusMessage && typeof this.statusMessage.textContent === "string"
|
||||
? this.statusMessage
|
||||
: activeContainer;
|
||||
if (activeMessageTarget.textContent !== expectedText) {
|
||||
return;
|
||||
}
|
||||
if (activeMessageTarget === this.statusMessage) {
|
||||
this.statusMessage.textContent = "";
|
||||
} else {
|
||||
activeContainer.textContent = "";
|
||||
}
|
||||
activeContainer.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, autoHideMs);
|
||||
}
|
||||
this.statusContainer.classList.remove("hidden");
|
||||
}
|
||||
|
||||
showSuccess(msg) {
|
||||
if (!msg) {
|
||||
this.successContainer.textContent = "";
|
||||
this.successContainer.classList.add("hidden");
|
||||
const container = this.successContainer;
|
||||
const HTMLElementCtor =
|
||||
container?.ownerDocument?.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!container || !HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.successContainer.textContent = msg;
|
||||
this.successContainer.classList.remove("hidden");
|
||||
if (!msg) {
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
container.textContent = msg;
|
||||
container.classList.remove("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
|
||||
setTimeout(() => {
|
||||
this.successContainer.textContent = "";
|
||||
this.successContainer.classList.add("hidden");
|
||||
if (container !== this.successContainer) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
container.classList.add("hidden");
|
||||
this.updateNotificationPortalVisibility();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
@@ -7288,6 +7505,22 @@ class Application {
|
||||
this.teardownModalViewCountSubscription();
|
||||
this.videoModalReadyPromise = null;
|
||||
|
||||
if (this.statusAutoHideHandle) {
|
||||
const ownerDocument =
|
||||
this.statusContainer?.ownerDocument ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
const defaultView =
|
||||
ownerDocument?.defaultView ||
|
||||
(typeof window !== "undefined" ? window : null);
|
||||
const clearScheduler =
|
||||
defaultView?.clearTimeout ||
|
||||
(typeof clearTimeout === "function" ? clearTimeout : null);
|
||||
if (typeof clearScheduler === "function") {
|
||||
clearScheduler(this.statusAutoHideHandle);
|
||||
}
|
||||
this.statusAutoHideHandle = null;
|
||||
}
|
||||
|
||||
if (this.watchHistoryTelemetry) {
|
||||
try {
|
||||
this.watchHistoryTelemetry.destroy();
|
||||
|
||||
+27
-4
@@ -33,6 +33,7 @@ import {
|
||||
validateInvoiceAmount
|
||||
} from "./payments/zapSharedState.js";
|
||||
import { splitAndZap } from "./payments/zapSplit.js";
|
||||
import { showLoginRequiredToZapNotification } from "./payments/zapNotifications.js";
|
||||
import {
|
||||
resolveLightningAddress,
|
||||
fetchPayServiceData,
|
||||
@@ -55,6 +56,7 @@ const getApp = () => getApplication();
|
||||
let currentChannelHex = null;
|
||||
let currentChannelNpub = null;
|
||||
let currentChannelLightningAddress = "";
|
||||
let channelZapPendingOpen = false;
|
||||
|
||||
const summarizeZapTracker = (tracker) =>
|
||||
Array.isArray(tracker)
|
||||
@@ -1755,13 +1757,19 @@ function handleZapButtonClick(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSessionActorWithoutLogin()) {
|
||||
const app = getApp();
|
||||
const message = "Log in with your Nostr profile to send zaps.";
|
||||
app?.showError?.(message);
|
||||
const requiresLogin = zapButton.dataset?.requiresLogin === "true";
|
||||
|
||||
if (requiresLogin || isSessionActorWithoutLogin()) {
|
||||
channelZapPendingOpen = true;
|
||||
const doc =
|
||||
zapButton?.ownerDocument ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
showLoginRequiredToZapNotification({ document: doc });
|
||||
return;
|
||||
}
|
||||
|
||||
channelZapPendingOpen = false;
|
||||
|
||||
if (!zapPopover || zapPopoverTrigger !== zapButton) {
|
||||
setupZapButton({ force: true });
|
||||
}
|
||||
@@ -3883,6 +3891,7 @@ window.addEventListener("bitvid:auth-changed", (event) => {
|
||||
resetZapRetryState();
|
||||
setZapStatus("", "neutral");
|
||||
clearZapReceipts();
|
||||
channelZapPendingOpen = false;
|
||||
const amountInput = getZapAmountInput();
|
||||
if (amountInput) {
|
||||
amountInput.value = "";
|
||||
@@ -3894,6 +3903,20 @@ window.addEventListener("bitvid:auth-changed", (event) => {
|
||||
zapInFlight = false;
|
||||
resetZapRetryState();
|
||||
setZapStatus("", "neutral");
|
||||
if (channelZapPendingOpen) {
|
||||
channelZapPendingOpen = false;
|
||||
const zapButton = getChannelZapButton();
|
||||
const requiresLogin =
|
||||
zapButton?.dataset?.requiresLogin === "true" ||
|
||||
zapButton?.hasAttribute("hidden");
|
||||
if (zapButton && !requiresLogin) {
|
||||
const opened = openZapControls({ focus: true });
|
||||
if (!opened) {
|
||||
setupZapButton({ force: true });
|
||||
openZapControls({ focus: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// js/payments/zapMessages.js
|
||||
|
||||
export const LOGIN_TO_ZAP_MESSAGE =
|
||||
"Log in and connect a wallet to send zaps.";
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
// js/payments/zapNotifications.js
|
||||
|
||||
import { getApplication } from "../applicationContext.js";
|
||||
import { LOGIN_TO_ZAP_MESSAGE } from "./zapMessages.js";
|
||||
|
||||
const LOGIN_NOTIFICATION_AUTO_HIDE_MS = 5000;
|
||||
|
||||
function getOwnerDocument(node) {
|
||||
return node?.ownerDocument || null;
|
||||
}
|
||||
|
||||
function isNotificationBannerActive({
|
||||
container,
|
||||
messageTarget,
|
||||
expectedText,
|
||||
}) {
|
||||
const doc = getOwnerDocument(container);
|
||||
const HTMLElementCtor = resolveHTMLElementCtor(doc);
|
||||
|
||||
if (!HTMLElementCtor || !(container instanceof HTMLElementCtor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container.classList.contains("hidden")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const resolvedTarget =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget
|
||||
: container;
|
||||
|
||||
const text =
|
||||
typeof resolvedTarget?.textContent === "string"
|
||||
? resolvedTarget.textContent.trim()
|
||||
: "";
|
||||
|
||||
return text === expectedText;
|
||||
}
|
||||
|
||||
function attemptStatusBanner(app, { autoHideMs, expectedText }) {
|
||||
if (!app || typeof app.showStatus !== "function") {
|
||||
return false;
|
||||
}
|
||||
|
||||
app.showStatus(expectedText, { autoHideMs });
|
||||
|
||||
return isNotificationBannerActive({
|
||||
container: app.statusContainer,
|
||||
messageTarget: app.statusMessage,
|
||||
expectedText,
|
||||
});
|
||||
}
|
||||
|
||||
function attemptErrorBanner(app, { expectedText }) {
|
||||
if (!app || typeof app.showError !== "function") {
|
||||
return false;
|
||||
}
|
||||
|
||||
app.showError(expectedText);
|
||||
|
||||
return isNotificationBannerActive({
|
||||
container: app.errorContainer,
|
||||
messageTarget: null,
|
||||
expectedText,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveDocument(candidate) {
|
||||
if (candidate && typeof candidate === "object") {
|
||||
return candidate;
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
return document;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveHTMLElementCtor(doc) {
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
doc.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null)
|
||||
);
|
||||
}
|
||||
|
||||
export function syncNotificationPortalVisibility(docCandidate) {
|
||||
const doc = resolveDocument(docCandidate);
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HTMLElementCtor = resolveHTMLElementCtor(doc);
|
||||
if (!HTMLElementCtor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const portal = doc.getElementById("notificationPortal");
|
||||
if (!portal || !(portal instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const banners = portal.querySelectorAll(".notification-banner");
|
||||
const hasVisibleBanner = Array.from(banners).some((banner) => {
|
||||
return (
|
||||
banner instanceof HTMLElementCtor && !banner.classList.contains("hidden")
|
||||
);
|
||||
});
|
||||
|
||||
portal.classList.toggle("notification-portal--active", hasVisibleBanner);
|
||||
}
|
||||
|
||||
function scheduleAutoHide({
|
||||
container,
|
||||
messageTarget,
|
||||
expectedText,
|
||||
doc,
|
||||
autoHideMs,
|
||||
}) {
|
||||
if (!Number.isFinite(autoHideMs) || autoHideMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scheduler =
|
||||
doc?.defaultView?.setTimeout ||
|
||||
(typeof setTimeout === "function" ? setTimeout : null);
|
||||
|
||||
if (typeof scheduler !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler(() => {
|
||||
if (!doc || typeof doc.contains !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doc.contains(container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HTMLElementCtor = resolveHTMLElementCtor(doc);
|
||||
if (!HTMLElementCtor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeTarget =
|
||||
container.querySelector("[data-status-message]") || messageTarget;
|
||||
const resolvedTarget =
|
||||
activeTarget && activeTarget instanceof HTMLElementCtor
|
||||
? activeTarget
|
||||
: messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget
|
||||
: container;
|
||||
|
||||
if (resolvedTarget.textContent !== expectedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolvedTarget !== container) {
|
||||
resolvedTarget.textContent = "";
|
||||
} else {
|
||||
container.textContent = "";
|
||||
}
|
||||
|
||||
container.classList.add("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
}, autoHideMs);
|
||||
}
|
||||
|
||||
export function showLoginRequiredToZapNotification({
|
||||
app: appCandidate,
|
||||
document: docCandidate,
|
||||
autoHideMs = LOGIN_NOTIFICATION_AUTO_HIDE_MS,
|
||||
} = {}) {
|
||||
const expectedText = LOGIN_TO_ZAP_MESSAGE;
|
||||
const app = appCandidate || getApplication();
|
||||
|
||||
const resolvedDocCandidate =
|
||||
docCandidate ||
|
||||
getOwnerDocument(app?.statusContainer) ||
|
||||
getOwnerDocument(app?.errorContainer);
|
||||
|
||||
if (attemptStatusBanner(app, { autoHideMs, expectedText })) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attemptErrorBanner(app, { expectedText })) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const doc = resolveDocument(resolvedDocCandidate);
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HTMLElementCtor = resolveHTMLElementCtor(doc);
|
||||
if (!HTMLElementCtor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const statusContainer = doc.getElementById("statusContainer");
|
||||
|
||||
if (statusContainer instanceof HTMLElementCtor) {
|
||||
const messageTarget =
|
||||
statusContainer.querySelector("[data-status-message]") || null;
|
||||
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = expectedText;
|
||||
} else {
|
||||
statusContainer.textContent = expectedText;
|
||||
}
|
||||
|
||||
statusContainer.classList.remove("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
|
||||
const resolvedText =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget.textContent
|
||||
: statusContainer.textContent;
|
||||
|
||||
scheduleAutoHide({
|
||||
container: statusContainer,
|
||||
messageTarget,
|
||||
expectedText: resolvedText,
|
||||
doc,
|
||||
autoHideMs,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const errorContainer = doc.getElementById("errorContainer");
|
||||
|
||||
if (errorContainer instanceof HTMLElementCtor) {
|
||||
errorContainer.textContent = expectedText;
|
||||
errorContainer.classList.remove("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
|
||||
scheduleAutoHide({
|
||||
container: errorContainer,
|
||||
messageTarget: null,
|
||||
expectedText,
|
||||
doc,
|
||||
autoHideMs,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
+23
-3
@@ -1,4 +1,5 @@
|
||||
import { isDevMode } from "../config.js";
|
||||
import { LOGIN_TO_ZAP_MESSAGE } from "../payments/zapMessages.js";
|
||||
import {
|
||||
calculateZapShares,
|
||||
fetchLightningMetadata,
|
||||
@@ -36,6 +37,7 @@ export default class ZapController {
|
||||
hasSessionActor,
|
||||
hasActiveWalletConnection,
|
||||
splitAndZap,
|
||||
notifyLoginRequired,
|
||||
payments = {},
|
||||
callbacks = {},
|
||||
requestWalletPane,
|
||||
@@ -74,6 +76,8 @@ export default class ZapController {
|
||||
}
|
||||
this.splitAndZap =
|
||||
typeof splitAndZap === "function" ? splitAndZap : () => Promise.resolve();
|
||||
this.notifyLoginRequired =
|
||||
typeof notifyLoginRequired === "function" ? notifyLoginRequired : null;
|
||||
this.payments = payments || {};
|
||||
this.callbacks = {
|
||||
onSuccess: typeof callbacks.onSuccess === "function" ? callbacks.onSuccess : null,
|
||||
@@ -127,16 +131,32 @@ export default class ZapController {
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
open(options = {}) {
|
||||
const config =
|
||||
options && typeof options === "object" ? options : Object.create(null);
|
||||
const requiresLogin = Boolean(config.requiresLogin);
|
||||
this.resetFeedback();
|
||||
if (requiresLogin) {
|
||||
if (this.notifyLoginRequired) {
|
||||
this.notifyLoginRequired();
|
||||
} else {
|
||||
this.notifyError(LOGIN_TO_ZAP_MESSAGE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!this.isUserLoggedIn()) {
|
||||
this.notifyError("Log in with your Nostr profile to send zaps.");
|
||||
return;
|
||||
if (this.notifyLoginRequired) {
|
||||
this.notifyLoginRequired();
|
||||
} else {
|
||||
this.notifyError(LOGIN_TO_ZAP_MESSAGE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
this.applyDefaultAmount();
|
||||
this.preloadLightningMetadata().catch((error) => {
|
||||
devLogger.warn("[zap] Preload metadata error:", error);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
Reference in new issue
Block a user