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