snort/packages/app/src/Element/Invoice.tsx

115 lines
3.4 KiB
TypeScript
Raw Normal View History

2023-01-02 13:40:42 +00:00
import "./Invoice.css";
2023-01-09 22:26:41 +00:00
import { useState } from "react";
2023-02-08 21:10:26 +00:00
import { useIntl, FormattedMessage } from "react-intl";
2023-01-02 13:40:42 +00:00
import { decode as invoiceDecode } from "light-bolt11-decoder";
import { useMemo } from "react";
2023-02-07 13:32:32 +00:00
import SendSats from "Element/SendSats";
2023-02-01 22:42:46 +00:00
import ZapCircle from "Icons/ZapCircle";
import useWebln from "Hooks/useWebln";
2023-01-02 13:40:42 +00:00
2023-02-08 21:10:26 +00:00
import messages from "./messages";
2023-01-16 17:48:25 +00:00
export interface InvoiceProps {
invoice: string;
2023-01-16 17:48:25 +00:00
}
2023-02-07 19:47:57 +00:00
2023-01-16 17:48:25 +00:00
export default function Invoice(props: InvoiceProps) {
const invoice = props.invoice;
const webln = useWebln();
const [showInvoice, setShowInvoice] = useState(false);
2023-02-08 21:10:26 +00:00
const { formatMessage } = useIntl();
2023-01-02 13:40:42 +00:00
const info = useMemo(() => {
try {
2023-02-07 19:47:57 +00:00
const parsed = invoiceDecode(invoice);
2023-01-09 22:26:41 +00:00
2023-02-09 12:26:54 +00:00
const amountSection = parsed.sections.find(a => a.name === "amount");
const amount = amountSection ? (amountSection.value as number) : NaN;
const timestampSection = parsed.sections.find(a => a.name === "timestamp");
const timestamp = timestampSection ? (timestampSection.value as number) : NaN;
const expirySection = parsed.sections.find(a => a.name === "expiry");
const expire = expirySection ? (expirySection.value as number) : NaN;
const descriptionSection = parsed.sections.find(a => a.name === "description")?.value;
2023-02-07 19:47:57 +00:00
const ret = {
amount: !isNaN(amount) ? amount / 1000 : 0,
expire: !isNaN(timestamp) && !isNaN(expire) ? timestamp + expire : null,
2023-02-09 12:26:54 +00:00
description: descriptionSection as string | undefined,
expired: false,
};
if (ret.expire) {
ret.expired = ret.expire < new Date().getTime() / 1000;
}
return ret;
} catch (e) {
console.error(e);
}
}, [invoice]);
2023-01-02 13:40:42 +00:00
const [isPaid, setIsPaid] = useState(false);
const isExpired = info?.expired;
const amount = info?.amount ?? 0;
const description = info?.description;
2023-02-01 22:42:46 +00:00
function header() {
return (
<>
2023-02-08 21:10:26 +00:00
<h4>
<FormattedMessage {...messages.Invoice} />
</h4>
<ZapCircle className="zap-circle" />
<SendSats
2023-02-08 21:10:26 +00:00
title={formatMessage(messages.PayInvoice)}
invoice={invoice}
show={showInvoice}
onClose={() => setShowInvoice(false)}
/>
</>
);
}
2023-01-06 19:57:26 +00:00
2023-02-07 19:47:57 +00:00
async function payInvoice(e: React.MouseEvent<HTMLButtonElement>) {
e.stopPropagation();
if (webln?.enabled) {
try {
await webln.sendPayment(invoice);
setIsPaid(true);
} catch (error) {
setShowInvoice(true);
}
} else {
setShowInvoice(true);
}
}
return (
<>
2023-02-09 12:26:54 +00:00
<div className={`note-invoice flex ${isExpired ? "expired" : ""} ${isPaid ? "paid" : ""}`}>
<div className="invoice-header">{header()}</div>
2023-01-06 19:57:26 +00:00
<p className="invoice-amount">
{amount > 0 && (
<>
2023-02-09 12:26:54 +00:00
{amount.toLocaleString()} <span className="sats">sat{amount === 1 ? "" : "s"}</span>
</>
)}
</p>
2023-01-12 06:07:48 +00:00
<div className="invoice-body">
{description && <p>{description}</p>}
{isPaid ? (
2023-02-08 21:10:26 +00:00
<div className="paid">
<FormattedMessage {...messages.Paid} />
</div>
) : (
<button disabled={isExpired} type="button" onClick={payInvoice}>
2023-02-09 12:26:54 +00:00
{isExpired ? <FormattedMessage {...messages.Expired} /> : <FormattedMessage {...messages.Pay} />}
</button>
)}
</div>
</div>
</>
);
2023-02-01 22:42:46 +00:00
}