ReceiveOffer.tsx raw

   1  import { CircleAlertIcon, CopyIcon, RefreshCwIcon } from "lucide-react";
   2  import React from "react";
   3  import { toast } from "sonner";
   4  import AppHeader from "src/components/AppHeader";
   5  import QRCode from "src/components/QRCode";
   6  import {
   7    Alert,
   8    AlertDescription,
   9    AlertTitle,
  10  } from "src/components/ui/alert.tsx";
  11  import { Button } from "src/components/ui/button";
  12  import {
  13    Card,
  14    CardContent,
  15    CardFooter,
  16    CardHeader,
  17    CardTitle,
  18  } from "src/components/ui/card";
  19  import { LoadingButton } from "src/components/ui/custom/loading-button";
  20  import { Input } from "src/components/ui/input";
  21  import { Label } from "src/components/ui/label";
  22  
  23  import { copyToClipboard } from "src/lib/clipboard";
  24  import { CreateOfferRequest } from "src/types";
  25  import { request } from "src/utils/request";
  26  
  27  export default function ReceiveOffer() {
  28    const [isLoading, setLoading] = React.useState(false);
  29    const [description, setDescription] = React.useState<string>("");
  30    const [offer, setOffer] = React.useState<string | null>(null);
  31  
  32    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
  33      event.preventDefault();
  34  
  35      try {
  36        setLoading(true);
  37        const offer = await request<string>("/api/offers", {
  38          method: "POST",
  39          headers: {
  40            "Content-Type": "application/json",
  41          },
  42          body: JSON.stringify({
  43            description,
  44          } as CreateOfferRequest),
  45        });
  46  
  47        if (offer) {
  48          setOffer(offer);
  49  
  50          toast("Successfully created BOLT-12 offer");
  51        }
  52      } catch (e) {
  53        toast.error("Failed to create offer", {
  54          description: "" + e,
  55        });
  56        console.error(e);
  57      } finally {
  58        setLoading(false);
  59      }
  60    };
  61  
  62    const copy = () => {
  63      copyToClipboard(offer as string);
  64    };
  65  
  66    return (
  67      <div className="grid gap-5">
  68        <AppHeader
  69          pageTitle={offer ? "Lightning Offer" : "Create Lightning Offer"}
  70          title={offer ? "Lightning Offer" : "Create Lightning Offer"}
  71          description={
  72            !offer ? "Create a reusable, non-expiring lightning invoice" : ""
  73          }
  74        />
  75        <div className="grid gap-6 md:max-w-lg">
  76          {!offer && (
  77            <Alert>
  78              <CircleAlertIcon className="h-4 w-4" />
  79              <AlertTitle>BOLT-12 Offers are in beta</AlertTitle>
  80              <AlertDescription>
  81                BOLT-12 is not supported by all wallets and nodes in the lightning
  82                network. This feature will work only if you have a channel with a
  83                node that supports onion message forwarding, and are paid by a
  84                lightning wallet that supports paying BOLT-12 offers.
  85              </AlertDescription>
  86            </Alert>
  87          )}
  88          {offer ? (
  89            <Card>
  90              <CardHeader>
  91                <CardTitle className="text-center">Lightning Offer</CardTitle>
  92              </CardHeader>
  93              <CardContent className="flex flex-col items-center gap-6">
  94                <QRCode
  95                  value={offer}
  96                  className="w-full"
  97                  paymentType="lightning"
  98                />
  99                {description && (
 100                  <p className="text-muted-foreground my-2">{description}</p>
 101                )}
 102              </CardContent>
 103              <CardFooter className="flex flex-col gap-3">
 104                <Button className="w-full" onClick={copy} variant="secondary">
 105                  <CopyIcon className="size-4" />
 106                  Copy Offer
 107                </Button>
 108                <Button
 109                  className="w-full"
 110                  variant="outline"
 111                  onClick={() => {
 112                    setDescription("");
 113                    setOffer(null);
 114                  }}
 115                >
 116                  <RefreshCwIcon className="size-4 shrink-0" />
 117                  New Offer
 118                </Button>
 119              </CardFooter>
 120            </Card>
 121          ) : (
 122            <form onSubmit={handleSubmit} className="grid gap-6">
 123              <div className="grid gap-2">
 124                <Label htmlFor="description">Description</Label>
 125                <Input
 126                  id="description"
 127                  type="text"
 128                  value={description}
 129                  placeholder="For e.g. what is this payment for?"
 130                  onChange={(e) => {
 131                    setDescription(e.target.value);
 132                  }}
 133                />
 134              </div>
 135              <div>
 136                <LoadingButton
 137                  className="w-full md:w-auto"
 138                  loading={isLoading}
 139                  type="submit"
 140                >
 141                  Create Offer
 142                </LoadingButton>
 143              </div>
 144            </form>
 145          )}
 146        </div>
 147      </div>
 148    );
 149  }
 150