import { LNURL } from "@snort/shared"; import { NostrLink, ParsedZap, TaggedNostrEvent } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; import { useState } from "react"; import { FormattedMessage, FormattedNumber, useIntl } from "react-intl"; import Spinner from "@/Components/Icons/Spinner"; import ZapModal from "@/Components/ZapModal/ZapModal"; import useEventPublisher from "@/Hooks/useEventPublisher"; import useLogin from "@/Hooks/useLogin"; import usePreferences from "@/Hooks/usePreferences"; import { unwrap } from "@/Utils"; import { formatShort } from "@/Utils/Number"; import { useWallet } from "@/Wallet"; interface PollProps { ev: TaggedNostrEvent; zaps: Array; } type PollTally = "zaps" | "pubkeys"; export default function Poll(props: PollProps) { const { formatMessage } = useIntl(); const { publisher, system } = useEventPublisher(); const { wallet } = useWallet(); const defaultZapAmount = usePreferences(s => s.defaultZapAmount); const myPubKey = useLogin(s => s.publicKey); const pollerProfile = useUserProfile(props.ev.pubkey); const [tallyBy, setTallyBy] = useState("pubkeys"); const [error, setError] = useState(""); const [invoice, setInvoice] = useState(""); const [voting, setVoting] = useState(); const didVote = props.zaps?.some(a => a.sender === myPubKey); const isMyPoll = props.ev.pubkey === myPubKey; const showResults = didVote || isMyPoll; const options = props.ev.tags ?.filter(a => a[0] === "poll_option") .sort((a, b) => (Number(a[1]) > Number(b[1]) ? 1 : -1)); async function zapVote(ev: React.MouseEvent, opt: number) { ev.stopPropagation(); if (voting || !publisher) return; const amount = defaultZapAmount; try { if (amount <= 0) { throw new Error( formatMessage( { defaultMessage: "Can't vote with {amount} sats, please set a different default zap amount", id: "NepkXH", }, { amount, }, ), ); } setVoting(opt); const r = await system.requestRouter?.forReplyTo(props.ev.pubkey); const zap = await publisher.zap( amount * 1000, props.ev.pubkey, r ?? [], NostrLink.fromEvent(props.ev), undefined, eb => eb.tag(["poll_option", opt.toString()]), ); const lnurl = props.ev.tags.find(a => a[0] === "zap")?.[1] || pollerProfile?.lud16 || pollerProfile?.lud06; if (!lnurl) return; const svc = new LNURL(lnurl); await svc.load(); if (!svc.canZap) { throw new Error( formatMessage({ defaultMessage: "Can't vote because LNURL service does not support zaps", id: "fOksnD", }), ); } const invoice = await svc.getInvoice(amount, undefined, zap); if (wallet?.isReady()) { await wallet?.payInvoice(unwrap(invoice.pr)); } else { setInvoice(unwrap(invoice.pr)); } } catch (e) { if (e instanceof Error) { setError(e.message); } else { setError( formatMessage({ defaultMessage: "Failed to send vote", id: "g985Wp", }), ); } } finally { setVoting(undefined); } } const totalVotes = (() => { switch (tallyBy) { case "zaps": return props.zaps?.filter(a => a.pollOption !== undefined).reduce((acc, v) => (acc += v.amount), 0) ?? 0; case "pubkeys": return new Set(props.zaps?.filter(a => a.pollOption !== undefined).map(a => unwrap(a.sender)) ?? []).size; } })(); return ( <>
{options?.map(a => { const opt = Number(a[1]); const desc = a[2]; const zapsOnOption = props.zaps?.filter(b => b.pollOption === opt) ?? []; const total = (() => { switch (tallyBy) { case "zaps": return zapsOnOption.reduce((acc, v) => (acc += v.amount), 0); case "pubkeys": return new Set(zapsOnOption.map(a => unwrap(a.sender))).size; } })(); const weight = totalVotes === 0 ? 0 : total / totalVotes; return (
zapVote(e, opt)}>
{opt === voting ? : <>{desc}}
{showResults && ( <>
%   ({formatShort(total)})
)}
); })} {error && {error}}
setInvoice("")} invoice={invoice} /> ); }