Zap flow
This commit is contained in:
parent
0ad5e387a1
commit
6a0ee5362a
@ -8,6 +8,7 @@
|
|||||||
"@testing-library/react": "^13.0.0",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
"hls.js": "^1.4.6",
|
"hls.js": "^1.4.6",
|
||||||
|
"qr-code-styling": "^1.6.0-rc.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-intersection-observer": "^9.5.1",
|
"react-intersection-observer": "^9.5.1",
|
||||||
@ -42,6 +43,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
|
"@webbtc/webln-types": "^1.0.12",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
src/d.ts
2
src/d.ts
@ -1,3 +1,5 @@
|
|||||||
|
/// <reference types="@webbtc/webln-types" />
|
||||||
|
|
||||||
declare module "*.jpg" {
|
declare module "*.jpg" {
|
||||||
const value: unknown;
|
const value: unknown;
|
||||||
export default value;
|
export default value;
|
||||||
|
@ -66,9 +66,12 @@
|
|||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.live-chat .messages .pill {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.live-chat .zap {
|
.live-chat .zap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: inherit;
|
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import { Icon } from "./icon";
|
|||||||
import Spinner from "./spinner";
|
import Spinner from "./spinner";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "hooks/login";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
import { formatSats, formatShort } from "number";
|
||||||
|
|
||||||
export interface LiveChatOptions {
|
export interface LiveChatOptions {
|
||||||
canWrite?: boolean,
|
canWrite?: boolean,
|
||||||
@ -107,21 +108,25 @@ function ChatZap({ ev }: { ev: TaggedRawEvent }) {
|
|||||||
useUserProfile(System, parsed.anonZap ? undefined : parsed.sender);
|
useUserProfile(System, parsed.anonZap ? undefined : parsed.sender);
|
||||||
|
|
||||||
if (!parsed.valid) {
|
if (!parsed.valid) {
|
||||||
console.debug(parsed);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="zap pill">
|
<div className="pill">
|
||||||
<Icon name="zap" />
|
<div className="zap">
|
||||||
<Profile pubkey={parsed.anonZap ? "" : (parsed.sender ?? "")} options={{
|
<Icon name="zap" />
|
||||||
showAvatar: !parsed.anonZap,
|
<Profile pubkey={parsed.anonZap ? "" : (parsed.sender ?? "")} options={{
|
||||||
overrideName: parsed.anonZap ? "Anonymous" : undefined
|
showAvatar: !parsed.anonZap,
|
||||||
}} />
|
overrideName: parsed.anonZap ? "Anonymous" : undefined
|
||||||
zapped
|
}} />
|
||||||
|
zapped
|
||||||
{parsed.amount}
|
|
||||||
|
{formatSats(parsed.amount)}
|
||||||
sats
|
|
||||||
|
sats
|
||||||
|
</div>
|
||||||
|
{parsed.content && <p>
|
||||||
|
{parsed.content}
|
||||||
|
</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,16 +1,18 @@
|
|||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import { HTMLProps, useEffect, useRef } from "react";
|
import { HTMLProps, useEffect, useMemo, useRef } from "react";
|
||||||
|
|
||||||
export function LiveVideoPlayer(props: HTMLProps<HTMLVideoElement> & { stream?: string }) {
|
export function LiveVideoPlayer(props: HTMLProps<HTMLVideoElement> & { stream?: string }) {
|
||||||
const video = useRef<HTMLVideoElement>(null);
|
const video = useRef<HTMLVideoElement>(null);
|
||||||
|
const streamCached = useMemo(() => props.stream, [props.stream]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.stream && video.current && !video.current.src && Hls.isSupported()) {
|
if (streamCached && video.current && !video.current.src && Hls.isSupported()) {
|
||||||
const hls = new Hls();
|
const hls = new Hls();
|
||||||
hls.loadSource(props.stream);
|
hls.loadSource(streamCached);
|
||||||
hls.attachMedia(video.current);
|
hls.attachMedia(video.current);
|
||||||
return () => hls.destroy();
|
return () => hls.destroy();
|
||||||
}
|
}
|
||||||
}, [video, props]);
|
}, [video, streamCached]);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<video ref={video} {...props} controls={true} />
|
<video ref={video} {...props} controls={true} />
|
||||||
|
50
src/element/qr-code.tsx
Normal file
50
src/element/qr-code.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import QRCodeStyling from "qr-code-styling";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export interface QrCodeProps {
|
||||||
|
data?: string;
|
||||||
|
link?: string;
|
||||||
|
avatar?: string;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QrCode(props: QrCodeProps) {
|
||||||
|
const qrRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ((props.data?.length ?? 0) > 0 && qrRef.current) {
|
||||||
|
const qr = new QRCodeStyling({
|
||||||
|
width: props.width || 256,
|
||||||
|
height: props.height || 256,
|
||||||
|
data: props.data,
|
||||||
|
margin: 5,
|
||||||
|
type: "canvas",
|
||||||
|
image: props.avatar,
|
||||||
|
dotsOptions: {
|
||||||
|
type: "rounded",
|
||||||
|
},
|
||||||
|
cornersSquareOptions: {
|
||||||
|
type: "extra-rounded",
|
||||||
|
},
|
||||||
|
imageOptions: {
|
||||||
|
crossOrigin: "anonymous",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
qrRef.current.innerHTML = "";
|
||||||
|
qr.append(qrRef.current);
|
||||||
|
if (props.link) {
|
||||||
|
qrRef.current.onclick = function () {
|
||||||
|
const elm = document.createElement("a");
|
||||||
|
elm.href = props.link ?? "";
|
||||||
|
elm.click();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (qrRef.current) {
|
||||||
|
qrRef.current.innerHTML = "";
|
||||||
|
}
|
||||||
|
}, [props.data, props.link]);
|
||||||
|
|
||||||
|
return <div className={`qr${props.className ? ` ${props.className}` : ""}`} ref={qrRef}></div>;
|
||||||
|
}
|
61
src/element/send-zap.css
Normal file
61
src/element/send-zap.css
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
.send-zap {
|
||||||
|
width: inherit;
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap small {
|
||||||
|
display: block;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #868686;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap .amounts {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
justify-content: space-evenly;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap .pill {
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #262626;
|
||||||
|
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap .pill.active {
|
||||||
|
color: inherit;
|
||||||
|
background: #353535;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap div.input {
|
||||||
|
background: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap p {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap .btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap .btn>span {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-zap .qr {
|
||||||
|
align-self: center;
|
||||||
|
}
|
115
src/element/send-zap.tsx
Normal file
115
src/element/send-zap.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import "./send-zap.css";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { LNURL } from "@snort/shared";
|
||||||
|
import { NostrEvent, EventPublisher } from "@snort/system";
|
||||||
|
import { formatSats } from "../number";
|
||||||
|
import { Icon } from "./icon";
|
||||||
|
import AsyncButton from "./async-button";
|
||||||
|
import { findTag } from "utils";
|
||||||
|
import { Relays } from "index";
|
||||||
|
import QrCode from "./qr-code";
|
||||||
|
|
||||||
|
export function SendZaps({ lnurl, ev, targetName, onFinish }: { lnurl: string, ev?: NostrEvent, targetName?: string, onFinish: () => void }) {
|
||||||
|
const UsdRate = 30_000;
|
||||||
|
|
||||||
|
const satsAmounts = [
|
||||||
|
100, 1_000, 5_000, 10_000, 50_000, 100_000, 500_000, 1_000_000
|
||||||
|
];
|
||||||
|
const usdAmounts = [
|
||||||
|
0.05, 0.50, 2, 5, 10, 50, 100, 200
|
||||||
|
]
|
||||||
|
const [isFiat, setIsFiat] = useState(false);
|
||||||
|
const [svc, setSvc] = useState<LNURL>();
|
||||||
|
const [amount, setAmount] = useState(satsAmounts[0]);
|
||||||
|
const [comment, setComment] = useState("");
|
||||||
|
const [invoice, setInvoice] = useState("");
|
||||||
|
|
||||||
|
const name = targetName ?? svc?.name;
|
||||||
|
async function loadService() {
|
||||||
|
const s = new LNURL(lnurl);
|
||||||
|
await s.load();
|
||||||
|
setSvc(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!svc) {
|
||||||
|
loadService().catch(console.warn);
|
||||||
|
}
|
||||||
|
}, [lnurl]);
|
||||||
|
|
||||||
|
async function send() {
|
||||||
|
if (!svc) return;
|
||||||
|
const pub = await EventPublisher.nip7();
|
||||||
|
if (!pub) return;
|
||||||
|
|
||||||
|
const amountInSats = isFiat ? Math.floor((amount / UsdRate) * 1e8) : amount;
|
||||||
|
let zap: NostrEvent | undefined;
|
||||||
|
if (ev) {
|
||||||
|
zap = await pub.zap(amountInSats * 1000, ev.pubkey, Relays, undefined, comment, eb => {
|
||||||
|
return eb.tag(["a", `${ev.kind}:${ev.pubkey}:${findTag(ev, "d")}`]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const invoice = await svc.getInvoice(amountInSats, comment, zap);
|
||||||
|
if (!invoice.pr) return;
|
||||||
|
|
||||||
|
if (window.webln) {
|
||||||
|
await window.webln.enable();
|
||||||
|
await window.webln.sendPayment(invoice.pr);
|
||||||
|
onFinish();
|
||||||
|
} else {
|
||||||
|
setInvoice(invoice.pr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function input() {
|
||||||
|
if (invoice) return;
|
||||||
|
return <>
|
||||||
|
<div className="flex g12">
|
||||||
|
<span className={`pill${isFiat ? "" : " active"}`} onClick={() => { setIsFiat(false); setAmount(satsAmounts[0]) }}>
|
||||||
|
SATS
|
||||||
|
</span>
|
||||||
|
<span className={`pill${isFiat ? " active" : ""}`} onClick={() => { setIsFiat(true); setAmount(usdAmounts[0]) }}>
|
||||||
|
USD
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small>Zap amount in {isFiat ? "USD" : "sats"}</small>
|
||||||
|
<div className="amounts">
|
||||||
|
{(isFiat ? usdAmounts : satsAmounts).map(a =>
|
||||||
|
<span key={a} className={`pill${a === amount ? " active" : ""}`} onClick={() => setAmount(a)}>
|
||||||
|
{isFiat ? `$${a.toLocaleString()}` : formatSats(a)}
|
||||||
|
</span>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small>
|
||||||
|
Your comment for {name}
|
||||||
|
</small>
|
||||||
|
<div className="input">
|
||||||
|
<textarea placeholder="Nice!" value={comment} onChange={e => setComment(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<AsyncButton onClick={send} className="btn btn-primary">
|
||||||
|
Zap!
|
||||||
|
</AsyncButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function payInvoice() {
|
||||||
|
if (!invoice) return;
|
||||||
|
|
||||||
|
const link = `lightning:${invoice}`;
|
||||||
|
return <QrCode data={link} link={link} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="send-zap">
|
||||||
|
<h3>
|
||||||
|
Zap {name}
|
||||||
|
<Icon name="zap" />
|
||||||
|
</h3>
|
||||||
|
{input()}
|
||||||
|
{payInvoice()}
|
||||||
|
</div>
|
||||||
|
}
|
@ -32,6 +32,8 @@ a {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pill.live {
|
.pill.live {
|
||||||
@ -43,6 +45,10 @@ a {
|
|||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.g12 {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -72,7 +78,7 @@ a {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"], textarea {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
border: unset;
|
border: unset;
|
||||||
background-color: unset;
|
background-color: unset;
|
||||||
|
@ -16,12 +16,14 @@ export const System = new NostrSystem({
|
|||||||
});
|
});
|
||||||
export const Login = new LoginStore();
|
export const Login = new LoginStore();
|
||||||
|
|
||||||
[
|
export const Relays = [
|
||||||
"wss://relay.snort.social",
|
"wss://relay.snort.social",
|
||||||
"wss://nos.lol",
|
"wss://nos.lol",
|
||||||
"wss://relay.damus.io",
|
"wss://relay.damus.io",
|
||||||
"wss://nostr.wine"
|
"wss://nostr.wine"
|
||||||
].forEach(r => System.ConnectToRelay(r, { read: true, write: true }));
|
];
|
||||||
|
|
||||||
|
Relays.forEach(r => System.ConnectToRelay(r, { read: true, write: true }));
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
20
src/number.ts
Normal file
20
src/number.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const intlSats = new Intl.NumberFormat(undefined, {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function formatShort(fmt: Intl.NumberFormat, n: number) {
|
||||||
|
if (n < 2e3) {
|
||||||
|
return n;
|
||||||
|
} else if (n < 1e6) {
|
||||||
|
return `${fmt.format(n / 1e3)}K`;
|
||||||
|
} else if (n < 1e9) {
|
||||||
|
return `${fmt.format(n / 1e6)}M`;
|
||||||
|
} else {
|
||||||
|
return `${fmt.format(n / 1e9)}G`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatSats(n: number) {
|
||||||
|
return formatShort(intlSats, n);
|
||||||
|
}
|
@ -3,4 +3,16 @@
|
|||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width: 1600px) {
|
||||||
|
.video-grid {
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width: 2000px) {
|
||||||
|
.video-grid {
|
||||||
|
grid-template-columns: repeat(8, 1fr);
|
||||||
|
}
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import { System } from "..";
|
|||||||
import { VideoTile } from "../element/video-tile";
|
import { VideoTile } from "../element/video-tile";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
|
|
||||||
export function RootPage() {
|
export function RootPage() {
|
||||||
const rb = new RequestBuilder("root");
|
const rb = new RequestBuilder("root");
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds([30_311 as EventKind]);
|
.kinds([30_311 as EventKind]);
|
||||||
|
@ -54,4 +54,11 @@
|
|||||||
|
|
||||||
.live-page .actions {
|
.live-page .actions {
|
||||||
margin: 8px 0 0 0;
|
margin: 8px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-page .btn.zap {
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
@ -1,16 +1,20 @@
|
|||||||
import "./stream-page.css";
|
import "./stream-page.css";
|
||||||
|
import { useState } from "react";
|
||||||
import { parseNostrLink, EventPublisher } from "@snort/system";
|
import { parseNostrLink, EventPublisher } from "@snort/system";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import useEventFeed from "hooks/event-feed";
|
import useEventFeed from "hooks/event-feed";
|
||||||
import { LiveVideoPlayer } from "element/live-video-player";
|
import { LiveVideoPlayer } from "element/live-video-player";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
import { Profile } from "element/profile";
|
import { Profile, getName } from "element/profile";
|
||||||
import { LiveChat } from "element/live-chat";
|
import { LiveChat } from "element/live-chat";
|
||||||
import AsyncButton from "element/async-button";
|
import AsyncButton from "element/async-button";
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "element/icon";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "hooks/login";
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
|
import Modal from "element/modal";
|
||||||
|
import { SendZaps } from "element/send-zap";
|
||||||
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
export function StreamPage() {
|
export function StreamPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@ -18,11 +22,14 @@ export function StreamPage() {
|
|||||||
const thisEvent = useEventFeed(link);
|
const thisEvent = useEventFeed(link);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [zap, setZap] = useState(false);
|
||||||
|
const profile = useUserProfile(System, thisEvent.data?.pubkey);
|
||||||
|
|
||||||
const stream = findTag(thisEvent.data, "streaming");
|
const stream = findTag(thisEvent.data, "streaming");
|
||||||
const status = findTag(thisEvent.data, "status");
|
const status = findTag(thisEvent.data, "status");
|
||||||
const isLive = status === "live";
|
const isLive = status === "live";
|
||||||
const isMine = link.author === login?.pubkey;
|
const isMine = link.author === login?.pubkey;
|
||||||
|
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||||
|
|
||||||
async function deleteStream() {
|
async function deleteStream() {
|
||||||
const pub = await EventPublisher.nip7();
|
const pub = await EventPublisher.nip7();
|
||||||
@ -66,15 +73,22 @@ export function StreamPage() {
|
|||||||
<Profile
|
<Profile
|
||||||
pubkey={thisEvent.data?.pubkey ?? ""}
|
pubkey={thisEvent.data?.pubkey ?? ""}
|
||||||
/>
|
/>
|
||||||
<AsyncButton onClick={() => { }} className="btn btn-primary">
|
<button onClick={() => setZap(true)} className="btn btn-primary zap">
|
||||||
Zap
|
Zap
|
||||||
<Icon name="zap" size={16} />
|
<Icon name="zap" size={16} />
|
||||||
</AsyncButton>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LiveChat link={link} />
|
<LiveChat link={link} />
|
||||||
|
{zap && zapTarget && thisEvent.data && <Modal onClose={() => setZap(false)}>
|
||||||
|
<SendZaps
|
||||||
|
lnurl={zapTarget}
|
||||||
|
ev={thisEvent.data}
|
||||||
|
targetName={getName(thisEvent.data.pubkey, profile)}
|
||||||
|
onFinish={() => setZap(false)} />
|
||||||
|
</Modal>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
17
yarn.lock
17
yarn.lock
@ -2556,6 +2556,11 @@
|
|||||||
"@webassemblyjs/ast" "1.11.6"
|
"@webassemblyjs/ast" "1.11.6"
|
||||||
"@xtuc/long" "4.2.2"
|
"@xtuc/long" "4.2.2"
|
||||||
|
|
||||||
|
"@webbtc/webln-types@^1.0.12":
|
||||||
|
version "1.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@webbtc/webln-types/-/webln-types-1.0.12.tgz#ddb5f0dbaa0a853ef21a4f36a603199d43cc8682"
|
||||||
|
integrity sha512-uCsJt78RaW/UYDXwAjjs6aj7fiXyozwMknWvPROCaGMx+rXoPddtDjMIMbMFLvUJVQmnyzpqGkx/0jBIvVaVvA==
|
||||||
|
|
||||||
"@xtuc/ieee754@^1.2.0":
|
"@xtuc/ieee754@^1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||||
@ -7638,6 +7643,18 @@ q@^1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||||
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
||||||
|
|
||||||
|
qr-code-styling@^1.6.0-rc.1:
|
||||||
|
version "1.6.0-rc.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/qr-code-styling/-/qr-code-styling-1.6.0-rc.1.tgz#6c89e185fa50cc9135101085c12ae95b06f1b290"
|
||||||
|
integrity sha512-ModRIiW6oUnsP18QzrRYZSc/CFKFKIdj7pUs57AEVH20ajlglRpN3HukjHk0UbNMTlKGuaYl7Gt6/O5Gg2NU2Q==
|
||||||
|
dependencies:
|
||||||
|
qrcode-generator "^1.4.3"
|
||||||
|
|
||||||
|
qrcode-generator@^1.4.3:
|
||||||
|
version "1.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7"
|
||||||
|
integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==
|
||||||
|
|
||||||
qs@6.11.0:
|
qs@6.11.0:
|
||||||
version "6.11.0"
|
version "6.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||||
|
Loading…
Reference in New Issue
Block a user