ConfirmPayment.tsx raw
1 import React from "react";
2 import { useLocation, useNavigate } from "react-router";
3 import { LinkButton } from "src/components/ui/custom/link-button";
4 import { LoadingButton } from "src/components/ui/custom/loading-button";
5
6 import type { Invoice } from "@getalby/lightning-tools/bolt11";
7 import { ArrowLeftIcon } from "lucide-react";
8 import { toast } from "sonner";
9 import AppHeader from "src/components/AppHeader";
10 import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
11 import FormattedFiatAmount from "src/components/FormattedFiatAmount";
12 import { InsufficientLightningBalanceAlert } from "src/components/InsufficientLightningBalanceAlert";
13 import Loading from "src/components/Loading";
14 import { PaymentFailedAlert } from "src/components/PaymentFailedAlert";
15 import { PendingPaymentAlert } from "src/components/PendingPaymentAlert";
16 import {
17 Card,
18 CardContent,
19 CardFooter,
20 CardHeader,
21 CardTitle,
22 } from "src/components/ui/card";
23 import { useBalances } from "src/hooks/useBalances";
24 import PayFromSelect from "src/screens/wallet/send/PayFromSelect";
25 import {
26 PayInvoiceRequest,
27 PayInvoiceResponse,
28 TransactionMetadata,
29 } from "src/types";
30 import { request } from "src/utils/request";
31
32 export default function ConfirmPayment() {
33 const { state } = useLocation();
34 const navigate = useNavigate();
35 const { data: balances } = useBalances();
36
37 const invoice = state?.args?.paymentRequest as Invoice;
38 const metadata = state?.args?.metadata as TransactionMetadata;
39 const [appId, setAppId] = React.useState<number>();
40 const [isLoading, setLoading] = React.useState(false);
41 const [errorMessage, setErrorMessage] = React.useState("");
42
43 const confirmPayment = async () => {
44 setErrorMessage("");
45 try {
46 setLoading(true);
47 const payload: PayInvoiceRequest = {
48 metadata,
49 fromAppId: appId,
50 };
51 const payInvoiceResponse = await request<PayInvoiceResponse>(
52 `/api/payments/${invoice.paymentRequest}`,
53 {
54 method: "POST",
55 body: JSON.stringify(payload),
56 headers: {
57 "Content-Type": "application/json",
58 },
59 }
60 );
61 if (!payInvoiceResponse?.preimage) {
62 throw new Error("No preimage in response");
63 }
64
65 navigate(`/wallet/send/success`, {
66 state: {
67 preimage: payInvoiceResponse.preimage,
68 pageTitle: "Pay Invoice",
69 invoice,
70 },
71 replace: true,
72 });
73 toast("Successfully paid invoice");
74 } catch (e) {
75 console.error(e);
76 setErrorMessage("" + e);
77 toast.error("Failed to send payment", {
78 description: "" + e,
79 });
80 } finally {
81 setLoading(false);
82 }
83 };
84
85 React.useEffect(() => {
86 if (!invoice) {
87 navigate("/wallet/send");
88 }
89 }, [navigate, invoice]);
90
91 if (!balances || !invoice) {
92 return <Loading />;
93 }
94
95 return (
96 <div className="grid gap-4">
97 <AppHeader pageTitle="Pay Invoice" title="Pay Invoice" />
98 <div className="max-w-lg grid gap-4">
99 <PendingPaymentAlert />
100 {errorMessage && (
101 <PaymentFailedAlert
102 errorMessage={errorMessage}
103 invoice={invoice.paymentRequest}
104 />
105 )}
106 </div>
107 <div className="w-full md:max-w-lg">
108 <Card>
109 <CardHeader>
110 <CardTitle className="text-center">Confirm Payment</CardTitle>
111 </CardHeader>
112 <CardContent className="grid gap-6 pt-2">
113 <div className="flex flex-col gap-1 items-center">
114 <p className="text-2xl font-medium slashed-zero">
115 <FormattedBitcoinAmount amountMsat={invoice.satoshi * 1000} />
116 </p>
117 <FormattedFiatAmount
118 amountSat={invoice.satoshi}
119 className="text-xl"
120 />
121 </div>
122 {invoice.description && (
123 <p className="text-lg text-center text-muted-foreground break-anywhere">
124 {invoice.description}
125 </p>
126 )}
127 </CardContent>
128 <CardFooter className="flex flex-col gap-2 pt-2">
129 <PayFromSelect appId={appId} onChange={setAppId} />
130 <InsufficientLightningBalanceAlert
131 className="mb-2"
132 amountSat={invoice.satoshi}
133 />
134 <LoadingButton
135 onClick={confirmPayment}
136 loading={isLoading}
137 type="submit"
138 className="w-full"
139 autoFocus
140 >
141 Confirm Payment
142 </LoadingButton>
143 <div className="flex items-center justify-between gap-2 text-muted-foreground text-xs sensitive slashed-zero">
144 Lightning Balance:{" "}
145 <FormattedBitcoinAmount
146 amountMsat={balances.lightning.totalSpendableMsat}
147 />
148 </div>
149 <LinkButton to="/wallet/send" variant="link" className="w-full">
150 <ArrowLeftIcon className="w-4 h-4 mr-2" />
151 Back
152 </LinkButton>
153 </CardFooter>
154 </Card>
155 </div>
156 </div>
157 );
158 }
159