import { QRCodeSVG } from 'qrcode.react' import React, { Dispatch, ReactNode, SetStateAction, useCallback, useMemo, useState } from 'react' import Countdown, { CountdownRenderProps } from 'react-countdown' import { toast } from 'react-toastify' import { MetadataController, ZapController } from '../controllers' import { useAppSelector, useDidMount } from '../hooks' import '../styles/popup.css' import { PaymentRequest } from '../types' import { copyTextToClipboard, formatNumber, getZapAmount, unformatNumber } from '../utils' import { LoadingSpinner } from './LoadingSpinner' type PresetAmountProps = { label: string value: number setAmount: Dispatch> } export const PresetAmount = React.memo( ({ label, value, setAmount }: PresetAmountProps) => { return ( ) } ) type ZapPresetsProps = { setAmount: Dispatch> } export const ZapPresets = React.memo(({ setAmount }: ZapPresetsProps) => { return ( <> ) }) type ZapButtonsProps = { disabled: boolean handleGenerateQRCode: () => void handleSend: () => void } export const ZapButtons = ({ disabled, handleGenerateQRCode, handleSend }: ZapButtonsProps) => { return (
) } type ZapQRProps = { paymentRequest: PaymentRequest handleClose: () => void handleQRExpiry: () => void setTotalZapAmount?: Dispatch> setHasZapped?: Dispatch> } export const ZapQR = React.memo( ({ paymentRequest, handleClose, handleQRExpiry, setTotalZapAmount, setHasZapped }: ZapQRProps) => { useDidMount(() => { ZapController.getInstance() .pollZapReceipt(paymentRequest) .then((zapReceipt) => { toast.success(`Successfully sent sats!`) if (setTotalZapAmount) { const amount = getZapAmount(zapReceipt) setTotalZapAmount((prev) => prev + amount) if (setHasZapped) setHasZapped(true) } }) .catch((err) => { toast.error(err.message || err) }) .finally(() => { handleClose() }) }) const onQrCodeClicked = async () => { if (!paymentRequest) return const zapController = ZapController.getInstance() if (await zapController.isWeblnProviderExists()) { zapController.sendPayment(paymentRequest.pr) } else { console.warn('Webln provider not present') const href = `lightning:${paymentRequest.pr}` const a = document.createElement('a') a.href = href a.click() } } return (
) } ) const MAX_POLLING_TIME = 2 * 60 * 1000 // 2 minutes in milliseconds const renderer = ({ minutes, seconds }: CountdownRenderProps) => ( {minutes}:{seconds} ) type TimerProps = { onTimerExpired: () => void } const Timer = React.memo(({ onTimerExpired }: TimerProps) => { const expiryTime = useMemo(() => { return Date.now() + MAX_POLLING_TIME }, []) return (
) }) type ZapPopUpProps = { title: string labelDescriptionMain?: ReactNode receiver: string eventId?: string aTag?: string notCloseAfterZap?: boolean lastNode?: ReactNode setTotalZapAmount?: Dispatch> setHasZapped?: Dispatch> handleClose: () => void } export const ZapPopUp = ({ title, labelDescriptionMain, receiver, eventId, aTag, lastNode, notCloseAfterZap, setTotalZapAmount, setHasZapped, handleClose }: ZapPopUpProps) => { const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') const [amount, setAmount] = useState(0) const [message, setMessage] = useState('') const [paymentRequest, setPaymentRequest] = useState() const userState = useAppSelector((state) => state.user) const handleAmountChange = (event: React.ChangeEvent) => { const unformattedValue = unformatNumber(event.target.value) setAmount(unformattedValue) } const generatePaymentRequest = useCallback(async (): Promise => { let userHexKey: string setIsLoading(true) setLoadingSpinnerDesc('Getting user pubkey') if (userState.auth && userState.user?.pubkey) { userHexKey = userState.user.pubkey as string } else { userHexKey = (await window.nostr?.getPublicKey()) as string } if (!userHexKey) { setIsLoading(false) toast.error('Could not get pubkey') return null } setLoadingSpinnerDesc('finding receiver metadata') const metadataController = await MetadataController.getInstance() const receiverMetadata = await metadataController.findMetadata(receiver) if (!receiverMetadata?.lud16) { setIsLoading(false) toast.error('Lighting address (lud16) is missing in receiver metadata!') return null } if (!receiverMetadata?.pubkey) { setIsLoading(false) toast.error('pubkey is missing in receiver metadata!') return null } const zapController = ZapController.getInstance() setLoadingSpinnerDesc('Creating zap request') return await zapController .getLightningPaymentRequest( receiverMetadata.lud16, amount, receiverMetadata.pubkey as string, userHexKey, message, eventId, aTag ) .catch((err) => { toast.error(err.message || err) return null }) .finally(() => { setIsLoading(false) }) }, [amount, message, userState, receiver, eventId, aTag]) const handleGenerateQRCode = async () => { const pr = await generatePaymentRequest() if (!pr) return setPaymentRequest(pr) } const handleSend = useCallback(async () => { const pr = await generatePaymentRequest() if (!pr) return setIsLoading(true) setLoadingSpinnerDesc('Sending payment!') const zapController = ZapController.getInstance() if (await zapController.isWeblnProviderExists()) { await zapController .sendPayment(pr.pr) .then(() => { toast.success(`Successfully sent ${amount} sats!`) if (setTotalZapAmount) { setTotalZapAmount((prev) => prev + amount) if (setHasZapped) setHasZapped(true) } if (!notCloseAfterZap) { handleClose() } }) .catch((err) => { toast.error(err.message || err) }) } else { toast.warn('Webln is not present. Use QR code to send zap.') setPaymentRequest(pr) } setIsLoading(false) }, [ amount, notCloseAfterZap, handleClose, generatePaymentRequest, setTotalZapAmount, setHasZapped ]) const handleQRExpiry = useCallback(() => { setPaymentRequest(undefined) }, []) const handleQRClose = useCallback(() => { setPaymentRequest(undefined) setIsLoading(false) if (!notCloseAfterZap) { handleClose() } }, [notCloseAfterZap, handleClose]) return ( <> {isLoading && }

{title}

{labelDescriptionMain}
setMessage(e.target.value)} />
{paymentRequest && ( )} {lastNode}
) }