PaymentSuccess.tsx raw
1 import type { Invoice } from "@getalby/lightning-tools/bolt11";
2 import {
3 ArrowLeftIcon,
4 CopyIcon,
5 ExternalLinkIcon,
6 HandCoinsIcon,
7 } from "lucide-react";
8 import { useEffect } from "react";
9 import { useLocation, useNavigate } from "react-router";
10 import ExternalLink from "src/components/ExternalLink";
11 import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
12 import FormattedFiatAmount from "src/components/FormattedFiatAmount";
13 import { Button } from "src/components/ui/button";
14 import {
15 Card,
16 CardContent,
17 CardFooter,
18 CardHeader,
19 CardTitle,
20 } from "src/components/ui/card";
21 import { copyToClipboard } from "src/lib/clipboard";
22
23 import AppHeader from "src/components/AppHeader";
24 import LottieSuccess from "src/components/LottieSuccess";
25 import { LinkButton } from "src/components/ui/custom/link-button";
26
27 export default function PaymentSuccess() {
28 const { state } = useLocation();
29 const navigate = useNavigate();
30
31 useEffect(() => {
32 if (!state?.preimage) {
33 navigate("/wallet/send");
34 }
35 }, [state, navigate]);
36
37 if (!state?.preimage || !state?.invoice) {
38 return null;
39 }
40
41 const to = state?.to as string;
42 const pageTitle = state?.pageTitle as string;
43 const invoice = state?.invoice as Invoice;
44 const amountSat = state?.amountSat as number;
45
46 const copy = () => {
47 copyToClipboard(state.preimage as string);
48 };
49
50 return (
51 <div className="grid gap-4">
52 <AppHeader pageTitle={pageTitle || "Send"} title={pageTitle || "Send"} />
53 <div className="w-full md:max-w-lg">
54 <Card className="w-full">
55 <CardHeader>
56 <CardTitle className="text-center">Payment Successful</CardTitle>
57 </CardHeader>
58 <CardContent className="flex flex-col items-center gap-6">
59 <LottieSuccess />
60 <div className="flex flex-col gap-1 items-center">
61 <p className="text-2xl font-medium slashed-zero">
62 <FormattedBitcoinAmount
63 amountMsat={(invoice.satoshi || amountSat) * 1000}
64 />
65 </p>
66 <FormattedFiatAmount
67 amountSat={invoice.satoshi || amountSat}
68 className="text-xl"
69 />
70 </div>
71 {(to || invoice.description || invoice.successAction) && (
72 <div className="flex flex-col items-center w-full gap-4">
73 {to && (
74 <p className="text-muted-foreground">
75 to <span className="text-foreground">{to}</span>
76 </p>
77 )}
78 {invoice.description && (
79 <p className="text-muted-foreground">{invoice.description}</p>
80 )}
81 <SuccessAction action={invoice.successAction} />
82 </div>
83 )}
84 </CardContent>
85 <CardFooter className="flex flex-col gap-3 pt-2">
86 <Button onClick={copy} variant="outline" className="w-full">
87 <CopyIcon className="size-4" />
88 Copy Preimage
89 </Button>
90 <LinkButton to="/wallet/send" variant="outline" className="w-full">
91 <HandCoinsIcon className="size-4" />
92 Make Another Payment
93 </LinkButton>
94 <LinkButton to="/wallet" variant="link" className="w-full">
95 <ArrowLeftIcon className="size-4" />
96 Back to Wallet
97 </LinkButton>
98 </CardFooter>
99 </Card>
100 </div>
101 </div>
102 );
103 }
104
105 function SuccessAction({ action }: { action: Invoice["successAction"] }) {
106 if (!action) {
107 return null;
108 }
109
110 switch (action.tag) {
111 case "message":
112 return (
113 <div className="w-full p-4 border rounded-lg">
114 <div className="font-medium">Message</div>
115 <p className="text-sm">{action.message}</p>
116 </div>
117 );
118 case "url":
119 return (
120 <>
121 <div className="w-full p-4 border rounded-lg">
122 <div className="font-medium">Description</div>
123 <p className="text-sm">{action.description}</p>
124 </div>
125 <div className="w-full p-4 border rounded-lg">
126 <div className="font-medium">URL</div>
127 <ExternalLink
128 to={action.url}
129 className="underline flex items-center"
130 >
131 <p className="text-sm">{action.url}</p>
132 <ExternalLinkIcon className="w-4 h-4 ml-2" />
133 </ExternalLink>
134 </div>
135 </>
136 );
137 }
138 }
139