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