|
|
|
@ -16,7 +16,9 @@ import { cn } from '@/lib/utils' |
|
|
|
import { useZap } from '@/providers/ZapProvider' |
|
|
|
import { useZap } from '@/providers/ZapProvider' |
|
|
|
import lightning from '@/services/lightning.service' |
|
|
|
import lightning from '@/services/lightning.service' |
|
|
|
import { Check, Copy, Wallet, Zap } from 'lucide-react' |
|
|
|
import { Check, Copy, Wallet, Zap } from 'lucide-react' |
|
|
|
import { useEffect, useMemo, useState } from 'react' |
|
|
|
import { closeModal } from '@getalby/bitcoin-connect-react' |
|
|
|
|
|
|
|
import { releaseBodyScrollLocks } from '@/lib/react-remove-scroll-body-cleanup' |
|
|
|
|
|
|
|
import { useEffect, useMemo, useRef, useState } from 'react' |
|
|
|
import { useTranslation } from 'react-i18next' |
|
|
|
import { useTranslation } from 'react-i18next' |
|
|
|
import { toast } from 'sonner' |
|
|
|
import { toast } from 'sonner' |
|
|
|
|
|
|
|
|
|
|
|
@ -30,10 +32,19 @@ function invoiceQrPayload(pr: string): string { |
|
|
|
|
|
|
|
|
|
|
|
export default function LightningInvoiceSection({ |
|
|
|
export default function LightningInvoiceSection({ |
|
|
|
lightningAddress, |
|
|
|
lightningAddress, |
|
|
|
paytoUri |
|
|
|
paytoUri, |
|
|
|
|
|
|
|
onBolt11InvoiceChange, |
|
|
|
|
|
|
|
onRequestClose, |
|
|
|
|
|
|
|
onPaymentSuccess |
|
|
|
}: { |
|
|
|
}: { |
|
|
|
lightningAddress: string |
|
|
|
lightningAddress: string |
|
|
|
paytoUri: string |
|
|
|
paytoUri: string |
|
|
|
|
|
|
|
/** Fired when a BOLT11 invoice is created or cleared (for Phoenix / external wallet links). */ |
|
|
|
|
|
|
|
onBolt11InvoiceChange?: (invoice: string | null) => void |
|
|
|
|
|
|
|
/** Close the payto dialog before opening an external wallet / Bitcoin Connect UI. */ |
|
|
|
|
|
|
|
onRequestClose?: () => void |
|
|
|
|
|
|
|
/** After a successful in-app or external wallet payment (kind-24 tip notice). */ |
|
|
|
|
|
|
|
onPaymentSuccess?: () => void |
|
|
|
}) { |
|
|
|
}) { |
|
|
|
const { t } = useTranslation() |
|
|
|
const { t } = useTranslation() |
|
|
|
const { defaultZapSats, isWalletConnected } = useZap() |
|
|
|
const { defaultZapSats, isWalletConnected } = useZap() |
|
|
|
@ -47,6 +58,16 @@ export default function LightningInvoiceSection({ |
|
|
|
const [invoiceDescription, setInvoiceDescription] = useState<string | null>(null) |
|
|
|
const [invoiceDescription, setInvoiceDescription] = useState<string | null>(null) |
|
|
|
const [creating, setCreating] = useState(false) |
|
|
|
const [creating, setCreating] = useState(false) |
|
|
|
const [paying, setPaying] = useState(false) |
|
|
|
const [paying, setPaying] = useState(false) |
|
|
|
|
|
|
|
const mountedRef = useRef(true) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
|
|
mountedRef.current = true |
|
|
|
|
|
|
|
return () => { |
|
|
|
|
|
|
|
mountedRef.current = false |
|
|
|
|
|
|
|
closeModal() |
|
|
|
|
|
|
|
releaseBodyScrollLocks() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, []) |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
setSats(clampZapSats(defaultZapSats)) |
|
|
|
setSats(clampZapSats(defaultZapSats)) |
|
|
|
@ -57,7 +78,7 @@ export default function LightningInvoiceSection({ |
|
|
|
setLnurlMetadataState('loading') |
|
|
|
setLnurlMetadataState('loading') |
|
|
|
let cancelled = false |
|
|
|
let cancelled = false |
|
|
|
void lightning.getLnurlPayInvoiceOptions(lightningAddress).then((opts) => { |
|
|
|
void lightning.getLnurlPayInvoiceOptions(lightningAddress).then((opts) => { |
|
|
|
if (!cancelled) { |
|
|
|
if (!cancelled && mountedRef.current) { |
|
|
|
if (opts) { |
|
|
|
if (opts) { |
|
|
|
setCommentMax(opts.commentAllowed) |
|
|
|
setCommentMax(opts.commentAllowed) |
|
|
|
setLnurlMetadataState('ready') |
|
|
|
setLnurlMetadataState('ready') |
|
|
|
@ -77,6 +98,10 @@ export default function LightningInvoiceSection({ |
|
|
|
setInvoiceDescription(null) |
|
|
|
setInvoiceDescription(null) |
|
|
|
}, [sats, description]) |
|
|
|
}, [sats, description]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
|
|
onBolt11InvoiceChange?.(invoice) |
|
|
|
|
|
|
|
}, [invoice, onBolt11InvoiceChange]) |
|
|
|
|
|
|
|
|
|
|
|
const invoiceSats = useMemo(() => { |
|
|
|
const invoiceSats = useMemo(() => { |
|
|
|
if (!invoice) return null |
|
|
|
if (!invoice) return null |
|
|
|
try { |
|
|
|
try { |
|
|
|
@ -98,12 +123,15 @@ export default function LightningInvoiceSection({ |
|
|
|
const pr = await lightning.createLnurlInvoice(lightningAddress, sats, { |
|
|
|
const pr = await lightning.createLnurlInvoice(lightningAddress, sats, { |
|
|
|
description: trimmedDesc || undefined |
|
|
|
description: trimmedDesc || undefined |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
if (!mountedRef.current) return |
|
|
|
setInvoice(pr) |
|
|
|
setInvoice(pr) |
|
|
|
setInvoiceDescription(trimmedDesc || null) |
|
|
|
setInvoiceDescription(trimmedDesc || null) |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
toast.error(`${t('Failed to create invoice')}: ${(error as Error).message}`) |
|
|
|
if (mountedRef.current) { |
|
|
|
|
|
|
|
toast.error(`${t('Failed to create invoice')}: ${(error as Error).message}`) |
|
|
|
|
|
|
|
} |
|
|
|
} finally { |
|
|
|
} finally { |
|
|
|
setCreating(false) |
|
|
|
if (mountedRef.current) setCreating(false) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -111,16 +139,20 @@ export default function LightningInvoiceSection({ |
|
|
|
if (!invoice) return |
|
|
|
if (!invoice) return |
|
|
|
try { |
|
|
|
try { |
|
|
|
setPaying(true) |
|
|
|
setPaying(true) |
|
|
|
const result = await lightning.payInvoice(invoice) |
|
|
|
const result = await lightning.payInvoice(invoice, onRequestClose) |
|
|
|
|
|
|
|
if (!mountedRef.current) return |
|
|
|
if (result) { |
|
|
|
if (result) { |
|
|
|
toast.success(t('Payment sent')) |
|
|
|
toast.success(t('Payment sent')) |
|
|
|
setInvoice(null) |
|
|
|
setInvoice(null) |
|
|
|
setInvoiceDescription(null) |
|
|
|
setInvoiceDescription(null) |
|
|
|
|
|
|
|
onPaymentSuccess?.() |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
toast.error(`${t('Lightning payment failed')}: ${(error as Error).message}`) |
|
|
|
if (mountedRef.current) { |
|
|
|
|
|
|
|
toast.error(`${t('Lightning payment failed')}: ${(error as Error).message}`) |
|
|
|
|
|
|
|
} |
|
|
|
} finally { |
|
|
|
} finally { |
|
|
|
setPaying(false) |
|
|
|
if (mountedRef.current) setPaying(false) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|