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