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