Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: alby pro subscriptions #1153

Merged
merged 21 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions frontend/src/components/UpgradeCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Button } from "src/components/ui/button";
import { UpgradeDialog } from "src/components/UpgradeDialog";

interface Props {
title: string;
description: string;
}

const UpgradeCard: React.FC<Props> = ({
title: message,
description: subMessage,
}) => {
return (
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm p-8">
<div className="flex flex-col items-center gap-1 text-center max-w-sm">
<span className="text-5xl">✨</span>
<h3 className="mt-4 text-lg font-semibold">{message}</h3>
<p className="text-sm text-muted-foreground mb-5">{subMessage}</p>
<UpgradeDialog>
<Button variant="premium">Upgrade</Button>
</UpgradeDialog>
</div>
</div>
);
};

export default UpgradeCard;
122 changes: 122 additions & 0 deletions frontend/src/components/UpgradeDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { LifeBuoy, Mail, RefreshCw, Users, Zap } from "lucide-react";
import { ReactNode } from "react";
import { Button } from "src/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "src/components/ui/dialog";
import { ALBY_PRO_PLAN } from "src/constants";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { openLink } from "src/utils/openLink";

interface UpgradeDialogProps {
children: ReactNode;
}

export const UpgradeDialog = ({ children }: UpgradeDialogProps) => {
const { data: albyMe } = useAlbyMe();

const handleUpgrade = () => {
openLink(
`https://www.getalby.com/subscription/confirm?plan=${ALBY_PRO_PLAN}`
);
};

if (albyMe?.subscription.buzz) {
return null;
}

return (
<Dialog>
<DialogTrigger>{children}</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>✨ Unlock Alby Pro</DialogTitle>
<DialogDescription>
Take your Alby Hub experience to the next level
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-6 my-4">
<div className="space-y-5">
<h3 className="font-medium text-lg">Pro Features Include:</h3>
<ul className="space-y-3">
<li className="flex items-center">
<Users className="w-5 h-5 mr-3 text-primary" />
<div>
<span className="font-medium">Unlimited Sub-wallets</span>
<p className="text-sm text-muted-foreground">
Share with friends, family, coworkers
</p>
</div>
</li>
<li className="flex items-center">
<RefreshCw className="w-5 h-5 mr-3 text-primary" />
<div>
<span className="font-medium">Encrypted Remote Backups</span>
<p className="text-sm text-muted-foreground">
Secure LDK wallet backups in the cloud
</p>
</div>
</li>
<li className="flex items-center">
<LifeBuoy className="w-5 h-5 mr-3 text-primary" />
<div>
<span className="font-medium">Priority Support</span>
<p className="text-sm text-muted-foreground">
Get help faster when you need it
</p>
</div>
</li>
<li className="flex items-center">
<Zap className="w-5 h-5 mr-3 text-primary" />
<div>
<span className="font-medium">Custom Lightning Address</span>
<p className="text-sm text-muted-foreground">
Create your personalized address
</p>
</div>
</li>
<li className="flex items-center">
<Mail className="w-5 h-5 mr-3 text-primary" />
<div>
<span className="font-medium">Email Notifications</span>
<p className="text-sm text-muted-foreground">
Stay updated on important activity
</p>
</div>
</li>
{/*
<li className="flex items-center">
<Server className="w-5 h-5 mr-3 text-primary" />
<div>
<span className="font-medium">Node Monitoring</span>
<p className="text-sm text-muted-foreground">
Keep your node running smoothly
</p>
</div>
</li>
*/}
</ul>
</div>
</div>
<div className="flex-col gap-1">
<Button
variant="premium"
size="lg"
className="w-full"
onClick={handleUpgrade}
>
Upgrade Now
</Button>
<p className="text-xs text-center text-muted-foreground mt-2">
30 day money back guarantee • Cancel anytime
</p>
</div>
</DialogContent>
</Dialog>
);
};
6 changes: 3 additions & 3 deletions frontend/src/components/layouts/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,18 @@ export default function AppLayout() {
<MenuItem
to="/"
onClick={(e) => {
openLink("https://getalby.com/help");
openLink("https://support.getalby.com");
e.preventDefault();
}}
>
<LifeBuoy className="h-4 w-4" />
Live Support
Support
</MenuItem>
<MenuItem
to="/"
onClick={(e) => {
openLink(
"https://guides.getalby.com/user-guide/v/alby-account-and-browser-extension/alby-hub"
"https://guides.getalby.com/user-guide/alby-account-and-browser-extension/alby-hub"
);
e.preventDefault();
}}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const buttonVariants = cva(
link: "text-foreground hover:text-accent-foreground underline underline-offset-4 hover:no-underline",
positive:
"bg-positive text-positive-foreground shadow-sm hover:bg-positive/90",
premium:
"text-black shadow-md shadow-amber-500/20 bg-gradient-to-r from-amber-500 to-amber-300 hover:from-amber-400 hover:to-amber-200 transition-all duration-200 hover:shadow-lg hover:shadow-amber-500/30",
},
size: {
default: "h-9 px-4 py-2",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export const LIST_TRANSACTIONS_LIMIT = 20;

export const SUPPORT_ALBY_CONNECTION_NAME = `ZapPlanner - Alby Hub`;
export const SUPPORT_ALBY_LIGHTNING_ADDRESS = "[email protected]";

export const ALBY_PRO_PLAN = `pro_202411_usd_year`;
5 changes: 5 additions & 0 deletions frontend/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Start from "src/screens/Start";
import Unlock from "src/screens/Unlock";
import { Welcome } from "src/screens/Welcome";
import AlbyAuthRedirect from "src/screens/alby/AlbyAuthRedirect";
import Support from "src/screens/alby/Support";
import SupportAlby from "src/screens/alby/SupportAlby";
import AppCreated from "src/screens/apps/AppCreated";
import AppList from "src/screens/apps/AppList";
Expand Down Expand Up @@ -367,6 +368,10 @@ const routes = [
path: "support-alby",
element: <SupportAlby />,
},
{
path: "support",
element: <Support />,
},
],
},
{
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { LightningMessageboardWidget } from "src/components/home/widgets/Lightni
import { NodeStatusWidget } from "src/components/home/widgets/NodeStatusWidget";
import { OnchainFeesWidget } from "src/components/home/widgets/OnchainFeesWidget";
import { WhatsNewWidget } from "src/components/home/widgets/WhatsNewWidget";
import { UpgradeDialog } from "src/components/UpgradeDialog";

function getGreeting(name: string | undefined) {
const hours = new Date().getHours();
Expand Down Expand Up @@ -59,7 +60,15 @@ function Home() {

return (
<>
<AppHeader title={getGreeting(albyMe?.name)} description="" />
<AppHeader
title={getGreeting(albyMe?.name)}
description=""
contentRight={
<UpgradeDialog>
<Button variant="premium">Upgrade</Button>
</UpgradeDialog>
}
/>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5 items-start justify-start">
{/* LEFT */}
<div className="grid gap-5">
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/screens/alby/Support.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import AppHeader from "src/components/AppHeader";
import { Button } from "src/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { UpgradeDialog } from "src/components/UpgradeDialog";

function Support() {
return (
<>
<AppHeader
title="Support"
description=""
contentRight={
<UpgradeDialog>
<Button variant="premium">Upgrade</Button>
</UpgradeDialog>
}
/>
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle>Where to find help</CardTitle>
</CardHeader>
<CardContent>TBD</CardContent>
</Card>
</div>
</>
);
}

export default Support;
65 changes: 41 additions & 24 deletions frontend/src/screens/internal-apps/UncleJim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { Label } from "src/components/ui/label";
import { LoadingButton } from "src/components/ui/loading-button";
import { Textarea } from "src/components/ui/textarea";
import { useToast } from "src/components/ui/use-toast";
import UpgradeCard from "src/components/UpgradeCard";
import { UpgradeDialog } from "src/components/UpgradeDialog";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useApp } from "src/hooks/useApp";
import { useApps } from "src/hooks/useApps";
import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo";
Expand All @@ -35,6 +38,7 @@ export function UncleJim() {
const { data: nodeConnectionInfo } = useNodeConnectionInfo();
const { toast } = useToast();
const [isLoading, setLoading] = React.useState(false);
const { data: albyMe } = useAlbyMe();

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand Down Expand Up @@ -79,42 +83,55 @@ export function UncleJim() {
(app) => app.metadata?.app_store_app_id === "uncle-jim"
);

const showForm =
albyMe?.subscription.buzz || (onboardedApps && onboardedApps?.length < 3);

return (
<div className="grid gap-5">
<AppHeader
title="Friends & Family"
description="Create sub-wallets for your friends and family powered by your Hub"
contentRight={
<UpgradeDialog>
<Button variant="premium">Upgrade</Button>
</UpgradeDialog>
}
/>
{!connectionSecret && (
<>
<form
onSubmit={handleSubmit}
className="flex flex-col items-start gap-5 max-w-lg"
>
<div className="w-full grid gap-1.5">
<Label htmlFor="name">Name of friend or family member</Label>
<Input
autoFocus
type="text"
name="name"
value={name}
id="name"
onChange={(e) => setName(e.target.value)}
required
autoComplete="off"
{showForm ? (
<form
onSubmit={handleSubmit}
className="flex flex-col items-start gap-5 max-w-lg"
>
<div className="w-full grid gap-1.5">
<Label htmlFor="name">Name of friend or family member</Label>
<Input
autoFocus
type="text"
name="name"
value={name}
id="name"
onChange={(e) => setName(e.target.value)}
required
autoComplete="off"
/>
</div>
<LoadingButton loading={isLoading} type="submit">
Create Sub-wallet
</LoadingButton>
</form>
) : (
<>
<UpgradeCard
title="Need more Sub-wallets?"
description="Unlock unlimited sub-wallets by upgrading to Alby Pro"
/>
</div>
<LoadingButton loading={isLoading} type="submit">
Create Sub-wallet
</LoadingButton>
</form>
</>
)}

{!!onboardedApps?.length && (
<>
<p className="text-sm text-muted-foreground">
Great job! You've onboarded {onboardedApps.length} friends and
family members so far.
</p>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 items-stretch app-list">
{onboardedApps.map((app, index) => (
<AppCard key={index} app={app} />
Expand Down
Loading