feat: zaps
This commit is contained in:
@ -103,4 +103,4 @@ export default function HyperText({ link, creator }: { link: string, creator: He
|
||||
}, [link]);
|
||||
|
||||
return render();
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user