This commit is contained in:
Kieran 2023-01-08 20:36:36 +00:00
parent 0e0266c813
commit 1310c87438
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
5 changed files with 148 additions and 54 deletions

View File

@ -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;
}

View File

@ -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}

View File

@ -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;
}
}

View File

@ -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>
)

View File

@ -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>