snort/src/element/LNURLTip.js

215 lines
7.3 KiB
JavaScript
Raw Normal View History

2023-01-07 20:54:12 +00:00
import "./LNURLTip.css";
import { useEffect, useMemo, useState } from "react";
import { bech32ToText } from "../Util";
import Modal from "./Modal";
import QrCode from "./QrCode";
2023-01-11 11:30:43 +00:00
import Copy from "./Copy";
2023-01-07 20:54:12 +00:00
export default function LNURLTip(props) {
const onClose = props.onClose || (() => { });
const service = props.svc;
const show = props.show || false;
const amounts = [50, 100, 500, 1_000, 5_000, 10_000];
const [payService, setPayService] = useState("");
const [amount, setAmount] = useState(0);
2023-01-07 23:01:32 +00:00
const [customAmount, setCustomAmount] = useState(0);
2023-01-12 09:57:35 +00:00
const [invoice, setInvoice] = useState(null);
2023-01-07 20:54:12 +00:00
const [comment, setComment] = useState("");
2023-01-08 20:36:36 +00:00
const [error, setError] = useState("");
const [success, setSuccess] = useState(null);
useEffect(() => {
2023-01-12 09:57:35 +00:00
if (show && invoice === null) {
2023-01-08 20:36:36 +00:00
loadService()
.then(a => setPayService(a))
.catch(() => setError("Failed to load LNURL service"));
} else {
setPayService("");
setError("");
2023-01-12 09:57:35 +00:00
setInvoice(props.invoice ? { pr: props.invoice } : null);
2023-01-08 20:36:36 +00:00
setAmount(0);
setComment("");
setSuccess(null);
}
}, [show, service]);
const serviceAmounts = useMemo(() => {
if (payService) {
let min = (payService.minSendable ?? 0) / 1000;
let max = (payService.maxSendable ?? 0) / 1000;
return amounts.filter(a => a >= min && a <= max);
}
return [];
}, [payService]);
const metadata = useMemo(() => {
if (payService) {
let meta = JSON.parse(payService.metadata);
let desc = meta.find(a => a[0] === "text/plain");
let image = meta.find(a => a[0] === "image/png;base64");
return {
description: desc ? desc[1] : null,
image: image ? image[1] : null
};
}
return null;
}, [payService]);
2023-01-07 20:54:12 +00:00
2023-01-07 23:01:32 +00:00
const selectAmount = (a) => {
setError("");
2023-01-12 09:57:35 +00:00
setInvoice(null);
2023-01-07 23:01:32 +00:00
setAmount(a);
};
2023-01-07 20:54:12 +00:00
async function fetchJson(url) {
let rsp = await fetch(url);
if (rsp.ok) {
let data = await rsp.json();
console.log(data);
2023-01-07 23:01:32 +00:00
setError("");
2023-01-07 20:54:12 +00:00
return data;
}
return null;
}
async function loadService() {
let isServiceUrl = service.toLowerCase().startsWith("lnurl");
if (isServiceUrl) {
let serviceUrl = bech32ToText(service);
return await fetchJson(serviceUrl);
} else {
let ns = service.split("@");
return await fetchJson(`https://${ns[1]}/.well-known/lnurlp/${ns[0]}`);
}
}
async function loadInvoice() {
if (amount === 0) return null;
const url = `${payService.callback}?amount=${parseInt(amount * 1000)}&comment=${encodeURIComponent(comment)}`;
try {
let rsp = await fetch(url);
if (rsp.ok) {
let data = await rsp.json();
console.log(data);
if (data.status === "ERROR") {
setError(data.reason);
} else {
2023-01-08 20:36:36 +00:00
setInvoice(data);
2023-01-07 23:01:32 +00:00
setError("");
2023-01-07 20:54:12 +00:00
}
} else {
setError("Failed to load invoice");
}
} catch (e) {
setError("Failed to load invoice");
}
};
2023-01-07 23:01:32 +00:00
function custom() {
let min = (payService?.minSendable ?? 0) / 1000;
let max = (payService?.maxSendable ?? 21_000_000_000) / 1000;
return (
<div className="flex mb10">
<input type="number" min={min} max={max} className="f-grow mr10" value={customAmount} onChange={(e) => setCustomAmount(e.target.value)} />
<div className="btn" onClick={() => selectAmount(customAmount)}>Confirm</div>
</div>
);
}
2023-01-08 20:36:36 +00:00
async function payWebLN() {
try {
if (!window.webln.enabled) {
await window.webln.enable();
}
let res = await window.webln.sendPayment(invoice.pr);
console.log(res);
setSuccess(invoice.successAction || {});
} catch (e) {
setError(e.toString());
console.warn(e);
2023-01-07 20:54:12 +00:00
}
2023-01-08 20:36:36 +00:00
}
2023-01-07 20:54:12 +00:00
2023-01-08 20:36:36 +00:00
function webLn() {
if ("webln" in window) {
return (
2023-01-11 11:30:43 +00:00
<div className="btn" onClick={() => payWebLN()}>Pay with WebLN</div>
2023-01-08 20:36:36 +00:00
)
2023-01-07 20:54:12 +00:00
}
return null;
2023-01-08 20:36:36 +00:00
}
2023-01-07 20:54:12 +00:00
2023-01-08 20:36:36 +00:00
function invoiceForm() {
if (invoice) return null;
return (
<>
2023-01-07 23:01:32 +00:00
<div className="f-ellipsis mb10">{metadata?.description ?? service}</div>
2023-01-07 20:54:12 +00:00
<div className="flex">
{payService?.commentAllowed > 0 ?
<input type="text" placeholder="Comment" className="mb10 f-grow" maxLength={payService?.commentAllowed} onChange={(e) => setComment(e.target.value)} /> : null}
</div>
<div className="mb10">
2023-01-07 23:01:32 +00:00
{serviceAmounts.map(a => <span className={`pill ${amount === a ? "active" : ""}`} key={a} onClick={() => selectAmount(a)}>
2023-01-07 20:54:12 +00:00
{a.toLocaleString()}
</span>)}
2023-01-07 23:01:32 +00:00
{payService ?
<span className={`pill ${amount === -1 ? "active" : ""}`} onClick={() => selectAmount(-1)}>
Custom
</span> : null}
2023-01-07 20:54:12 +00:00
</div>
2023-01-07 23:01:32 +00:00
{amount === -1 ? custom() : null}
2023-01-08 20:36:36 +00:00
{amount > 0 ? <div className="btn mb10" onClick={() => loadInvoice()}>Get Invoice</div> : null}
</>
)
}
function payInvoice() {
2023-01-12 09:57:35 +00:00
if (success) return null;
const pr = invoice?.pr;
2023-01-08 20:36:36 +00:00
return (
<>
<div className="invoice">
2023-01-11 11:30:43 +00:00
<QrCode data={pr} link={`lightning:${pr}`} />
2023-01-08 20:36:36 +00:00
<div className="actions">
2023-01-11 11:30:43 +00:00
{pr && (
2023-01-12 09:57:35 +00:00
<>
<div className="copy-action">
<Copy text={pr} maxSize={26} />
2023-01-11 11:30:43 +00:00
</div>
2023-01-12 09:57:35 +00:00
<div className="pay-actions">
<div className="btn" onClick={() => window.open(`lightning:${pr}`)}>
Open Wallet
</div>
<div>{webLn()}</div>
</div>
</>
2023-01-11 11:30:43 +00:00
)}
2023-01-08 20:36:36 +00:00
</div>
</div>
</>
)
}
function successAction() {
2023-01-12 09:57:35 +00:00
if (!success) return null;
2023-01-08 20:36:36 +00:00
return (
<>
<p>{success?.description ?? "Paid!"}</p>
{success.url ? <a href={success.url} target="_blank">{success.url}</a> : null}
</>
)
}
if (!show) return null;
return (
<Modal onClose={() => onClose()}>
<div className="lnurl-tip" onClick={(e) => e.stopPropagation()}>
<h2> Send sats</h2>
{invoiceForm()}
2023-01-07 20:54:12 +00:00
{error ? <p className="error">{error}</p> : null}
2023-01-08 20:36:36 +00:00
{payInvoice()}
{successAction()}
2023-01-07 20:54:12 +00:00
</div>
</Modal>
)
}