feat: alby cloud enable vss banner (#1450)

* feat: alby cloud enable vss banner

* fix: reload info after initiating storage migration
This commit is contained in:
Roland 2025-07-03 15:14:14 +07:00 committed by GitHub
parent 064de377a0
commit 2342b742a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 42 deletions

View file

@ -0,0 +1,67 @@
import { XIcon } from "lucide-react";
import { Link } from "react-router-dom";
import ExternalLink from "src/components/ExternalLink";
import { useAlbyInfo } from "src/hooks/useAlbyInfo";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useInfo } from "src/hooks/useInfo";
export function Banner({ onDismiss }: { onDismiss: () => void }) {
const { data: info } = useInfo();
const { data: albyInfo } = useAlbyInfo();
const { data: albyMe } = useAlbyMe();
if (!info || !albyInfo) {
return null;
}
// vss migration (alby cloud only)
// TODO: remove after 2026-01-01
const vssMigrationRequired =
info.oauthRedirect &&
!!albyMe?.subscription.plan_code.includes("buzz") &&
info.vssSupported &&
!info.ldkVssEnabled;
if (vssMigrationRequired) {
return (
<div className="fixed w-full bg-foreground text-background z-20 py-2 text-sm flex items-center justify-center">
<Link
to={`/settings/backup?dynamic=true`}
className="w-full px-12 md:px-24"
>
<p className="line-clamp-2 md:block whitespace-normal md:whitespace-nowrap overflow-hidden text-ellipsis text-center">
<span className="font-semibold mr-2">
Enable Dynamic Channels Backup
</span>
<span></span>
<span className="ml-2">Upgrade your channel backups now</span>
</p>
</Link>
<XIcon
className="absolute right-4 cursor-pointer w-4 text-background"
onClick={onDismiss}
/>
</div>
);
}
return (
<div className="fixed w-full bg-foreground text-background z-20 py-2 text-sm flex items-center justify-center">
<ExternalLink
to={`https://getalby.com/update/hub?version=${info?.version}`}
className="w-full px-12 md:px-24"
>
<p className="line-clamp-2 md:block whitespace-normal md:whitespace-nowrap overflow-hidden text-ellipsis text-center">
<span className="font-semibold mr-2">Update Available</span>
<span></span>
<span className="ml-2">{albyInfo.hub.latestReleaseNotes}</span>
</p>
</ExternalLink>
<XIcon
className="absolute right-4 cursor-pointer w-4 text-background"
onClick={onDismiss}
/>
</div>
);
}

View file

@ -1,33 +0,0 @@
import { XIcon } from "lucide-react";
import ExternalLink from "src/components/ExternalLink";
import { useAlbyInfo } from "src/hooks/useAlbyInfo";
import { useInfo } from "src/hooks/useInfo";
export function UpdateBanner({ onDismiss }: { onDismiss: () => void }) {
const { data: info } = useInfo();
const { data: albyInfo } = useAlbyInfo();
if (!info || !albyInfo) {
return null;
}
return (
<div className="fixed w-full bg-foreground text-background z-20 py-2 text-sm flex items-center justify-center">
<ExternalLink
to={`https://getalby.com/update/hub?version=${info?.version}`}
className="w-full px-12 md:px-24"
>
<p className="line-clamp-2 md:block whitespace-normal md:whitespace-nowrap overflow-hidden text-ellipsis text-center">
<span className="font-semibold mr-2">Update Available</span>
<span></span>
<span className="ml-2">{albyInfo.hub.latestReleaseNotes}</span>
</p>
</ExternalLink>
<XIcon
className="absolute right-4 cursor-pointer w-4 text-background"
onClick={onDismiss}
/>
</div>
);
}

View file

