diff --git a/backend/src/payment/providers/services/btcpay.service.ts b/backend/src/payment/providers/services/btcpay.service.ts index 7457aff..f187803 100644 --- a/backend/src/payment/providers/services/btcpay.service.ts +++ b/backend/src/payment/providers/services/btcpay.service.ts @@ -345,12 +345,13 @@ export class BTCPayService implements LightningService { async sendPaymentWithAddress( address: string, payment: Payment, + comment?: string, ): Promise { try { const sats = Math.floor(payment.milisatsAmount / 1000); // Resolve the Lightning address to a BOLT11 invoice - const bolt11 = await this.resolveLightningAddress(address, sats); + const bolt11 = await this.resolveLightningAddress(address, sats, comment); // Pay the BOLT11 via BTCPay's internal Lightning node const payUrl = this.storeUrl('/lightning/BTC/invoices/pay'); @@ -578,6 +579,7 @@ export class BTCPayService implements LightningService { private async resolveLightningAddress( address: string, amountSats: number, + comment?: string, ): Promise { const [username, domain] = address.split('@'); if (!username || !domain) { @@ -606,6 +608,16 @@ export class BTCPayService implements LightningService { const callbackUrl = new URL(lnurlData.callback); callbackUrl.searchParams.set('amount', amountMillisats.toString()); + // Include a descriptive comment if the endpoint supports it. + // LNURL-pay endpoints advertise commentAllowed (max char length). + const commentAllowed = lnurlData.commentAllowed || 0; + if (comment && commentAllowed > 0) { + callbackUrl.searchParams.set( + 'comment', + comment.slice(0, commentAllowed), + ); + } + const { data: invoiceData } = await axios.get(callbackUrl.toString(), { timeout: 10_000 }); if (invoiceData.status === 'ERROR') { diff --git a/backend/src/payment/providers/services/lightning.service.ts b/backend/src/payment/providers/services/lightning.service.ts index c02b20b..13e73fd 100644 --- a/backend/src/payment/providers/services/lightning.service.ts +++ b/backend/src/payment/providers/services/lightning.service.ts @@ -5,6 +5,7 @@ export interface LightningService { sendPaymentWithAddress( address: string, payment: Payment, + comment?: string, ): Promise; validateAddress(address: string): Promise; diff --git a/backend/src/payment/providers/services/strike.service.ts b/backend/src/payment/providers/services/strike.service.ts index 477baaa..62581dc 100644 --- a/backend/src/payment/providers/services/strike.service.ts +++ b/backend/src/payment/providers/services/strike.service.ts @@ -23,6 +23,7 @@ export class StrikeService implements LightningService { async sendPaymentWithAddress( address: string, payment: Payment, + comment?: string, currency: 'USD' | 'BTC' = 'BTC', ): Promise { try { @@ -39,7 +40,7 @@ export class StrikeService implements LightningService { : payment.usdAmount, currency, }, - description: isBlinkWallet ? undefined : 'Tip from IndeeHub', + description: isBlinkWallet ? undefined : (comment || 'Tip from IndeeHub'), }); const createPaymentConfig = { diff --git a/backend/src/payment/services/payment.service.ts b/backend/src/payment/services/payment.service.ts index d547460..a9312e1 100644 --- a/backend/src/payment/services/payment.service.ts +++ b/backend/src/payment/services/payment.service.ts @@ -233,7 +233,7 @@ export class PaymentService { order: { [column]: 'DESC', }, - relations: ['filmmaker', 'filmmaker.paymentMethods'], + relations: ['filmmaker', 'filmmaker.paymentMethods', 'content'], take: 5, }; @@ -347,10 +347,16 @@ export class PaymentService { } try { + // Build a descriptive comment for the Lightning invoice so the + // creator's wallet shows what the payment is for. + const contentTitle = shareholder.content?.title || 'content'; + const payoutComment = `IndeeHub ${type} payout - ${contentTitle}`; + this.logger.log(`[payout:${type}] Sending ${rounded} sats to ${selectedLightningAddress.lightningAddress}...`); const providerPayment = await this.provider.sendPaymentWithAddress( selectedLightningAddress.lightningAddress, payment, + payoutComment, ); payment.providerId = providerPayment.id; diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue index 87c7621..f49ad23 100644 --- a/src/components/AppHeader.vue +++ b/src/components/AppHeader.vue @@ -269,7 +269,28 @@ {{ userInitials }} {{ userName }} + + + + +
+
+ +
+ +
+
@@ -374,6 +395,29 @@ const hasNostrSession = computed(() => { return !!(sessionStorage.getItem('nostr_token') || sessionStorage.getItem('auth_token')) }) +/** + * Auto-detect stale auth state: backend thinks we're authenticated + * (sessionStorage tokens exist) but accountManager has no active + * Nostr account (localStorage was cleared). This causes a dead-end + * "Nostr" profile button with no dropdown. Fix by auto-logging out. + */ +watch( + [() => isAuthenticated.value, () => nostrLoggedIn.value], + ([authed, nostr]) => { + if (authed && !nostr) { + // Only auto-logout when the auth type is nostr-based — + // check if there's a nostr token but no active Nostr account + const hasNostrToken = !!sessionStorage.getItem('nostr_token') + const hasNostrPubkey = !!sessionStorage.getItem('nostr_pubkey') + if (hasNostrToken || hasNostrPubkey) { + console.warn('[AppHeader] Stale Nostr session detected (tokens exist but no active account). Auto-logging out.') + handleLogout() + } + } + }, + { immediate: true }, +) + /** Switch content source and reload */ function handleSourceSelect(sourceId: string) { contentSourceStore.setSource(sourceId as any) diff --git a/src/components/AuthModal.vue b/src/components/AuthModal.vue index 221bd28..f0600ac 100644 --- a/src/components/AuthModal.vue +++ b/src/components/AuthModal.vue @@ -64,27 +64,29 @@ - - + + + + + -