diff --git a/frontend/src/components/MnemonicInputs.tsx b/frontend/src/components/MnemonicInputs.tsx index ed786cea..0d312bde 100644 --- a/frontend/src/components/MnemonicInputs.tsx +++ b/frontend/src/components/MnemonicInputs.tsx @@ -1,8 +1,8 @@ import { wordlist } from "@scure/bip39/wordlists/english"; -import { useState } from "react"; import { Card, CardContent, + CardDescription, CardHeader, CardTitle, } from "src/components/ui/card"; @@ -12,6 +12,7 @@ type MnemonicInputsProps = { mnemonic?: string; setMnemonic?(mnemonic: string): void; readOnly?: boolean; + description?: string; }; export default function MnemonicInputs({ @@ -19,11 +20,8 @@ export default function MnemonicInputs({ setMnemonic, readOnly, children, + description, }: React.PropsWithChildren) { - const [revealedIndex, setRevealedIndex] = useState( - undefined - ); - const words = mnemonic?.split(" ") || []; while (words.length < 12) { words.push(""); @@ -37,32 +35,29 @@ export default function MnemonicInputs({ <> - Recovery phrase to your wallet + + Wallet Recovery Phrase + + + {description} + -
+
{words.map((word, i) => { - const isRevealed = revealedIndex === i; const inputId = `mnemonic-word-${i}`; return (
- - {i + 1}. - + {i + 1}.
setRevealedIndex(i)} - onBlur={() => setRevealedIndex(undefined)} readOnly={readOnly} - className="w-32 text-center" + className="w-32 text-center bg-muted border-zinc-200 text-muted-foreground" list={readOnly ? undefined : "wordlist"} - value={isRevealed ? word : word.length ? "•••••" : ""} + value={word} onChange={(e) => { - if (revealedIndex !== i) { - return; - } words[i] = e.target.value; setMnemonic?.( words diff --git a/frontend/src/components/SidebarHint.tsx b/frontend/src/components/SidebarHint.tsx index 059ec548..8eaf2965 100644 --- a/frontend/src/components/SidebarHint.tsx +++ b/frontend/src/components/SidebarHint.tsx @@ -1,3 +1,6 @@ +import { UpdateIcon } from "@radix-ui/react-icons"; +import { IconProps } from "@radix-ui/react-icons/dist/types"; +import { compare } from "compare-versions"; import { ListTodo, LucideIcon, Zap } from "lucide-react"; import { ReactElement } from "react"; import { Link, useLocation } from "react-router-dom"; @@ -9,6 +12,8 @@ import { CardTitle, } from "src/components/ui/card"; import { Progress } from "src/components/ui/progress"; +import { useAlbyInfo } from "src/hooks/useAlbyInfo"; +import { useInfo } from "src/hooks/useInfo"; import { useOnboardingData } from "src/hooks/useOnboardingData"; import useChannelOrderStore from "src/state/ChannelOrderStore"; @@ -16,6 +21,8 @@ function SidebarHint() { const { isLoading, checklistItems } = useOnboardingData(); const { order } = useChannelOrderStore(); const location = useLocation(); + const { data: albyInfo } = useAlbyInfo(); + const { data: info } = useInfo(); // User has a channel order if ( @@ -67,14 +74,36 @@ function SidebarHint() { /> ); } -} + if (info && albyInfo) { + const upToDate = + info.version && + info.version.startsWith("v") && + compare(info.version.substring(1), albyInfo.hub.latestVersion, ">="); + + if (!upToDate) { + return ( + + ); + } + } +} type SidebarHintCardProps = { title: string; description: string | ReactElement; buttonText: string; buttonLink: string; - icon: LucideIcon; + icon: + | LucideIcon + | React.ForwardRefExoticComponent< + IconProps & React.RefAttributes + >; }; function SidebarHintCard({ title, diff --git a/frontend/src/components/layouts/SettingsLayout.tsx b/frontend/src/components/layouts/SettingsLayout.tsx index 4d69eb17..d024f54e 100644 --- a/frontend/src/components/layouts/SettingsLayout.tsx +++ b/frontend/src/components/layouts/SettingsLayout.tsx @@ -1,60 +1,14 @@ -import { Power } from "lucide-react"; -import React, { useState } from "react"; -import { NavLink, Outlet, useNavigate } from "react-router-dom"; +import React from "react"; +import { NavLink, Outlet } from "react-router-dom"; import AppHeader from "src/components/AppHeader"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "src/components/ui/alert-dialog"; import { buttonVariants } from "src/components/ui/button"; -import { LoadingButton } from "src/components/ui/loading-button"; -import { useToast } from "src/components/ui/use-toast"; import { useInfo } from "src/hooks/useInfo"; import { cn } from "src/lib/utils"; -import { request } from "src/utils/request"; export default function SettingsLayout() { - const { - data: info, - mutate: refetchInfo, - hasMnemonic, - hasNodeBackup, - } = useInfo(); - const navigate = useNavigate(); - const { toast } = useToast(); - const [shuttingDown, setShuttingDown] = useState(false); - - const shutdown = React.useCallback(async () => { - setShuttingDown(true); - try { - await request("/api/stop", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - - await refetchInfo(); - setShuttingDown(false); - navigate("/", { replace: true }); - toast({ title: "Your node has been turned off." }); - } catch (error) { - console.error(error); - toast({ - title: "Failed to shutdown node: " + error, - variant: "destructive", - }); - } - }, [navigate, refetchInfo, toast]); + const { data: info, hasMnemonic, hasNodeBackup } = useInfo(); return ( <> @@ -63,35 +17,9 @@ export default function SettingsLayout() { description="Manage your Alby Hub settings." breadcrumb={false} contentRight={ - - - - {!shuttingDown && } - - - - - - Do you want to turn off Alby Hub? - - - This will turn off your Alby Hub and make your node offline. - You won't be able to send or receive bitcoin until you unlock - it. - - - - Cancel - - Continue - - - - + info?.version && ( +

{info.version}

+ ) } />
@@ -101,9 +29,8 @@ export default function SettingsLayout() { Unlock Password - {hasMnemonic && Backup} - {hasNodeBackup && ( - Migrate Node + {(hasMnemonic || hasNodeBackup) && ( + Backup )} {info?.albyAccountConnected && ( Your Alby Account @@ -113,6 +40,7 @@ export default function SettingsLayout() { )} Developer Debug Tools + Shutdown
diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index 3ec6aeb1..c535c92b 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -42,10 +42,12 @@ import DepositBitcoin from "src/screens/onchain/DepositBitcoin"; import ConnectPeer from "src/screens/peers/ConnectPeer"; import Peers from "src/screens/peers/Peers"; import { AlbyAccount } from "src/screens/settings/AlbyAccount"; +import Backup from "src/screens/settings/Backup"; import { ChangeUnlockPassword } from "src/screens/settings/ChangeUnlockPassword"; import DebugTools from "src/screens/settings/DebugTools"; import DeveloperSettings from "src/screens/settings/DeveloperSettings"; import Settings from "src/screens/settings/Settings"; +import Shutdown from "src/screens/settings/Shutdown"; import { ImportMnemonic } from "src/screens/setup/ImportMnemonic"; import { RestoreNode } from "src/screens/setup/RestoreNode"; import { SetupAdvanced } from "src/screens/setup/SetupAdvanced"; @@ -175,9 +177,14 @@ const routes = [ }, { path: "backup", - element: , + element: , handle: { crumb: () => "Backup" }, }, + { + path: "mnemonic-backup", + element: , + handle: { crumb: () => "Key Backup" }, + }, { path: "node-backup", element: , @@ -194,6 +201,10 @@ const routes = [ path: "debug-tools", element: , }, + { + path: "shutdown", + element: , + }, ], }, ], diff --git a/frontend/src/screens/BackupMnemonic.tsx b/frontend/src/screens/BackupMnemonic.tsx index f086ed6e..10ed400d 100644 --- a/frontend/src/screens/BackupMnemonic.tsx +++ b/frontend/src/screens/BackupMnemonic.tsx @@ -1,13 +1,12 @@ import { + CopyIcon, ExternalLinkIcon, LifeBuoy, ShieldAlert, - ShieldCheck, } from "lucide-react"; import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; -import Container from "src/components/Container"; import ExternalLink from "src/components/ExternalLink"; import MnemonicInputs from "src/components/MnemonicInputs"; import SettingsHeader from "src/components/SettingsHeader"; @@ -18,6 +17,7 @@ import { Label } from "src/components/ui/label"; import { LoadingButton } from "src/components/ui/loading-button"; import { useToast } from "src/components/ui/use-toast"; import { useInfo } from "src/hooks/useInfo"; +import { copyToClipboard } from "src/lib/clipboard"; import { MnemonicResponse } from "src/types"; import { handleRequestError } from "src/utils/handleRequestError"; import { request } from "src/utils/request"; @@ -92,38 +92,39 @@ export function BackupMnemonic() { return ( <> {!decryptedMnemonic ? ( - -

Please confirm it's you

-

- Enter your unlock password to continue +

+

+ Enter your unlock password to view your recovery phrase.

- <> -
- - setUnlockPassword(e.target.value)} - value={unlockPassword} - placeholder="Password" - /> -
- Continue - +
+ + setUnlockPassword(e.target.value)} + value={unlockPassword} + placeholder="Password" + /> +
+
+ + View Recovery Phrase + +
- +
) : (
@@ -148,14 +149,6 @@ export function BackupMnemonic() { )}
-
-
- -
- - Make sure to write them down somewhere safe and private. - -
@@ -180,8 +173,13 @@ export function BackupMnemonic() {
- -
+ +
{backedUp && !info?.albyAccountConnected && ( -
+
)} +
+ +
+ )} + + {!hasMnemonic && !hasNodeBackup && ( +

+ No wallet recovery phrase or channel state backup present. +

+ )} + + ); +} diff --git a/frontend/src/screens/settings/ChangeUnlockPassword.tsx b/frontend/src/screens/settings/ChangeUnlockPassword.tsx index 70b06974..bed32f9d 100644 --- a/frontend/src/screens/settings/ChangeUnlockPassword.tsx +++ b/frontend/src/screens/settings/ChangeUnlockPassword.tsx @@ -1,7 +1,8 @@ +import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import React from "react"; -import Container from "src/components/Container"; import SettingsHeader from "src/components/SettingsHeader"; +import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert"; import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { LoadingButton } from "src/components/ui/loading-button"; @@ -57,12 +58,21 @@ export function ChangeUnlockPassword() { return ( <> - -
+
+ + +
+ Important! +
+
+ + Password can't be reset or recovered. Make sure to back it up! + +
+
- Change Password +
+ Change Password +
- +
); } diff --git a/frontend/src/screens/settings/Shutdown.tsx b/frontend/src/screens/settings/Shutdown.tsx new file mode 100644 index 00000000..5b76f266 --- /dev/null +++ b/frontend/src/screens/settings/Shutdown.tsx @@ -0,0 +1,80 @@ +import { Power } from "lucide-react"; +import React, { useState } from "react"; +import Lottie from "react-lottie"; +import { useNavigate } from "react-router-dom"; +import animationData from "src/assets/lotties/loading.json"; +import SettingsHeader from "src/components/SettingsHeader"; +import { LoadingButton } from "src/components/ui/loading-button"; +import { useToast } from "src/components/ui/use-toast"; +import { useInfo } from "src/hooks/useInfo"; +import { request } from "src/utils/request"; + +function Shutdown() { + const [shuttingDown, setShuttingDown] = useState(false); + const { mutate: refetchInfo } = useInfo(); + const navigate = useNavigate(); + const { toast } = useToast(); + + const defaultOptions = { + loop: true, + autoplay: true, + animationData: animationData, + rendererSettings: { + preserveAspectRatio: "xMidYMid slice", + }, + }; + + const shutdown = React.useCallback(async () => { + setShuttingDown(true); + try { + await request("/api/stop", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + await refetchInfo(); + setShuttingDown(false); + navigate("/", { replace: true }); + toast({ title: "Your node has been turned off." }); + } catch (error) { + console.error(error); + toast({ + title: "Failed to shutdown node: " + error, + variant: "destructive", + }); + } + }, [navigate, refetchInfo, toast]); + + return ( + <> + + {shuttingDown ? ( +
+ +

+ Shutting down... +

+
+ ) : ( + <> +
+
+ +
+ Shutdown +
+
+
+
+ + )} + + ); +} + +export default Shutdown; diff --git a/frontend/src/screens/setup/ImportMnemonic.tsx b/frontend/src/screens/setup/ImportMnemonic.tsx index 10ad6470..75edb493 100644 --- a/frontend/src/screens/setup/ImportMnemonic.tsx +++ b/frontend/src/screens/setup/ImportMnemonic.tsx @@ -62,7 +62,7 @@ export function ImportMnemonic() { <>