mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-31 18:02:27 +00:00
Fix zap login notification handling
This commit is contained in:
1 parent
841f1dd7f5
commit
72033a900b
4 files changed
+262
-166
No files matched your search
@@ -57,7 +57,7 @@ import {
|
||||
ingestLocalViewEvent,
|
||||
} from "./viewCounter.js";
|
||||
import { splitAndZap as splitAndZapDefault } from "./payments/zapSplit.js";
|
||||
import { LOGIN_TO_ZAP_MESSAGE } from "./payments/zapMessages.js";
|
||||
import { showLoginRequiredToZapNotification } from "./payments/zapNotifications.js";
|
||||
import {
|
||||
formatAbsoluteTimestamp as formatAbsoluteTimestampUtil,
|
||||
formatTimeAgo as formatTimeAgoUtil,
|
||||
@@ -209,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;
|
||||
@@ -591,6 +592,8 @@ class Application {
|
||||
typeof nostrClient?.sessionActor?.pubkey === "string" &&
|
||||
nostrClient.sessionActor.pubkey.trim()
|
||||
),
|
||||
notifyLoginRequired: () =>
|
||||
showLoginRequiredToZapNotification({ app: this }),
|
||||
splitAndZap: (...args) => this.splitAndZap(...args),
|
||||
payments: this.payments,
|
||||
callbacks: {
|
||||
@@ -651,9 +654,11 @@ class Application {
|
||||
this.boundVideoModalZapOpenHandler = (event) => {
|
||||
const requiresLogin = Boolean(event?.detail?.requiresLogin);
|
||||
if (requiresLogin) {
|
||||
this.showStatus(LOGIN_TO_ZAP_MESSAGE, { autoHideMs: 5000 });
|
||||
this.pendingModalZapOpen = true;
|
||||
} else {
|
||||
this.pendingModalZapOpen = false;
|
||||
}
|
||||
this.zapController?.open();
|
||||
this.zapController?.open({ requiresLogin });
|
||||
};
|
||||
this.boundVideoModalZapCloseHandler = () => {
|
||||
this.zapController?.close();
|
||||
@@ -3158,6 +3163,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,
|
||||
@@ -3235,6 +3261,7 @@ class Application {
|
||||
|
||||
async handleAuthLogout(detail = {}) {
|
||||
this.resetViewLoggingState();
|
||||
this.pendingModalZapOpen = false;
|
||||
|
||||
await this.nwcSettingsService.onLogout({
|
||||
pubkey: detail?.pubkey || this.pubkey,
|
||||
|
||||
+20
-160
@@ -33,7 +33,7 @@ import {
|
||||
validateInvoiceAmount
|
||||
} from "./payments/zapSharedState.js";
|
||||
import { splitAndZap } from "./payments/zapSplit.js";
|
||||
import { LOGIN_TO_ZAP_MESSAGE } from "./payments/zapMessages.js";
|
||||
import { showLoginRequiredToZapNotification } from "./payments/zapNotifications.js";
|
||||
import {
|
||||
resolveLightningAddress,
|
||||
fetchPayServiceData,
|
||||
@@ -48,168 +48,10 @@ import {
|
||||
|
||||
const getApp = () => getApplication();
|
||||
|
||||
const LOGIN_NOTIFICATION_AUTO_HIDE_MS = 5000;
|
||||
|
||||
function syncNotificationPortalVisibility(doc) {
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HTMLElementCtor =
|
||||
doc.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
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) =>
|
||||
banner instanceof HTMLElementCtor && !banner.classList.contains("hidden"),
|
||||
);
|
||||
|
||||
portal.classList.toggle("notification-portal--active", hasVisibleBanner);
|
||||
}
|
||||
|
||||
function showLoginRequiredToZapNotification() {
|
||||
const app = getApp();
|
||||
if (app) {
|
||||
if (typeof app.showStatus === "function") {
|
||||
app.showStatus(LOGIN_TO_ZAP_MESSAGE, {
|
||||
autoHideMs: LOGIN_NOTIFICATION_AUTO_HIDE_MS,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof app.showError === "function") {
|
||||
app.showError(LOGIN_TO_ZAP_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const doc = typeof document !== "undefined" ? document : null;
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HTMLElementCtor =
|
||||
doc.defaultView?.HTMLElement ||
|
||||
(typeof HTMLElement !== "undefined" ? HTMLElement : null);
|
||||
|
||||
if (!HTMLElementCtor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statusContainer = doc.getElementById("statusContainer");
|
||||
|
||||
if (statusContainer && statusContainer instanceof HTMLElementCtor) {
|
||||
const messageTarget =
|
||||
statusContainer.querySelector("[data-status-message]") || null;
|
||||
|
||||
if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = LOGIN_TO_ZAP_MESSAGE;
|
||||
} else {
|
||||
statusContainer.textContent = LOGIN_TO_ZAP_MESSAGE;
|
||||
}
|
||||
|
||||
statusContainer.classList.remove("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
|
||||
const schedule =
|
||||
doc.defaultView?.setTimeout ||
|
||||
(typeof setTimeout === "function" ? setTimeout : null);
|
||||
|
||||
if (typeof schedule === "function") {
|
||||
const initialTarget =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget
|
||||
: statusContainer;
|
||||
const expectedText = initialTarget.textContent;
|
||||
schedule(() => {
|
||||
if (!doc || typeof doc.contains !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doc.contains(statusContainer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeTarget =
|
||||
statusContainer.querySelector("[data-status-message]") || null;
|
||||
const resolvedTarget =
|
||||
activeTarget && activeTarget instanceof HTMLElementCtor
|
||||
? activeTarget
|
||||
: messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget
|
||||
: statusContainer;
|
||||
|
||||
if (resolvedTarget.textContent !== expectedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTarget && activeTarget instanceof HTMLElementCtor) {
|
||||
activeTarget.textContent = "";
|
||||
} else if (messageTarget && messageTarget instanceof HTMLElementCtor) {
|
||||
messageTarget.textContent = "";
|
||||
} else {
|
||||
statusContainer.textContent = "";
|
||||
}
|
||||
|
||||
statusContainer.classList.add("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
}, LOGIN_NOTIFICATION_AUTO_HIDE_MS);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const errorContainer = doc.getElementById("errorContainer");
|
||||
|
||||
if (!errorContainer || !(errorContainer instanceof HTMLElementCtor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorContainer.textContent = LOGIN_TO_ZAP_MESSAGE;
|
||||
errorContainer.classList.remove("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
|
||||
const schedule =
|
||||
doc.defaultView?.setTimeout ||
|
||||
(typeof setTimeout === "function" ? setTimeout : null);
|
||||
|
||||
if (typeof schedule !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
schedule(() => {
|
||||
if (!doc || typeof doc.contains !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doc.contains(errorContainer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorContainer.textContent !== LOGIN_TO_ZAP_MESSAGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorContainer.textContent = "";
|
||||
errorContainer.classList.add("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
}, LOGIN_NOTIFICATION_AUTO_HIDE_MS);
|
||||
}
|
||||
|
||||
let currentChannelHex = null;
|
||||
let currentChannelNpub = null;
|
||||
let currentChannelLightningAddress = "";
|
||||
let channelZapPendingOpen = false;
|
||||
|
||||
const summarizeZapTracker = (tracker) =>
|
||||
Array.isArray(tracker)
|
||||
@@ -1913,10 +1755,13 @@ function handleZapButtonClick(event) {
|
||||
const requiresLogin = zapButton.dataset?.requiresLogin === "true";
|
||||
|
||||
if (requiresLogin || isSessionActorWithoutLogin()) {
|
||||
channelZapPendingOpen = true;
|
||||
showLoginRequiredToZapNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
channelZapPendingOpen = false;
|
||||
|
||||
if (!zapPopover || zapPopoverTrigger !== zapButton) {
|
||||
setupZapButton({ force: true });
|
||||
}
|
||||
@@ -4023,6 +3868,7 @@ window.addEventListener("bitvid:auth-changed", (event) => {
|
||||
resetZapRetryState();
|
||||
setZapStatus("", "neutral");
|
||||
clearZapReceipts();
|
||||
channelZapPendingOpen = false;
|
||||
const amountInput = getZapAmountInput();
|
||||
if (amountInput) {
|
||||
amountInput.value = "";
|
||||
@@ -4034,6 +3880,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,189 @@
|
||||
// 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 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 app = appCandidate || getApplication();
|
||||
|
||||
if (app && typeof app.showStatus === "function") {
|
||||
app.showStatus(LOGIN_TO_ZAP_MESSAGE, { autoHideMs });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (app && typeof app.showError === "function") {
|
||||
app.showError(LOGIN_TO_ZAP_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
|
||||
const doc = resolveDocument(docCandidate);
|
||||
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 = LOGIN_TO_ZAP_MESSAGE;
|
||||
} else {
|
||||
statusContainer.textContent = LOGIN_TO_ZAP_MESSAGE;
|
||||
}
|
||||
|
||||
statusContainer.classList.remove("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
|
||||
const expectedText =
|
||||
messageTarget && messageTarget instanceof HTMLElementCtor
|
||||
? messageTarget.textContent
|
||||
: statusContainer.textContent;
|
||||
|
||||
scheduleAutoHide({
|
||||
container: statusContainer,
|
||||
messageTarget,
|
||||
expectedText,
|
||||
doc,
|
||||
autoHideMs,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const errorContainer = doc.getElementById("errorContainer");
|
||||
|
||||
if (errorContainer instanceof HTMLElementCtor) {
|
||||
errorContainer.textContent = LOGIN_TO_ZAP_MESSAGE;
|
||||
errorContainer.classList.remove("hidden");
|
||||
syncNotificationPortalVisibility(doc);
|
||||
|
||||
scheduleAutoHide({
|
||||
container: errorContainer,
|
||||
messageTarget: null,
|
||||
expectedText: LOGIN_TO_ZAP_MESSAGE,
|
||||
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