feat: zaps

This commit is contained in:
Alejandro Gomez
2023-02-03 22:38:14 +01:00
parent d1087d0405
commit eb77e91b57
17 changed files with 341 additions and 12 deletions

View File

@ -103,4 +103,4 @@ export default function HyperText({ link, creator }: { link: string, creator: He
}, [link]);
return render();
}
}

View File

@ -1,12 +1,16 @@
import "./LNURLTip.css";
import { useEffect, useMemo, useState } from "react";
import { bech32ToText } from "Util";
import { HexKey } from "Nostr";
import useEventPublisher from "Feed/EventPublisher";
import Modal from "Element/Modal";
import QrCode from "Element/QrCode";
import Copy from "Element/Copy";
import useWebln from "Hooks/useWebln";
interface LNURLService {
allowsNostr?: boolean
nostrPubkey?: HexKey
minSendable?: number,
maxSendable?: number,
metadata: string,
@ -31,12 +35,15 @@ export interface LNURLTipProps {
invoice?: string, // shortcut to invoice qr tab
title?: string,
notice?: string
note?: HexKey
author?: HexKey
}
export default function LNURLTip(props: LNURLTipProps) {
const onClose = props.onClose || (() => { });
const service = props.svc;
const show = props.show || false;
const { note, author } = props
const amounts = [50, 100, 500, 1_000, 5_000, 10_000, 50_000];
const [payService, setPayService] = useState<LNURLService>();
const [amount, setAmount] = useState<number>();
@ -46,6 +53,7 @@ export default function LNURLTip(props: LNURLTipProps) {
const [error, setError] = useState<string>();
const [success, setSuccess] = useState<LNURLSuccessAction>();
const webln = useWebln(show);
const publisher = useEventPublisher();
useEffect(() => {
if (show && !props.invoice) {
@ -117,7 +125,16 @@ export default function LNURLTip(props: LNURLTipProps) {
async function loadInvoice() {
if (!amount || !payService) return null;
const url = `${payService.callback}?amount=${Math.floor(amount * 1000)}${comment ? `&comment=${encodeURIComponent(comment)}` : ""}`;
let url = ''
const amountParam = `amount=${Math.floor(amount * 1000)}`
const commentParam = comment ? `&comment=${encodeURIComponent(comment)}` : ""
if (payService.allowsNostr && payService.nostrPubkey && author) {
const ev = await publisher.zap(author, note, comment)
const nostrParam = ev && `&nostr=${encodeURIComponent(JSON.stringify(ev.ToObject()))}`
url = `${payService.callback}?${amountParam}${commentParam}${nostrParam}`;
} else {
url = `${payService.callback}?${amountParam}${commentParam}`;
}
try {
let rsp = await fetch(url);
if (rsp.ok) {
@ -235,4 +252,4 @@ export default function LNURLTip(props: LNURLTipProps) {
</div>
</Modal>
)
}
}

View File

@ -14,6 +14,7 @@ import useEventPublisher from "Feed/EventPublisher";
import { getReactions, hexToBech32, normalizeReaction, Reaction } from "Util";
import { NoteCreator } from "Element/NoteCreator";
import LNURLTip from "Element/LNURLTip";
import { parseZap, ZapsSummary } from "Element/Zap";
import { useUserProfile } from "Feed/ProfileFeed";
import { default as NEvent } from "Nostr/Event";
import { RootState } from "State/Store";
@ -50,6 +51,9 @@ export default function NoteFooter(props: NoteFooterProps) {
const langNames = new Intl.DisplayNames([...window.navigator.languages], { type: "language" });
const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]);
const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related, ev]);
const zaps = useMemo(() => getReactions(related, ev.Id, EventKind.ZapReceipt).map(parseZap).filter(z => z.valid), [related]);
const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0)
const didZap = zaps.some(a => a.zapper === login);
const groupReactions = useMemo(() => {
return reactions?.reduce((acc, { content }) => {
let r = normalizeReaction(content);
@ -97,10 +101,11 @@ export default function NoteFooter(props: NoteFooterProps) {
if (service) {
return (
<>
<div className="reaction-pill" onClick={() => setTip(true)}>
<div className={`reaction-pill ${didZap ? 'reacted' : ''}`} onClick={() => setTip(true)}>
<div className="reaction-pill-icon">
<Zap />
</div>
{zapTotal > 0 && (<div className="reaction-pill-number">{formatShort(zapTotal)}</div>)}
</div>
</>
)
@ -259,7 +264,7 @@ export default function NoteFooter(props: NoteFooterProps) {
show={reply}
setShow={setReply}
/>
<LNURLTip svc={author?.lud16 || author?.lud06} onClose={() => setTip(false)} show={tip} />
<LNURLTip svc={author?.lud16 || author?.lud06} onClose={() => setTip(false)} show={tip} author={author?.pubkey} note={ev.Id} />
</div>
)
}

View File

@ -7,6 +7,7 @@ import useTimelineFeed, { TimelineSubject } from "Feed/TimelineFeed";
import { TaggedRawEvent } from "Nostr";
import EventKind from "Nostr/EventKind";
import LoadMore from "Element/LoadMore";
import Zap, { parseZap } from "Element/Zap";
import Note from "Element/Note";
import NoteReaction from "Element/NoteReaction";
import useModeration from "Hooks/useModeration";
@ -50,6 +51,9 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod
case EventKind.TextNote: {
return <Note key={e.id} data={e} related={related.notes} ignoreModeration={ignoreModeration} />
}
case EventKind.ZapReceipt: {
return <Zap zap={parseZap(e)} />
}
case EventKind.Reaction:
case EventKind.Repost: {
let eRef = e.tags.find(a => a[0] === "e")?.at(1);