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