UpgradeDialog.tsx raw
1 import { CheckIcon, SparklesIcon, StarsIcon } from "lucide-react";
2 import { ReactNode, useState } from "react";
3 import { Badge } from "src/components/ui/badge";
4 import { ExternalLinkButton } from "src/components/ui/custom/external-link-button";
5 import {
6 Dialog,
7 DialogContent,
8 DialogDescription,
9 DialogHeader,
10 DialogTitle,
11 } from "src/components/ui/dialog";
12 import { DropdownMenuItem } from "src/components/ui/dropdown-menu";
13 import { Separator } from "src/components/ui/separator";
14 import { useAlbyMe } from "src/hooks/useAlbyMe";
15 import { useInfo } from "src/hooks/useInfo";
16
17 interface UpgradeDialogProps {
18 children?: ReactNode;
19 open?: boolean;
20 onOpenChange?: (open: boolean) => void;
21 }
22
23 interface ProDropdownMenuItemProps {
24 children: ReactNode;
25 onClick?: () => void;
26 }
27
28 export function ProDropdownMenuItem({
29 children,
30 onClick,
31 }: ProDropdownMenuItemProps) {
32 const { data: albyMe } = useAlbyMe();
33
34 if (!albyMe?.subscription.plan_code) {
35 return (
36 <UpgradeDialog>
37 <div className="cursor-pointer">
38 <DropdownMenuItem className="w-full pointer-events-none">
39 {children}
40 <Badge variant="outline">
41 <StarsIcon />
42 Pro
43 </Badge>
44 </DropdownMenuItem>
45 </div>
46 </UpgradeDialog>
47 );
48 }
49
50 return (
51 <DropdownMenuItem className="cursor-pointer" onClick={onClick}>
52 {children}
53 <Badge variant="outline">
54 <StarsIcon />
55 Pro
56 </Badge>
57 </DropdownMenuItem>
58 );
59 }
60
61 export const UpgradeDialog = ({
62 children,
63 open: controlledOpen,
64 onOpenChange: controlledOnOpenChange,
65 }: UpgradeDialogProps) => {
66 const [internalOpen, setInternalOpen] = useState(false);
67 const open = controlledOpen ?? internalOpen;
68 const setOpen = controlledOnOpenChange ?? setInternalOpen;
69
70 const { data: albyMe } = useAlbyMe();
71 const { data: info } = useInfo();
72
73 if (!info || (info.albyAccountConnected && !albyMe)) {
74 // still loading - make sure button does not incorrectly show
75 return;
76 }
77
78 if (albyMe?.subscription.plan_code) {
79 return null;
80 }
81
82 return (
83 <Dialog open={open} onOpenChange={setOpen}>
84 {children && (
85 <span onClick={() => setOpen(true)} className="cursor-pointer">
86 {children}
87 </span>
88 )}
89 <DialogContent className="md:max-w-md">
90 <DialogHeader className="sr-only">
91 <DialogTitle>Upgrade to Pro</DialogTitle>
92 <DialogDescription>
93 Upgrade to Alby Hub Pro to unlock advanced features and help fund
94 Alby's open-source work.
95 </DialogDescription>
96 </DialogHeader>
97
98 <div className="flex flex-col">
99 <h2 className="flex flex-wrap items-center gap-2 text-xl font-semibold tracking-tight">
100 Do more with your Hub
101 <Badge
102 variant="outline"
103 className="border-primary/40 bg-primary/10 text-foreground"
104 >
105 <SparklesIcon />
106 Pro
107 </Badge>
108 </h2>
109 <p className="mt-1.5 text-sm text-muted-foreground">
110 Unlock advanced features and help fund Alby's open-source work.
111 </p>
112
113 <div className="mt-5 flex items-baseline gap-1.5">
114 <span className="text-4xl font-semibold tracking-tight tabular-nums">
115 $3
116 </span>
117 <span className="text-sm text-muted-foreground">/ month</span>
118 </div>
119 <p className="mt-1 text-xs text-muted-foreground">
120 Billed $36 yearly · bitcoin or credit card · cancel anytime
121 </p>
122 </div>
123
124 <Separator />
125
126 <ul className="grid gap-2.5 text-sm">
127 {[
128 "Unlimited sub-wallets",
129 info.backendType === "LDK" && "Encrypted remote backups",
130 "Custom lightning address",
131 "Export transactions",
132 "Email notifications",
133 "Priority support",
134 ]
135 .filter((b): b is string => Boolean(b))
136 .map((benefit) => (
137 <li key={benefit} className="flex items-center gap-2.5">
138 <span className="flex size-4.5 shrink-0 items-center justify-center rounded-full bg-primary/15">
139 <CheckIcon
140 className="size-3 text-foreground/70"
141 strokeWidth={2.5}
142 />
143 </span>
144 <span>{benefit}</span>
145 </li>
146 ))}
147 </ul>
148
149 <div className="mt-2 space-y-2.5">
150 <ExternalLinkButton
151 size="lg"
152 className="w-full"
153 to="https://www.getalby.com/subscription/pro"
154 >
155 Upgrade to Pro
156 </ExternalLinkButton>
157 <p className="text-center text-xs text-muted-foreground">
158 30-day money-back guarantee
159 </p>
160 </div>
161 </DialogContent>
162 </Dialog>
163 );
164 };
165