mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: alby pro subscriptions (#1153)
* feat: draft * fix: replace alert dialog * fix: ordering of features * feat: new components & helpers * fix: make yearly plan the default * fix: cleanup * fix: update guides link * fix: upgrade * fix: add to home * fix: upgrade prompts * fix: remove support page * fix: external link button * fix: minify * fix: conditionally show backup feature * fix: onboarding & account linking * fix: replace alby pro icon * chore: use plan code instead of buzz, fix incorrect components briefly rendering * fix: badge * fix: change badge type --------- Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
parent
7c93bff201
commit
d3a3b615cb
14 changed files with 428 additions and 177 deletions
|
|
@ -76,8 +76,7 @@ type AlbyMeHub struct {
|
|||
}
|
||||
|
||||
type AlbyMeSubscription struct {
|
||||
// PlanCode string `json:"plan_code"`
|
||||
Buzz bool `json:"buzz"`
|
||||
PlanCode string `json:"plan_code"`
|
||||
}
|
||||
|
||||
type AlbyMe struct {
|
||||
|
|
|
|||
28
frontend/src/components/UpgradeCard.tsx
Normal file
28
frontend/src/components/UpgradeCard.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { SparklesIcon } from "lucide-react";
|
||||
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">
|
||||
<SparklesIcon className="w-12 h-12" />
|
||||
<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;
|
||||
124
frontend/src/components/UpgradeDialog.tsx
Normal file
124
frontend/src/components/UpgradeDialog.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
LifeBuoy,
|
||||
Mail,
|
||||
RefreshCw,
|
||||
SparklesIcon,
|
||||
Users,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { ReactNode } from "react";
|
||||
import { ExternalLinkButton } from "src/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "src/components/ui/dialog";
|
||||
import { useAlbyMe } from "src/hooks/useAlbyMe";
|
||||
import { useInfo } from "src/hooks/useInfo";
|
||||
|
||||
interface UpgradeDialogProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const UpgradeDialog = ({ children }: UpgradeDialogProps) => {
|
||||
const { data: albyMe } = useAlbyMe();
|
||||
const { data: info } = useInfo();
|
||||
|
||||
if (!info || (info.albyAccountConnected && !albyMe)) {
|
||||
// still loading - make sure button does not incorrectly show
|
||||
return;
|
||||
}
|
||||
|
||||
if (albyMe?.subscription.plan_code) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger>{children}</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex flex-row gap-2 items-center">
|
||||
<SparklesIcon className="w-6 h-6" />
|
||||
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" />
|
||||
<div>
|
||||
<span className="font-medium">Unlimited Sub-wallets</span>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Share with friends, family, coworkers
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
{info?.backendType === "LDK" && (
|
||||
<li className="flex items-center">
|
||||
<RefreshCw className="w-5 h-5 mr-3" />
|
||||
<div>
|
||||
<span className="font-medium">
|
||||
Encrypted Remote Backups
|
||||
</span>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Secure wallet backups in the cloud
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
<li className="flex items-center">
|
||||
<LifeBuoy className="w-5 h-5 mr-3" />
|
||||
<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" />
|
||||
<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" />
|
||||
<div>
|
||||
<span className="font-medium">Email Notifications</span>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Stay updated on important activity
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-col gap-1">
|
||||
<ExternalLinkButton
|
||||
variant="premium"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
to="https://www.getalby.com/subscription/new"
|
||||
>
|
||||
Upgrade Now
|
||||
</ExternalLinkButton>
|
||||
<p className="text-xs text-center text-muted-foreground mt-2">
|
||||
30 day money back guarantee • Cancel anytime
|
||||
</p>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { compare } from "compare-versions";
|
||||
import {
|
||||
CircleHelp,
|
||||
Cloud,
|
||||
EllipsisVertical,
|
||||
Home,
|
||||
LayoutGrid,
|
||||
|
|
@ -12,6 +11,7 @@ import {
|
|||
Settings,
|
||||
ShieldAlertIcon,
|
||||
ShieldCheckIcon,
|
||||
SparklesIcon,
|
||||
User2,
|
||||
Wallet,
|
||||
} from "lucide-react";
|
||||
|
|
@ -46,6 +46,7 @@ import {
|
|||
} from "src/components/ui/tooltip";
|
||||
import { useAlbyMe } from "src/hooks/useAlbyMe";
|
||||
|
||||
import { UpgradeDialog } from "src/components/UpgradeDialog";
|
||||
import { useAlbyInfo } from "src/hooks/useAlbyInfo";
|
||||
import { useHealthCheck } from "src/hooks/useHealthCheck";
|
||||
import { useInfo } from "src/hooks/useInfo";
|
||||
|
|
@ -171,6 +172,7 @@ export default function AppLayout() {
|
|||
<MenuItem
|
||||
to="/"
|
||||
onClick={(e) => {
|
||||
openLink("https://support.getalby.com");
|
||||
openLink("https://support.getalby.com");
|
||||
e.preventDefault();
|
||||
}}
|
||||
|
|
@ -178,18 +180,12 @@ export default function AppLayout() {
|
|||
<CircleHelp className="h-4 w-4" />
|
||||
Help
|
||||
</MenuItem>
|
||||
{!albyMe?.hub.name && info?.albyAccountConnected && (
|
||||
<MenuItem
|
||||
to="/"
|
||||
onClick={(e) => {
|
||||
openLink("https://getalby.com/subscription/new");
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Cloud className="h-4 w-4" />
|
||||
Alby Cloud
|
||||
</MenuItem>
|
||||
)}
|
||||
<UpgradeDialog>
|
||||
<div className="flex items-center gap-3 rounded-lg px-3 py-2 text-muted-foreground transition-all hover:text-accent-foreground">
|
||||
<SparklesIcon className="h-4 w-4" />
|
||||
Upgrade
|
||||
</div>
|
||||
</UpgradeDialog>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -55,9 +55,12 @@ export const useOnboardingData = (): UseOnboardingDataResponse => {
|
|||
const hasCustomApp =
|
||||
apps && apps.find((x) => x.name !== "getalby.com") !== undefined;
|
||||
const hasTransaction = transactions.totalCount > 0;
|
||||
const hasSetupSupportPayment =
|
||||
apps &&
|
||||
apps.find((x) => x.name === SUPPORT_ALBY_CONNECTION_NAME) !== undefined;
|
||||
const hasSetupSupportPayment = !!(
|
||||
(apps &&
|
||||
apps.find((x) => x.name === SUPPORT_ALBY_CONNECTION_NAME) !==
|
||||
undefined) ||
|
||||
albyMe?.subscription.plan_code
|
||||
);
|
||||
|
||||
const checklistItems: Omit<ChecklistItem, "disabled">[] = [
|
||||
...(hasChannelManagement
|
||||
|
|
@ -110,9 +113,9 @@ export const useOnboardingData = (): UseOnboardingDataResponse => {
|
|||
...(!info.oauthRedirect
|
||||
? [
|
||||
{
|
||||
title: "Support Alby Hub",
|
||||
title: "Support Alby",
|
||||
description:
|
||||
"Setup a recurring payment to support the development of Alby Hub",
|
||||
"Set up a monthly contribution to help us build the future of digital payments.",
|
||||
checked: hasSetupSupportPayment,
|
||||
to: "/support-alby",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import {
|
|||
Headphones,
|
||||
LifeBuoy,
|
||||
Mail,
|
||||
SparklesIcon,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import Container from "src/components/Container";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
|
||||
import { Badge } from "src/components/ui/badge";
|
||||
import { LinkButton } from "src/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -26,40 +29,52 @@ export function ConnectAlbyAccount({ connectUrl }: ConnectAlbyAccountProps) {
|
|||
<Container>
|
||||
<TwoColumnLayoutHeader
|
||||
title="Connect Your Alby Account"
|
||||
description="Alby Account brings several benefits to your Alby Hub"
|
||||
description="Your Alby Account brings several benefits to your Alby Hub"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 w-full gap-2 mt-5">
|
||||
<Card className="w-full">
|
||||
<Card className="w-full relative">
|
||||
<CardHeader className="flex flex-col justify-center items-center text-center p-4">
|
||||
<Zap className="w-6 h-6" />
|
||||
<CardTitle className="text-sm">Lightning Address</CardTitle>
|
||||
<CardTitle className="text-sm">
|
||||
Lightning Address
|
||||
<AlbyProIcon />
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Personalized lightning address to receive payments
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="w-full">
|
||||
<Card className="w-full relative">
|
||||
<CardHeader className="flex flex-col justify-center items-center text-center p-4">
|
||||
<Mail className="w-6 h-6" />
|
||||
<CardTitle className="text-sm">Email Notifications</CardTitle>
|
||||
<CardTitle className="text-sm">
|
||||
Email Notifications
|
||||
<AlbyProIcon />
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Instant notifications about incoming transactions and more
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="w-full">
|
||||
<Card className="w-full relative">
|
||||
<CardHeader className="flex flex-col justify-center items-center text-center p-4">
|
||||
<DatabaseBackup className="w-6 h-6" />
|
||||
<CardTitle className="text-sm">Encrypted Backups</CardTitle>
|
||||
<CardTitle className="text-sm">
|
||||
Encrypted Backups
|
||||
<AlbyProIcon />
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Ensures you can always recover funds from lightning channels
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card className="w-full">
|
||||
<Card className="w-full relative">
|
||||
<CardHeader className="flex flex-col justify-center items-center text-center p-4">
|
||||
<LifeBuoy className="w-6 h-6" />
|
||||
<CardTitle className="text-sm">Support</CardTitle>
|
||||
<CardTitle className="text-sm">
|
||||
Support
|
||||
<AlbyProIcon />
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Human support via live chat when you need a helping hand
|
||||
</CardDescription>
|
||||
|
|
@ -84,15 +99,9 @@ export function ConnectAlbyAccount({ connectUrl }: ConnectAlbyAccountProps) {
|
|||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center items-center text-center p-4 mt-4">
|
||||
<CardTitle className="text-sm">and there's more...</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Claim your Nostr address, discover apps, etc
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<div className="flex flex-col items-center justify-center gap-2 mt-10">
|
||||
<LinkButton to={connectUrl || "/alby/auth"} size="lg">
|
||||
Connect now
|
||||
Connect
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
size="sm"
|
||||
|
|
@ -103,7 +112,29 @@ export function ConnectAlbyAccount({ connectUrl }: ConnectAlbyAccountProps) {
|
|||
Maybe later
|
||||
</LinkButton>
|
||||
</div>
|
||||
<div className="text-muted-foreground flex flex-col items-center text-xs gap-2 mt-10">
|
||||
<Badge title="Alby Pro">
|
||||
<SparklesIcon className="w-3 h-3" />
|
||||
</Badge>
|
||||
<div>
|
||||
Unlock additional features with{" "}
|
||||
<ExternalLink
|
||||
to="https://getalby.com/pricing"
|
||||
className="underline"
|
||||
>
|
||||
Alby Pro
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
|
||||
function AlbyProIcon() {
|
||||
return (
|
||||
<div className="absolute right-2 top-2" title="Alby Pro">
|
||||
<SparklesIcon className="w-3 h-3" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
import { Code, PlusCircle, RefreshCw } from "lucide-react";
|
||||
import { Code, PlusCircle, RefreshCw, SparklesIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "src/components/ui/alert-dialog";
|
||||
import { Badge } from "src/components/ui/badge";
|
||||
import { Button, LinkButton } from "src/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "src/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "src/components/ui/dialog";
|
||||
import { Input } from "src/components/ui/input";
|
||||
import { Label } from "src/components/ui/label";
|
||||
import { LoadingButton } from "src/components/ui/loading-button";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import { UpgradeDialog } from "src/components/UpgradeDialog";
|
||||
import {
|
||||
SUPPORT_ALBY_CONNECTION_NAME,
|
||||
SUPPORT_ALBY_LIGHTNING_ADDRESS,
|
||||
|
|
@ -157,11 +160,11 @@ function SupportAlby() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full w-full max-w-screen-sm mx-auto flex flex-col justify-center">
|
||||
<div className="h-full w-full max-w-screen-md mx-auto flex flex-col justify-center">
|
||||
<div className="flex flex-col items-center justify-center gap-6">
|
||||
<section className="text-center">
|
||||
<h2 className="text-3xl font-semibold mb-4 max-sm:mt-8">
|
||||
✨ Your Support Matters
|
||||
Your Support Matters
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-balance">
|
||||
We are committed to elevating the Bitcoin ecosystem by offering
|
||||
|
|
@ -176,13 +179,13 @@ function SupportAlby() {
|
|||
<CardTitle>Why Your Contribution Is Important</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="flex flex-col gap-5">
|
||||
<ul className="flex flex-col gap-5 text-sm">
|
||||
<li className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<PlusCircle className="w-4 h-4 mr-2" />
|
||||
Unlock New Features
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
Your support empowers us to design and implement
|
||||
cutting-edge{" "}
|
||||
<ExternalLink
|
||||
|
|
@ -200,7 +203,7 @@ function SupportAlby() {
|
|||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
Ensure Continuous Improvement
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
With your contributions, we can provide{" "}
|
||||
<ExternalLink
|
||||
className="underline"
|
||||
|
|
@ -217,7 +220,7 @@ function SupportAlby() {
|
|||
<Code className="w-4 h-4 mr-2" />
|
||||
Support Open-Source Freedom
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
Your support helps us keep Alby Hub true to the principles
|
||||
of{" "}
|
||||
<ExternalLink
|
||||
|
|
@ -233,101 +236,131 @@ function SupportAlby() {
|
|||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button size="lg">Become a Supporter</Button>
|
||||
</AlertDialogTrigger>
|
||||
<LinkButton
|
||||
size="sm"
|
||||
variant="link"
|
||||
to="/"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
Maybe later
|
||||
</LinkButton>
|
||||
</div>
|
||||
<AlertDialogContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Become a Supporter</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
A new app connection will be established to facilitate
|
||||
monthly payments to Alby. You can cancel it anytime through
|
||||
the connections page.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<div className="flex flex-col gap-3 my-5">
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<Label htmlFor="amount" className="text-right mt-2">
|
||||
Amount <br></br>
|
||||
<span className="font-normal text-muted-foreground">
|
||||
(sats / month)
|
||||
</span>
|
||||
</Label>
|
||||
<div className="col-span-3">
|
||||
<Input
|
||||
id="amount"
|
||||
value={amount}
|
||||
required
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
/>
|
||||
<div className="grid grid-cols-3 gap-1 mt-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAmount("3000")}
|
||||
>
|
||||
🙏 3000
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAmount("6000")}
|
||||
>
|
||||
💪 6000
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAmount("10000")}
|
||||
>
|
||||
✨ 10000
|
||||
</Button>
|
||||
<div className="grid grid-cols-2 gap-3 w-full">
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<SparklesIcon className="h-4 w-4" />
|
||||
<div>Alby Pro</div>
|
||||
</div>
|
||||
<Badge variant="outline">ACCOUNT REQUIRED</Badge>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Upgrade your Alby Account to Alby Pro, support Alby and enjoy
|
||||
additional perks.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex justify-center">
|
||||
<UpgradeDialog>
|
||||
<Button variant="premium">Unlock Alby Pro</Button>
|
||||
</UpgradeDialog>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Become a Supporter</CardTitle>
|
||||
<CardDescription>
|
||||
Support Alby with recurring #value4value payments.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex justify-center">
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<DialogTrigger asChild>
|
||||
<Button>Become a Supporter</Button>
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
<DialogContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Become a Supporter</DialogTitle>
|
||||
<DialogDescription>
|
||||
A new app connection will be established to facilitate
|
||||
monthly payments to Alby. You can cancel it anytime
|
||||
through the connections page.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3 my-5">
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<Label htmlFor="amount" className="text-right mt-2">
|
||||
Amount <br></br>
|
||||
<span className="font-normal text-muted-foreground">
|
||||
(sats / month)
|
||||
</span>
|
||||
</Label>
|
||||
<div className="col-span-3">
|
||||
<Input
|
||||
id="amount"
|
||||
value={amount}
|
||||
required
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
/>
|
||||
<div className="grid grid-cols-3 gap-1 mt-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAmount("3000")}
|
||||
>
|
||||
🙏 3000
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAmount("6000")}
|
||||
>
|
||||
💪 6000
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setAmount("10000")}
|
||||
>
|
||||
✨ 10000
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="comment" className="text-right">
|
||||
Name{" "}
|
||||
<span className="font-normal text-muted-foreground">
|
||||
(optional)
|
||||
</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="sender-name"
|
||||
value={senderName}
|
||||
onChange={(e) => setSenderName(e.target.value)}
|
||||
placeholder={`Nickname, npub, @twitter, etc.`}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="comment" className="text-right">
|
||||
Name{" "}
|
||||
<span className="font-normal text-muted-foreground">
|
||||
(optional)
|
||||
</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="sender-name"
|
||||
value={senderName}
|
||||
onChange={(e) => setSenderName(e.target.value)}
|
||||
placeholder={`Nickname, npub, @twitter, etc.`}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
disabled={!!isSubmitting}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Complete Setup
|
||||
</LoadingButton>
|
||||
</AlertDialogFooter>
|
||||
</form>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<DialogFooter>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
disabled={!!isSubmitting}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Complete Setup
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<LinkButton
|
||||
size="sm"
|
||||
variant="link"
|
||||
to="/"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
Maybe later
|
||||
</LinkButton>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import AppHeader from "src/components/AppHeader";
|
|||
import AppCard from "src/components/connections/AppCard";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import { IsolatedAppTopupDialog } from "src/components/IsolatedAppTopupDialog";
|
||||
import Loading from "src/components/Loading";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
|
|
@ -17,8 +18,12 @@ 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 { useInfo } from "src/hooks/useInfo";
|
||||
import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo";
|
||||
import { copyToClipboard } from "src/lib/clipboard";
|
||||
import { createApp } from "src/requests/createApp";
|
||||
|
|
@ -35,6 +40,8 @@ export function UncleJim() {
|
|||
const { data: nodeConnectionInfo } = useNodeConnectionInfo();
|
||||
const { toast } = useToast();
|
||||
const [isLoading, setLoading] = React.useState(false);
|
||||
const { data: info } = useInfo();
|
||||
const { data: albyMe } = useAlbyMe();
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -79,42 +86,61 @@ export function UncleJim() {
|
|||
(app) => app.metadata?.app_store_app_id === "uncle-jim"
|
||||
);
|
||||
|
||||
const showForm =
|
||||
albyMe?.subscription.plan_code ||
|
||||
(onboardedApps && onboardedApps?.length < 3);
|
||||
|
||||
if (!info || (info.albyAccountConnected && !albyMe)) {
|
||||
// make sure to not render the incorrect component
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
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} />
|
||||
|
|
|
|||
|
|
@ -102,10 +102,10 @@ export function AlbyAccount() {
|
|||
)}
|
||||
</p>
|
||||
)}
|
||||
{!me.subscription.buzz && (
|
||||
{!me.subscription.plan_code && (
|
||||
<p>VSS is only available to Alby users with a paid subscription.</p>
|
||||
)}
|
||||
{!info.ldkVssEnabled && me.subscription.buzz && (
|
||||
{!info.ldkVssEnabled && me.subscription.plan_code && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export function SetupSecurity() {
|
|||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Access to your Alby Hub is protected by an unlock password you
|
||||
set.
|
||||
set. It cannot be recovered or reset.
|
||||
</span>
|
||||
</div>
|
||||
{store.nodeInfo.backendType === "LND" ||
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ export type AlbyMe = {
|
|||
name?: string;
|
||||
};
|
||||
subscription: {
|
||||
buzz: boolean;
|
||||
plan_code: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ func (svc *service) requestVssToken(ctx context.Context) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
// only activate VSS for Alby paid subscribers
|
||||
if me.Subscription.Buzz {
|
||||
if me.Subscription.PlanCode != "" {
|
||||
svc.cfg.SetUpdate("LdkVssEnabled", "true", "")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue