import "./SendSats.css"; import React, { ReactNode, useEffect, useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; import { HexKey } from "@snort/system"; import { LNURLSuccessAction } from "@snort/shared"; import { formatShort } from "@/Number"; import Icon from "@/Icons/Icon"; import useEventPublisher from "@/Hooks/useEventPublisher"; import ProfileImage from "@/Element/User/ProfileImage"; import Modal from "@/Element/Modal"; import QrCode from "@/Element/QrCode"; import Copy from "@/Element/Copy"; import { debounce } from "@/SnortUtils"; import { LNWallet, useWallet } from "@/Wallet"; import useLogin from "@/Hooks/useLogin"; import AsyncButton from "@/Element/Button/AsyncButton"; import { ZapTarget, ZapTargetResult, Zapper } from "@/Zapper"; import messages from "./messages"; enum ZapType { PublicZap = 1, AnonZap = 2, PrivateZap = 3, NonZap = 4, } export interface SendSatsProps { onClose?: () => void; targets?: Array; show?: boolean; invoice?: string; // shortcut to invoice qr tab title?: ReactNode; notice?: string; note?: HexKey; allocatePool?: boolean; } export default function SendSats(props: SendSatsProps) { const onClose = props.onClose || (() => undefined); const [zapper, setZapper] = useState(); const [invoice, setInvoice] = useState>(); const [error, setError] = useState(); const [success, setSuccess] = useState(); const [amount, setAmount] = useState(); const { publisher, system } = useEventPublisher(); const walletState = useWallet(); const wallet = walletState.wallet; useEffect(() => { if (props.show) { const invoiceTarget = { target: { type: "lnurl", value: "", weight: 1, }, pr: props.invoice, paid: false, sent: 0, fee: 0, } as ZapTargetResult; setError(undefined); setInvoice(props.invoice ? [invoiceTarget] : undefined); setSuccess(undefined); } }, [props.show]); useEffect(() => { if (success && !success.url) { // Fire onClose when success is set with no URL action return debounce(1_000, () => { onClose(); }); } }, [success]); useEffect(() => { if (props.targets && props.show) { try { console.debug("loading zapper"); const zapper = new Zapper(system, publisher); zapper.load(props.targets).then(() => { console.debug(zapper); setZapper(zapper); }); } catch (e) { console.error(e); if (e instanceof Error) { setError(e.message); } } } }, [props.targets, props.show]); function successAction() { if (!success) return null; return (

{success?.description ?? }

{success.url && (

{success.url}

)}
); } function title() { if (!props.targets) { return ( <>

{zapper?.canZap() ? ( ) : ( )}

); } if (props.targets.length === 1 && props.targets[0].name) { const t = props.targets[0]; const values = { name: t.name, }; return ( <> {t.zap?.pubkey && }

{zapper?.canZap() ? ( ) : ( )}

); } if (props.targets.length > 1) { const total = props.targets.reduce((acc, v) => (acc += v.weight), 0); return (

{zapper?.canZap() ? ( ) : ( )}

{props.targets.map(v => ( ))}
); } } if (!(props.show ?? false)) return null; return (
{props.title || title()}
{zapper && !invoice && ( setAmount(v)} onNextStage={async p => { const targetsWithComments = (props.targets ?? []).map(v => { if (p.comment) { v.memo = p.comment; } if (p.type === ZapType.AnonZap && v.zap) { v.zap = { ...v.zap, anon: true, }; } else if (p.type === ZapType.NonZap) { v.zap = undefined; } return v; }); if (targetsWithComments.length > 0) { const sends = await zapper.send(wallet, targetsWithComments, p.amount); if (sends[0].error) { setError(sends[0].error.message); } else if (sends.every(a => a.paid)) { setSuccess({}); } else { setInvoice(sends); } } }} /> )} {error &&

{error}

} {invoice && !success && ( { setSuccess({}); }} /> )} {successAction()}
); } interface SendSatsInputSelection { amount: number; comment?: string; type: ZapType; } function SendSatsInput(props: { zapper: Zapper; onChange?: (v: SendSatsInputSelection) => void; onNextStage: (v: SendSatsInputSelection) => Promise; }) { const { defaultZapAmount, readonly } = useLogin(s => ({ defaultZapAmount: s.appData.item.preferences.defaultZapAmount, readonly: s.readonly, })); const { formatMessage } = useIntl(); const amounts: Record = { [defaultZapAmount.toString()]: "", "1000": "👍", "5000": "💜", "10000": "😍", "20000": "🤩", "50000": "🔥", "100000": "🚀", "1000000": "🤯", }; const [comment, setComment] = useState(); const [amount, setAmount] = useState(defaultZapAmount); const [customAmount, setCustomAmount] = useState(defaultZapAmount); const [zapType, setZapType] = useState(readonly ? ZapType.AnonZap : ZapType.PublicZap); function getValue() { return { amount, comment, type: zapType, } as SendSatsInputSelection; } useEffect(() => { if (props.onChange) { props.onChange(getValue()); } }, [amount, comment, zapType]); function renderAmounts() { const min = props.zapper.minAmount() / 1000; const max = props.zapper.maxAmount() / 1000; const filteredAmounts = Object.entries(amounts).filter(([k]) => Number(k) >= min && Number(k) <= max); return (
{filteredAmounts.map(([k, v]) => ( setAmount(Number(k))}> {v}  {k === "1000" ? "1K" : formatShort(Number(k))} ))}
); } function custom() { const min = props.zapper.minAmount() / 1000; const max = props.zapper.maxAmount() / 1000; return (
setCustomAmount(parseInt(e.target.value))} />
); } return (

{renderAmounts()} {custom()} {props.zapper.maxComment() > 0 && ( setComment(e.target.value)} /> )}
{(amount ?? 0) > 0 && ( props.onNextStage(getValue())}> )}
); } function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; setZapType: (t: ZapType) => void }) { const { readonly } = useLogin(s => ({ readonly: s.readonly })); const makeTab = (t: ZapType, n: React.ReactNode) => ( ); return (

{!readonly && makeTab(ZapType.PublicZap, )} {/*makeTab(ZapType.PrivateZap, "Private")*/} {makeTab(ZapType.AnonZap, )} {makeTab( ZapType.NonZap, , )}
); } function SendSatsInvoice(props: { invoice: Array; wallet?: LNWallet; notice?: ReactNode; onInvoicePaid: () => void; }) { return (
{props.notice && {props.notice}} {props.invoice.map(v => ( <> ))}
); }