WebLN
This commit is contained in:
parent
0e0266c813
commit
1310c87438
@ -1,13 +1,13 @@
|
||||
.invoice {
|
||||
.note-invoice {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #444;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.invoice h2, .invoice h4, .invoice p {
|
||||
.note-invoice h2, .note-invoice h4, .note-invoice p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.invoice small {
|
||||
.note-invoice small {
|
||||
color: #666;
|
||||
}
|
@ -45,7 +45,7 @@ export default function Invoice(props) {
|
||||
|
||||
|
||||
return (
|
||||
<div className="invoice flex">
|
||||
<div className="note-invoice flex">
|
||||
<div className="f-grow flex f-col">
|
||||
{header()}
|
||||
{info?.expire ? <small>{info?.expired ? "Expired" : "Expires"} {moment(info.expire * 1000).fromNow()}</small> : null}
|
||||
|
@ -7,9 +7,36 @@
|
||||
min-height: 10vh;
|
||||
}
|
||||
|
||||
.lnurl-tip .btn {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.lnurl-tip .btn:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions {
|
||||
align-items: flex-start;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.lnurl-tip .invoice .actions > div {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
@media(max-width: 720px) {
|
||||
.lnurl-tip {
|
||||
width: 100vw;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.lnurl-tip .invoice {
|
||||
flex-direction: column;
|
||||
}
|
||||
.lnurl-tip .invoice .actions {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
@ -14,7 +14,45 @@ export default function LNURLTip(props) {
|
||||
const [customAmount, setCustomAmount] = useState(0);
|
||||
const [invoice, setInvoice] = useState("");
|
||||
const [comment, setComment] = useState("");
|
||||
const [error, setError] = useState("")
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
loadService()
|
||||
.then(a => setPayService(a))
|
||||
.catch(() => setError("Failed to load LNURL service"));
|
||||
} else {
|
||||
setPayService("");
|
||||
setError("");
|
||||
setInvoice("");
|
||||
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]);
|
||||
|
||||
const selectAmount = (a) => {
|
||||
setError("");
|
||||
@ -55,7 +93,7 @@ export default function LNURLTip(props) {
|
||||
if (data.status === "ERROR") {
|
||||
setError(data.reason);
|
||||
} else {
|
||||
setInvoice(data.pr);
|
||||
setInvoice(data);
|
||||
setError("");
|
||||
}
|
||||
} else {
|
||||
@ -77,47 +115,33 @@ export default function LNURLTip(props) {
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (payService && amount > 0) {
|
||||
loadInvoice();
|
||||
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);
|
||||
}
|
||||
}, [payService, amount]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
loadService()
|
||||
.then(a => setPayService(a))
|
||||
.catch(() => setError("Failed to load LNURL service"));
|
||||
}
|
||||
}, [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
|
||||
};
|
||||
function webLn() {
|
||||
if ("webln" in window) {
|
||||
return (
|
||||
<div className="btn mb10" onClick={() => payWebLN()}>Pay with WebLN</div>
|
||||
)
|
||||
}
|
||||
return null;
|
||||
}, [payService]);
|
||||
}
|
||||
|
||||
if (!show) return null;
|
||||
return (
|
||||
<Modal onClose={() => onClose()}>
|
||||
<div className="lnurl-tip" onClick={(e) => e.stopPropagation()}>
|
||||
<h2>⚡️ Send sats</h2>
|
||||
function invoiceForm() {
|
||||
if (invoice) return null;
|
||||
return (
|
||||
<>
|
||||
<div className="f-ellipsis mb10">{metadata?.description ?? service}</div>
|
||||
<div className="flex">
|
||||
{payService?.commentAllowed > 0 ?
|
||||
@ -133,8 +157,51 @@ export default function LNURLTip(props) {
|
||||
</span> : null}
|
||||
</div>
|
||||
{amount === -1 ? custom() : null}
|
||||
{amount > 0 ? <div className="btn mb10" onClick={() => loadInvoice()}>Get Invoice</div> : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function payInvoice() {
|
||||
if(success) return null;
|
||||
const pr = invoice.pr;
|
||||
return (
|
||||
<>
|
||||
<div className="invoice">
|
||||
<div>
|
||||
<QrCode data={pr} link={`lightning:${pr}`} />
|
||||
</div>
|
||||
<div className="actions">
|
||||
{pr ? <>
|
||||
{webLn()}
|
||||
<div className="btn" onClick={() => window.open(`lightning:${pr}`)}>Open Wallet</div>
|
||||
<div className="btn" onClick={() => navigator.clipboard.writeText(pr)}>Copy Invoice</div>
|
||||
</> : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function successAction() {
|
||||
if(!success) return null;
|
||||
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()}
|
||||
{error ? <p className="error">{error}</p> : null}
|
||||
<QrCode link={invoice} />
|
||||
{payInvoice()}
|
||||
{successAction()}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
@ -1,17 +1,15 @@
|
||||
import QRCodeStyling from "qr-code-styling";
|
||||
import {useEffect, useRef} from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function QrCode(props) {
|
||||
const qrRef = useRef();
|
||||
const link = props.link;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Showing QR: ", link);
|
||||
if (link?.length > 0) {
|
||||
if (props.data?.length > 0) {
|
||||
let qr = new QRCodeStyling({
|
||||
width: props.width || 256,
|
||||
height: props.height || 256,
|
||||
data: link,
|
||||
data: props.data,
|
||||
margin: 5,
|
||||
type: 'canvas',
|
||||
image: props.avatar,
|
||||
@ -27,15 +25,17 @@ export default function QrCode(props) {
|
||||
});
|
||||
qrRef.current.innerHTML = "";
|
||||
qr.append(qrRef.current);
|
||||
qrRef.current.onclick = function (e) {
|
||||
let elm = document.createElement("a");
|
||||
elm.href = `lightning:${link}`;
|
||||
elm.click();
|
||||
if (props.link) {
|
||||
qrRef.current.onclick = function (e) {
|
||||
let elm = document.createElement("a");
|
||||
elm.href = props.link;
|
||||
elm.click();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qrRef.current.innerHTML = "";
|
||||
}
|
||||
}, [link]);
|
||||
}, [props.data, props.link]);
|
||||
|
||||
return (
|
||||
<div className="qr" ref={qrRef}></div>
|
||||
|
Loading…
Reference in New Issue
Block a user