diff --git a/src/element/Invoice.css b/src/element/Invoice.css index ff569555..3f2021fe 100644 --- a/src/element/Invoice.css +++ b/src/element/Invoice.css @@ -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; } \ No newline at end of file diff --git a/src/element/Invoice.js b/src/element/Invoice.js index 2d769e1c..1e00e65e 100644 --- a/src/element/Invoice.js +++ b/src/element/Invoice.js @@ -45,7 +45,7 @@ export default function Invoice(props) { return ( -
+
{header()} {info?.expire ? {info?.expired ? "Expired" : "Expires"} {moment(info.expire * 1000).fromNow()} : null} diff --git a/src/element/LNURLTip.css b/src/element/LNURLTip.css index 23e1e4e4..785f3195 100644 --- a/src/element/LNURLTip.css +++ b/src/element/LNURLTip.css @@ -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; + } } \ No newline at end of file diff --git a/src/element/LNURLTip.js b/src/element/LNURLTip.js index df44ed81..3d856b14 100644 --- a/src/element/LNURLTip.js +++ b/src/element/LNURLTip.js @@ -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 ( +
payWebLN()}>Pay with WebLN
+ ) } return null; - }, [payService]); + } - if (!show) return null; - return ( - onClose()}> -
e.stopPropagation()}> -

⚡️ Send sats

+ function invoiceForm() { + if (invoice) return null; + return ( + <>
{metadata?.description ?? service}
{payService?.commentAllowed > 0 ? @@ -133,8 +157,51 @@ export default function LNURLTip(props) { : null}
{amount === -1 ? custom() : null} + {amount > 0 ?
loadInvoice()}>Get Invoice
: null} + + ) + } + + function payInvoice() { + if(success) return null; + const pr = invoice.pr; + return ( + <> +
+
+ +
+
+ {pr ? <> + {webLn()} +
window.open(`lightning:${pr}`)}>Open Wallet
+
navigator.clipboard.writeText(pr)}>Copy Invoice
+ : null} +
+
+ + ) + } + + function successAction() { + if(!success) return null; + return ( + <> +

{success?.description ?? "Paid!"}

+ {success.url ? {success.url} : null} + + ) + } + + if (!show) return null; + return ( + onClose()}> +
e.stopPropagation()}> +

⚡️ Send sats

+ {invoiceForm()} {error ?

{error}

: null} - + {payInvoice()} + {successAction()}
) diff --git a/src/element/QrCode.js b/src/element/QrCode.js index 50727b93..4654a1fe 100644 --- a/src/element/QrCode.js +++ b/src/element/QrCode.js @@ -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 (