snort/packages/app/src/Element/Poll.tsx

125 lines
4.0 KiB
TypeScript
Raw Normal View History

2023-04-05 15:10:14 +00:00
import { TaggedRawEvent } from "@snort/nostr";
import { useState } from "react";
import { useSelector } from "react-redux";
2023-04-10 13:00:26 +00:00
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
2023-04-05 15:10:14 +00:00
import { ParsedZap } from "Element/Zap";
import Text from "Element/Text";
import useEventPublisher from "Feed/EventPublisher";
import { RootState } from "State/Store";
import { useWallet } from "Wallet";
import { useUserProfile } from "Hooks/useUserProfile";
import { LNURL } from "LNURL";
import { unwrap } from "Util";
import { formatShort } from "Number";
import Spinner from "Icons/Spinner";
import SendSats from "Element/SendSats";
interface PollProps {
ev: TaggedRawEvent;
zaps: Array<ParsedZap>;
}
export default function Poll(props: PollProps) {
const { formatMessage } = useIntl();
const publisher = useEventPublisher();
const { wallet } = useWallet();
const prefs = useSelector((s: RootState) => s.login.preferences);
const pollerProfile = useUserProfile(props.ev.pubkey);
const [error, setError] = useState("");
const [invoice, setInvoice] = useState("");
const [voting, setVoting] = useState<number>();
const options = props.ev.tags.filter(a => a[0] === "poll_option").sort((a, b) => Number(a[1]) - Number(b[1]));
async function zapVote(opt: number) {
2023-04-10 12:53:53 +00:00
if (voting) return;
2023-04-05 15:10:14 +00:00
const amount = prefs.defaultZapAmount;
try {
setVoting(opt);
2023-04-05 17:07:42 +00:00
const zap = await publisher.zap(amount * 1000, props.ev.pubkey, props.ev.id, undefined, [
2023-04-05 15:10:14 +00:00
["poll_option", opt.toString()],
]);
2023-04-10 12:53:53 +00:00
if (!zap) {
throw new Error(
formatMessage({
defaultMessage: "Can't create vote, maybe you're not logged in?",
})
);
}
2023-04-05 15:10:14 +00:00
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) {
2023-04-10 12:53:53 +00:00
throw new Error(
formatMessage({
defaultMessage: "Can't vote because LNURL service does not support zaps",
})
);
2023-04-05 15:10:14 +00:00
}
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);
2023-04-10 12:53:53 +00:00
} else {
setError(
formatMessage({
defaultMessage: "Failed to send vote",
})
);
2023-04-05 15:10:14 +00:00
}
} finally {
setVoting(undefined);
}
}
const allTotal = props.zaps.filter(a => a.pollOption !== undefined).reduce((acc, v) => (acc += v.amount), 0);
return (
<>
2023-04-10 13:00:26 +00:00
<small>
<FormattedMessage
defaultMessage="Your are voting with {amount} sats"
values={{
amount: formatShort(prefs.defaultZapAmount),
}}
/>
</small>
2023-04-05 15:10:14 +00:00
<div className="poll-body">
{options.map(a => {
const opt = Number(a[1]);
const desc = a[2];
const zapsOnOption = props.zaps.filter(b => b.pollOption === opt);
const total = zapsOnOption.reduce((acc, v) => (acc += v.amount), 0);
2023-04-10 12:53:53 +00:00
const weight = allTotal === 0 ? 0 : total / allTotal;
2023-04-05 15:10:14 +00:00
return (
<div key={a[1]} className="flex" onClick={() => zapVote(opt)}>
<div className="f-grow">
{opt === voting ? <Spinner /> : <Text content={desc} tags={props.ev.tags} creator={props.ev.pubkey} />}
</div>
<div className="flex">
2023-04-05 17:07:42 +00:00
<FormattedNumber value={weight * 100} maximumFractionDigits={0} />% &nbsp;
2023-04-05 15:10:14 +00:00
<small>({formatShort(total)})</small>
</div>
2023-04-05 17:07:42 +00:00
<div style={{ width: `${weight * 100}%` }} className="progress"></div>
2023-04-05 15:10:14 +00:00
</div>
);
})}
{error && <b className="error">{error}</b>}
</div>
<SendSats show={invoice !== ""} onClose={() => setInvoice("")} invoice={invoice} />
</>
);
}