import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { useToast } from '@/hooks/useToast'; import { useWallet } from '@/hooks/useWallet'; import { useNWC } from '@/hooks/useNWCContext'; import { getInvoiceFromLightningAddress } from '@/lib/lightningAddress'; import { assertInvoiceAmount } from '@/lib/verifyInvoice'; import { LISTING_FEE_SATS, PLATFORM_LIGHTNING_ADDRESS } from '@/hooks/useListingRestrictions'; import { Zap, Loader2, CheckCircle, Copy, ExternalLink, ShieldAlert, TrendingUp, MapPin } from 'lucide-react'; import { useTranslation } from '@/contexts/language'; // qrcode imported lazily in async function interface SpamFeePaymentProps { feeRecipient: string; feeRecipientName: string; onPaymentSuccess: () => void; /** Offered as a FREE alternative to the sats fee: mine NIP-13 proof of work. */ onPowInstead?: () => void; onSkip?: () => void; } type PaymentState = 'idle' | 'generating' | 'waiting' | 'paying' | 'success' | 'error'; export function SpamFeePayment({ feeRecipient, feeRecipientName, onPaymentSuccess, onPowInstead, }: SpamFeePaymentProps) { const { t } = useTranslation(); const { toast } = useToast(); const { webln, activeNWC } = useWallet(); const { sendPayment } = useNWC(); const [paymentState, setPaymentState] = useState('idle'); const [invoice, setInvoice] = useState(null); const [qrCodeUrl, setQrCodeUrl] = useState(null); const [error, setError] = useState(null); // Auto-pay when wallet is connected and invoice is generated useEffect(() => { if (invoice && paymentState === 'waiting' && (webln || activeNWC)) { handleAutoPay(); } // Intentionally omit handleAutoPay: it is recreated each render, so adding // it would re-trigger auto-pay on every render. Keyed to invoice/state. // eslint-disable-next-line react-hooks/exhaustive-deps }, [invoice, paymentState, webln, activeNWC]); const generateInvoice = async () => { setPaymentState('generating'); setError(null); try { const bolt11 = await getInvoiceFromLightningAddress({ lightningAddress: feeRecipient, amountSats: LISTING_FEE_SATS, comment: t('marketUi.spamFee.invoiceComment'), }); // SECURITY: verify the returned invoice really requests the listing fee // (and not an inflated amount) before it can be auto-paid. assertInvoiceAmount(bolt11, LISTING_FEE_SATS); setInvoice(bolt11); // Generate QR code const { default: QRCode } = await import('qrcode'); const qr = await QRCode.toDataURL(bolt11.toUpperCase(), { width: 256, margin: 2, color: { dark: '#000000', light: '#ffffff', }, }); setQrCodeUrl(qr); setPaymentState('waiting'); // If wallet is available, try auto-pay if (webln || activeNWC) { await handleAutoPay(); } } catch (err) { console.error('Failed to generate invoice:', err); setError(err instanceof Error ? err.message : t('marketUi.spamFee.generateFailed')); setPaymentState('error'); } }; const handleAutoPay = async () => { if (!invoice) return; setPaymentState('paying'); try { // Try NWC first if (activeNWC && activeNWC.isConnected) { await sendPayment(activeNWC, invoice); setPaymentState('success'); toast({ title: t('marketUi.spamFee.paymentSuccess'), description: t('marketUi.spamFee.satsSent', { amount: LISTING_FEE_SATS.toLocaleString(), name: feeRecipientName }), }); setTimeout(onPaymentSuccess, 1500); return; } // Try WebLN if (webln) { if (webln.enable) { await webln.enable(); } await webln.sendPayment(invoice); setPaymentState('success'); toast({ title: t('marketUi.spamFee.paymentSuccess'), description: t('marketUi.spamFee.satsSent', { amount: LISTING_FEE_SATS.toLocaleString(), name: feeRecipientName }), }); setTimeout(onPaymentSuccess, 1500); return; } // No auto-pay available, stay in waiting state setPaymentState('waiting'); } catch (err) { console.error('Auto-pay failed:', err); // Fall back to manual payment setPaymentState('waiting'); toast({ title: t('marketUi.spamFee.autoPayFailed'), description: t('marketUi.spamFee.autoPayFailedBody'), variant: 'destructive', }); } }; const copyInvoice = async () => { if (!invoice) return; try { await navigator.clipboard.writeText(invoice); toast({ title: t('marketUi.spamFee.invoiceCopied') }); } catch { toast({ title: t('marketUi.spamFee.copyFailed'), variant: 'destructive' }); } }; const openInWallet = () => { if (!invoice) return; window.open(`lightning:${invoice}`, '_blank'); }; // Show success state if (paymentState === 'success') { return (

{t('marketUi.spamFee.received')}

{t('marketUi.spamFee.receivedBody', { amount: LISTING_FEE_SATS.toLocaleString() })}

); } // Show payment flow return ( {t('marketUi.spamFee.title')} {t('marketUi.spamFee.subtitle')} {/* Why this fee exists */}

{t('marketUi.spamFee.whyTitle')}

{t('marketUi.spamFee.spamLabel')} {t('marketUi.spamFee.spamBody')}

{t('marketUi.spamFee.trustLabel')} {t('marketUi.spamFee.trustBody')}

{t('marketUi.spamFee.supportsLabel')} {t('marketUi.spamFee.supportsPrefix')}{' '} {feeRecipientName} {feeRecipient !== PLATFORM_LIGHTNING_ADDRESS && t('marketUi.spamFee.supportsSpotOwner')} {feeRecipient === PLATFORM_LIGHTNING_ADDRESS && t('marketUi.spamFee.supportsPlatform')}

{/* Fee amount */}
⚡ {LISTING_FEE_SATS.toLocaleString()} {t('common.sats')}

{t('marketUi.spamFee.oneTimeFee', { usd: (LISTING_FEE_SATS * 0.0006).toFixed(2) })}

{/* Initial state — pay, or prove work instead (free) */} {paymentState === 'idle' && (
{onPowInstead && ( <>

{t('common.or')}

{t('marketUi.spamFee.proveWorkBody', { amount: LISTING_FEE_SATS.toLocaleString() })}

)}
)} {/* Generating invoice */} {paymentState === 'generating' && (

{t('marketUi.spamFee.generating')}

)} {/* Paying */} {paymentState === 'paying' && (

{t('marketUi.spamFee.processing')}

)} {/* Waiting for payment - show QR and manual options */} {paymentState === 'waiting' && invoice && qrCodeUrl && (
{/* QR Code */}
{t('marketUi.spamFee.qrAlt')}
{/* Action buttons */}
{/* Try again with wallet */} {(webln || activeNWC) && ( )}

{t('marketUi.spamFee.scanHint')}

)} {/* Error state */} {paymentState === 'error' && error && (

{error}

)} {/* How to avoid future fees */}

✨ {t('marketUi.spamFee.skipTitle')}

  • → {t('marketUi.spamFee.skip1')}
  • → {t('marketUi.spamFee.skip2')}
  • → {t('marketUi.spamFee.skip3')}

{t('marketUi.spamFee.skipNote')}

); }