@ -1,8 +1,8 @@
import { Outlet } from "react-router-dom";
import { AppSidebar } from "src/components/AppSidebar";
import { Banner } from "src/components/Banner";
import { SidebarInset, SidebarProvider } from "src/components/ui/sidebar";
import { UpdateBanner } from "src/components/UpdateBanner";
import { useBanner } from "src/hooks/useBanner";
import { useInfo } from "src/hooks/useInfo";
import { useNotifyReceivedPayments } from "src/hooks/useNotifyReceivedPayments";
@ -31,7 +31,7 @@ export default function AppLayout() {
)}
>
<SidebarProvider className="flex flex-col">
{showBanner && <UpdateBanner onDismiss={dismissBanner} />}
{showBanner && <Banner onDismiss={dismissBanner} />}
<div className="flex flex-1">
<AppSidebar />
<SidebarInset>

View file

@ -1,11 +1,13 @@
import { compare } from "compare-versions";
import React from "react";
import { useAlbyInfo } from "src/hooks/useAlbyInfo";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useInfo } from "src/hooks/useInfo";
export function useBanner() {
const { data: info } = useInfo();
const { data: albyInfo } = useAlbyInfo();
const { data: albyMe } = useAlbyMe();
const [showBanner, setShowBanner] = React.useState(false);
React.useEffect(() => {
@ -14,13 +16,21 @@ export function useBanner() {
return;
}
// vss migration (alby cloud only)
// TODO: remove after 2026-01-01
const vssMigrationRequired =
info.oauthRedirect &&
!!albyMe?.subscription.plan_code.includes("buzz") &&
info.vssSupported &&
!info.ldkVssEnabled;
const upToDate =
Boolean(info.version) &&
info.version.startsWith("v") &&
compare(info.version.substring(1), albyInfo.hub.latestVersion, ">=");
setShowBanner(!upToDate);
}, [info, albyInfo]);
setShowBanner(!upToDate || vssMigrationRequired);
}, [info, albyInfo, albyMe?.subscription.plan_code]);
const dismissBanner = () => {
setShowBanner(false);

View file

@ -1,10 +1,12 @@
import React from "react";
import { useToast } from "src/components/ui/use-toast";
import { useInfo } from "src/hooks/useInfo";
import { request } from "src/utils/request";
export function useMigrateLDKStorage() {
const [isMigratingStorage, setMigratingStorage] = React.useState(false);
const { toast } = useToast();
const { mutate: reloadInfo } = useInfo();
const migrateLDKStorage = async (to: "VSS") => {
try {
@ -19,6 +21,7 @@ export function useMigrateLDKStorage() {
to,
}),
});
await reloadInfo();
toast({
title: "Please unlock your hub",
});

View file

@ -6,7 +6,7 @@ import {
} from "lucide-react";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router-dom";
import ExternalLink from "src/components/ExternalLink";
import Loading from "src/components/Loading";
import MnemonicDialog from "src/components/mnemonic/MnemonicDialog";
@ -201,7 +201,7 @@ export default function Backup() {
{me?.subscription.plan_code && info.ldkVssEnabled ? (
<Badge variant={"positive"}>Active</Badge>
) : (
<Badge>Alby Cloud</Badge>
<Badge className="shrink-0">Alby Cloud</Badge>
)}
</div>
<p className="text-sm text-muted-foreground mb-4">
@ -284,10 +284,19 @@ type Props = {
};
function DynamicChannelsBackupDialog({ info }: Props) {
const [open, setOpen] = useState(false);
const [searchParams] = useSearchParams();
React.useEffect(() => {
if (searchParams.get("dynamic") === "true") {
setOpen(true);
}
}, [searchParams]);
const { isMigratingStorage, migrateLDKStorage } = useMigrateLDKStorage();
return (
<AlertDialog>
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger asChild>
<LoadingButton
variant="secondary"
@ -304,8 +313,16 @@ function DynamicChannelsBackupDialog({ info }: Props) {
<AlertDialogDescription>
<div>
<p>
As part of enabling VSS your hub will be shut down, and you will
need to enter your unlock password to start it again.
By enabling dynamic channel backups, your channels state is
dynamically updated and stored end-to-end encrypted by Alby's
Versioned Storage Service. This allows you to recover your
spending balance with your recovery phrase alone, without having
to close your channels.
</p>
<p className="mt-2">
As part of enabling dynamic channels backup your hub will be
shut down, and you will need to enter your unlock password to
start it again.
</p>
<p className="mt-2">
Please ensure you have no pending payments or channel closures