import { useEffect, useMemo, useState, useCallback } from 'react' import QRCode from 'react-qr-code' import type { AlbyInvoice } from '@/types/alby' import { getAlbyService, isWebLNAvailable } from '@/lib/alby' import { AlbyInstaller } from './AlbyInstaller' import { t } from '@/lib/i18n' interface PaymentModalProps { invoice: AlbyInvoice onClose: () => void onPaymentComplete: () => void } function useInvoiceTimer(expiresAt?: number): number | null { const [timeRemaining, setTimeRemaining] = useState(null) useEffect(() => { if (!expiresAt) { return } const updateTimeRemaining = (): void => { const now = Math.floor(Date.now() / 1000) const remaining = expiresAt - now setTimeRemaining(remaining > 0 ? remaining : 0) } updateTimeRemaining() const interval = setInterval(updateTimeRemaining, 1000) return () => clearInterval(interval) }, [expiresAt]) return timeRemaining } function PaymentHeader({ amount, timeRemaining, onClose, }: { amount: number timeRemaining: number | null onClose: () => void }): React.ReactElement { const timeLabel = useMemo((): string | null => { if (timeRemaining === null) { return null } if (timeRemaining <= 0) { return t('payment.expired') } const minutes = Math.floor(timeRemaining / 60) const secs = timeRemaining % 60 return `${minutes}:${secs.toString().padStart(2, '0')}` }, [timeRemaining]) return (

{t('payment.modal.zapAmount', { amount })}

{timeLabel && (

{t('payment.modal.timeRemaining', { time: timeLabel })}

)}
) } function InvoiceDisplay({ invoiceText, paymentUrl }: { invoiceText: string; paymentUrl: string }): React.ReactElement { return (

{t('payment.modal.lightningInvoice')}

{invoiceText}

{t('payment.modal.scanQr')}

) } function PaymentActions({ copied, onCopy, onOpenWallet, }: { copied: boolean onCopy: () => Promise onOpenWallet: () => void }): React.ReactElement { return (
) } function ExpiredNotice({ show }: { show: boolean }): React.ReactElement | null { if (!show) { return null } return (

{t('payment.modal.invoiceExpired')}

{t('payment.modal.invoiceExpiredHelp')}

) } type PaymentModalState = { copied: boolean errorMessage: string | null paymentUrl: string timeRemaining: number | null handleCopy: () => Promise handleOpenWallet: () => Promise } function usePaymentModalState(invoice: AlbyInvoice, onPaymentComplete: () => void): PaymentModalState { const [copied, setCopied] = useState(false) const [errorMessage, setErrorMessage] = useState(null) const paymentUrl = `lightning:${invoice.invoice}` const timeRemaining = useInvoiceTimer(invoice.expiresAt) const handleCopy = useCallback( createHandleCopy({ invoice: invoice.invoice, setCopied, setErrorMessage }), [invoice.invoice] ) const handleOpenWallet = useCallback( createHandleOpenWallet({ invoice: invoice.invoice, onPaymentComplete, setErrorMessage }), [invoice.invoice, onPaymentComplete] ) return { copied, errorMessage, paymentUrl, timeRemaining, handleCopy, handleOpenWallet } } function createHandleCopy(params: { invoice: string setCopied: (value: boolean) => void setErrorMessage: (value: string | null) => void }): () => Promise { return async (): Promise => { try { await navigator.clipboard.writeText(params.invoice) params.setCopied(true) scheduleCopiedReset(params.setCopied) } catch (e) { console.error('Failed to copy:', e) params.setErrorMessage(t('payment.modal.copyFailed')) } } } function createHandleOpenWallet(params: { invoice: string onPaymentComplete: () => void setErrorMessage: (value: string | null) => void }): () => Promise { return async (): Promise => { try { await payWithWebLN(params.invoice) params.onPaymentComplete() } catch (e) { const error = normalizePaymentError(e) if (isUserCancellationError(error)) { return } console.error('Payment failed:', error) params.setErrorMessage(error.message) } } } function normalizePaymentError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)) } function scheduleCopiedReset(setCopied: (value: boolean) => void): void { setTimeout(() => setCopied(false), 2000) } function isUserCancellationError(error: Error): boolean { return error.message.includes('user rejected') || error.message.includes('cancelled') } async function payWithWebLN(invoice: string): Promise { const alby = getAlbyService() if (!isWebLNAvailable()) { throw new Error(t('payment.modal.weblnNotAvailable')) } await alby.enable() await alby.sendPayment(invoice) } export function PaymentModal({ invoice, onClose, onPaymentComplete }: PaymentModalProps): React.ReactElement { const { copied, errorMessage, paymentUrl, timeRemaining, handleCopy, handleOpenWallet } = usePaymentModalState(invoice, onPaymentComplete) const handleOpenWalletSync = (): void => { void handleOpenWallet() } return (
{errorMessage && (

{errorMessage}

)}

{t('payment.modal.autoVerify')}

) }