SwapOutStatus.tsx raw

   1  import {
   2    CircleAlertIcon,
   3    CircleCheckIcon,
   4    CircleXIcon,
   5    CopyIcon,
   6  } from "lucide-react";
   7  import { useParams } from "react-router";
   8  import AppHeader from "src/components/AppHeader";
   9  import ExternalLink from "src/components/ExternalLink";
  10  import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
  11  import FormattedFiatAmount from "src/components/FormattedFiatAmount";
  12  import Loading from "src/components/Loading";
  13  import LottieLoading from "src/components/LottieLoading";
  14  import { Button } from "src/components/ui/button";
  15  import {
  16    Card,
  17    CardContent,
  18    CardDescription,
  19    CardHeader,
  20    CardTitle,
  21  } from "src/components/ui/card";
  22  import { useInfo } from "src/hooks/useInfo";
  23  import { useSwap } from "src/hooks/useSwaps";
  24  import { copyToClipboard } from "src/lib/clipboard";
  25  import { SwapOut } from "src/types";
  26  
  27  export default function SwapOutStatus() {
  28    const { data: info } = useInfo();
  29    const { swapId } = useParams() as { swapId: string };
  30    const { data: swap } = useSwap<SwapOut>(swapId, true);
  31  
  32    if (!swap) {
  33      return <Loading />;
  34    }
  35  
  36    const copyTxId = () => {
  37      copyToClipboard(swap.claimTxId as string);
  38    };
  39  
  40    const swapStatus = swap.state;
  41    const statusText = {
  42      SUCCESS: "Swap Successful",
  43      FAILED: "Swap Failed",
  44      PENDING: swap.lockupTxId
  45        ? "Waiting for confirmation"
  46        : "Making lightning payment",
  47    };
  48  
  49    return (
  50      <div className="grid gap-5">
  51        <AppHeader pageTitle="Swap Out" title="Swap Out" />
  52        <div className="w-full max-w-lg">
  53          <Card className="w-full md:max-w-xs">
  54            <CardHeader>
  55              <CardTitle className="flex justify-center">
  56                {swapStatus === "PENDING" && <Loading className="w-4 h-4 mr-2" />}
  57                {statusText[swapStatus]}
  58              </CardTitle>
  59              <CardDescription className="text-muted-foreground text-sm">
  60                {swap.autoSwap && (
  61                  <p className="text-center mb-1">
  62                    Auto swap{swap.usedXpub && <> to xpub</>}
  63                  </p>
  64                )}
  65                <div className="flex items-center justify-center gap-2">
  66                  Swap ID: {swap.id}{" "}
  67                  <CopyIcon
  68                    className="cursor-pointer text-muted-foreground size-4"
  69                    onClick={() => {
  70                      copyToClipboard(swap.id);
  71                    }}
  72                  />
  73                </div>
  74              </CardDescription>
  75            </CardHeader>
  76            <CardContent className="flex flex-col items-center gap-4">
  77              {swapStatus === "SUCCESS" ? (
  78                <>
  79                  <CircleCheckIcon className="w-60 h-60" />
  80                  <div className="flex flex-col gap-2 items-center">
  81                    <p className="text-xl font-bold slashed-zero text-center">
  82                      <FormattedBitcoinAmount
  83                        amountMsat={(swap.receiveAmountSat as number) * 1000}
  84                      />
  85                    </p>
  86                    <FormattedFiatAmount
  87                      amountSat={swap.receiveAmountSat as number}
  88                    />
  89                  </div>
  90                  <div className="flex justify-center gap-4 flex-wrap">
  91                    <Button onClick={copyTxId} variant="outline">
  92                      <CopyIcon />
  93                      Copy Transaction ID
  94                    </Button>
  95                  </div>
  96                </>
  97              ) : (
  98                <>
  99                  {swapStatus === "PENDING" ? (
 100                    <LottieLoading />
 101                  ) : (
 102                    <CircleXIcon className="w-60 h-60" />
 103                  )}
 104                  <div className="flex flex-col gap-2 items-center">
 105                    <p className="text-xl font-bold slashed-zero text-center">
 106                      <FormattedBitcoinAmount
 107                        amountMsat={swap.sendAmountSat * 1000}
 108                      />
 109                    </p>
 110                    <div className="flex items-center">
 111                      <span className="text-sm text-muted-foreground">~</span>
 112                      <FormattedFiatAmount amountSat={swap.sendAmountSat} />
 113                    </div>
 114                  </div>
 115                </>
 116              )}
 117              <div className="flex flex-col justify-start gap-2 w-full mt-2">
 118                {swapStatus !== "FAILED" ? (
 119                  swap.lockupTxId ? (
 120                    <>
 121                      {swapStatus === "SUCCESS" && (
 122                        <div className="flex items-center text-muted-foreground text-sm">
 123                          <CircleCheckIcon className="w-5 h-5 mr-2 text-green-600 dark:text-emerald-500" />
 124                          <div className="flex items-center gap-2">
 125                            <p>Confirmed onchain</p>
 126                            <ExternalLink
 127                              to={`${info?.mempoolUrl}/tx/${swap.claimTxId}`}
 128                              className="flex items-center underline text-foreground"
 129                            >
 130                              View
 131                            </ExternalLink>
 132                          </div>
 133                        </div>
 134                      )}
 135                      {swapStatus === "PENDING" && (
 136                        <div className="flex items-center text-muted-foreground text-sm">
 137                          <Loading className="w-5 h-5 mr-2" />
 138                          <div className="flex items-center gap-2">
 139                            <p>
 140                              {swap.claimTxId
 141                                ? "Waiting for on-chain confirmation"
 142                                : "Broadcasting claim transaction"}
 143                              ...
 144                            </p>
 145                            <ExternalLink
 146                              to={`${info?.mempoolUrl}/tx/${swap.claimTxId || swap.lockupTxId}`}
 147                              className="flex items-center underline text-foreground"
 148                            >
 149                              View
 150                            </ExternalLink>
 151                          </div>
 152                        </div>
 153                      )}
 154                      <Divider color="border-green-600 dark:border-emerald-500" />
 155                      <div className="flex items-center text-muted-foreground text-sm">
 156                        <CircleCheckIcon className="w-5 h-5 mr-2 text-green-600 dark:text-emerald-500" />
 157                        Lightning invoice paid
 158                      </div>
 159                      <Divider color="border-green-600 dark:border-emerald-500" />
 160                    </>
 161                  ) : (
 162                    <>
 163                      <div className="flex items-center text-muted-foreground text-sm">
 164                        <Loading className="w-5 h-5 mr-2" />
 165                        Paying lightning invoice...
 166                      </div>
 167                      <Divider color="border-green-600 dark:border-emerald-500" />
 168                    </>
 169                  )
 170                ) : (
 171                  <>
 172                    <div className="flex items-center text-muted-foreground text-sm">
 173                      <CircleAlertIcon className="w-5 h-5 mr-2 text-red-500" />
 174                      Swap failed
 175                    </div>
 176                    <Divider color="border-red-500" />
 177                    {swap.lockupTxId ? (
 178                      <>
 179                        <div className="flex items-center text-muted-foreground text-sm">
 180                          <CircleAlertIcon className="w-5 h-5 mr-2 text-red-500" />
 181                          Failed to claim swap in time
 182                        </div>
 183                        <Divider color="border-red-500" />
 184                      </>
 185                    ) : (
 186                      <>
 187                        <div className="flex items-center text-muted-foreground text-sm">
 188                          <CircleAlertIcon className="w-5 h-5 mr-2 text-red-500" />
 189                          Failed to pay swap invoice
 190                        </div>
 191                        <Divider color="border-red-500" />
 192                      </>
 193                    )}
 194                  </>
 195                )}
 196                <div className="flex items-center text-muted-foreground text-sm">
 197                  <CircleCheckIcon className="w-5 h-5 mr-2 text-green-600 dark:text-emerald-500" />
 198                  Swap initiated
 199                </div>
 200              </div>
 201            </CardContent>
 202          </Card>
 203        </div>
 204      </div>
 205    );
 206  }
 207  
 208  const Divider = ({ color }: { color: string }) => (
 209    <div className={`ml-2.25 py-1 border-l ${color}`}></div>
 210  );
 211