mirror of
https://github.com/block/buzz.git
synced 2026-11-01 06:51:17 +00:00
feat(desktop): refine optional backup verification
- Present backup verification as an optional post-download step with contextual headings and a Skip for now action. - Match the verification password field to the backup password input and move its primary action into the onboarding footer. - Add an upward-pulsing file-to-password illustration with reduced-motion support and a stronger selected-file affordance. - Remove saved filesystem paths from the verification view to avoid exposing local details. - Reset encrypted-backup state whenever users re-enter the creation flow. - Confirm before leaving only when an unfinished pre-download password or encryption would be discarded. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
committed by
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w
parent
09bc460196
commit
9a7faa9425
@@ -1,6 +1,7 @@
|
||||
import { Check, Eye, EyeOff, FileKey2, FileUp } from "lucide-react";
|
||||
import { Check, CircleHelp, Eye, EyeOff, FileKey2, FileUp } from "lucide-react";
|
||||
import { motion, useReducedMotion } from "motion/react";
|
||||
import * as React from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import {
|
||||
verifyNcryptsecBackup,
|
||||
@@ -15,7 +16,6 @@ import { Spinner } from "@/shared/ui/spinner";
|
||||
import {
|
||||
ONBOARDING_SECURITY_PRIMARY_CTA_CLASS,
|
||||
ONBOARDING_SECONDARY_CTA_CLASS,
|
||||
ONBOARDING_SECURITY_ICON_CLASS,
|
||||
} from "./OnboardingChrome";
|
||||
|
||||
type BackupTestStage = "drop" | "password" | "success";
|
||||
@@ -56,8 +56,9 @@ type BackupTestFlowProps = {
|
||||
/** Re-open the native save dialog for another copy of the backup file. */
|
||||
onSaveCopy?: () => void;
|
||||
isSaving?: boolean;
|
||||
savedPath?: string | null;
|
||||
saveError?: string | null;
|
||||
/** Optional onboarding footer target for the verification CTA. */
|
||||
verifyButtonPortal?: HTMLElement | null;
|
||||
/** Host-owned progress so it survives this component unmounting. */
|
||||
progress: BackupTestProgress;
|
||||
onProgressChange: React.Dispatch<React.SetStateAction<BackupTestProgress>>;
|
||||
@@ -67,6 +68,22 @@ type BackupTestFlowProps = {
|
||||
|
||||
const BURST_EMOJIS = ["🎉", "✨", "🐝", "🍯", "🔑", "💛"] as const;
|
||||
const BURST_PARTICLE_COUNT = 18;
|
||||
const VERIFICATION_CONNECTOR_DOTS = [
|
||||
"verification-dot-1",
|
||||
"verification-dot-2",
|
||||
"verification-dot-3",
|
||||
"verification-dot-4",
|
||||
] as const;
|
||||
const VERIFICATION_DOT_ANIMATION = {
|
||||
opacity: [0.35, 1, 0.35],
|
||||
scale: [0.85, 1.25, 0.85],
|
||||
};
|
||||
const VERIFICATION_DOT_TRANSITION = {
|
||||
duration: 0.7,
|
||||
ease: "easeInOut" as const,
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
repeatDelay: 1.2,
|
||||
};
|
||||
|
||||
type BurstParticle = {
|
||||
id: number;
|
||||
@@ -132,6 +149,43 @@ function SuccessBurst() {
|
||||
);
|
||||
}
|
||||
|
||||
function VerificationConnector({
|
||||
delayOffset,
|
||||
reduceMotion,
|
||||
}: {
|
||||
delayOffset: number;
|
||||
reduceMotion: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
className="my-5 flex h-14 flex-col items-center justify-between py-1"
|
||||
>
|
||||
{VERIFICATION_CONNECTOR_DOTS.map((dot, index) => (
|
||||
<motion.span
|
||||
animate={reduceMotion ? undefined : VERIFICATION_DOT_ANIMATION}
|
||||
className="block size-1.5 rounded-full bg-foreground/65"
|
||||
initial={reduceMotion ? false : { opacity: 0.35, scale: 0.85 }}
|
||||
key={dot}
|
||||
transition={
|
||||
reduceMotion
|
||||
? undefined
|
||||
: {
|
||||
...VERIFICATION_DOT_TRANSITION,
|
||||
delay:
|
||||
(VERIFICATION_CONNECTOR_DOTS.length -
|
||||
1 -
|
||||
index +
|
||||
delayOffset) *
|
||||
0.16,
|
||||
}
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* "Test your backup" flow: the user drops a backup file onto a large
|
||||
* dropzone, then enters its password. Verification is a real NIP-49 decrypt
|
||||
@@ -143,8 +197,8 @@ export function BackupTestFlow({
|
||||
expectedNcryptsec,
|
||||
onSaveCopy,
|
||||
isSaving = false,
|
||||
savedPath,
|
||||
saveError,
|
||||
verifyButtonPortal,
|
||||
progress,
|
||||
onProgressChange,
|
||||
onVerified,
|
||||
@@ -443,14 +497,6 @@ export function BackupTestFlow({
|
||||
{isSaving ? <Spinner className="h-4 w-4 border-2" /> : null}
|
||||
Re-download backup
|
||||
</Button>
|
||||
{savedPath ? (
|
||||
<p
|
||||
className="text-center text-xs text-muted-foreground"
|
||||
data-testid="encrypted-backup-saved-path"
|
||||
>
|
||||
Saved to {savedPath}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{saveError ? (
|
||||
@@ -465,119 +511,142 @@ export function BackupTestFlow({
|
||||
key="password"
|
||||
transition={{ duration: reduceMotion ? 0 : 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center gap-2 text-sm text-foreground animate-in fade-in slide-in-from-bottom-1 duration-300 motion-reduce:animate-none"
|
||||
data-testid="backup-test-file-accepted"
|
||||
>
|
||||
<FileKey2
|
||||
aria-hidden="true"
|
||||
className="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
<span className="max-w-70 truncate font-mono text-xs">
|
||||
{fileName}
|
||||
</span>
|
||||
<Check aria-hidden="true" className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<p className="text-center text-sm leading-6 text-muted-foreground">
|
||||
That's the one. Now enter your password to prove you can unlock it.
|
||||
</p>
|
||||
<div className="relative">
|
||||
<Input
|
||||
aria-label="Backup password"
|
||||
autoComplete="off"
|
||||
className={cn(
|
||||
"font-mono",
|
||||
isSpotlight
|
||||
? "h-14 rounded-none border-0 bg-transparent px-14 text-center text-lg shadow-none focus-visible:ring-0"
|
||||
: "h-10 bg-background pr-10",
|
||||
)}
|
||||
data-testid="backup-test-password"
|
||||
disabled={isVerifying}
|
||||
onChange={(event) => setAttempt(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
void handleVerify();
|
||||
}
|
||||
}}
|
||||
placeholder="Your backup password"
|
||||
ref={passwordInputRef}
|
||||
type={isRevealed ? "text" : "password"}
|
||||
value={attempt}
|
||||
/>
|
||||
<Button
|
||||
aria-label={isRevealed ? "Hide password" : "Reveal password"}
|
||||
className={cn(
|
||||
"absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
||||
isSpotlight && ONBOARDING_SECURITY_ICON_CLASS,
|
||||
)}
|
||||
data-testid="backup-test-password-reveal-toggle"
|
||||
disabled={isVerifying}
|
||||
onClick={() => setIsRevealed((revealed) => !revealed)}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{isRevealed ? (
|
||||
<EyeOff aria-hidden="true" className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye aria-hidden="true" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
{error ? (
|
||||
<p
|
||||
className="absolute left-1 top-full mt-1 text-xs text-destructive animate-in fade-in duration-200 motion-reduce:animate-none"
|
||||
data-testid="backup-test-error"
|
||||
role="alert"
|
||||
{(() => {
|
||||
const fileRow = (
|
||||
<div
|
||||
className="flex max-w-full items-center gap-3 rounded-2xl border border-foreground/15 bg-foreground/10 px-4 py-3 text-foreground shadow-sm animate-in fade-in slide-in-from-bottom-1 duration-300 motion-reduce:animate-none"
|
||||
data-testid="backup-test-file-accepted"
|
||||
>
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-3 pt-2">
|
||||
<Button
|
||||
className={cn(
|
||||
"font-medium",
|
||||
isSpotlight
|
||||
? ONBOARDING_SECURITY_PRIMARY_CTA_CLASS
|
||||
: "h-9 px-6 text-sm text-primary-foreground",
|
||||
)}
|
||||
data-testid="backup-test-verify"
|
||||
disabled={!attempt || isVerifying}
|
||||
onClick={() => void handleVerify()}
|
||||
type="button"
|
||||
>
|
||||
{isVerifying ? (
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-xl bg-foreground/10">
|
||||
<FileKey2
|
||||
aria-hidden="true"
|
||||
className="size-5 text-foreground/80"
|
||||
/>
|
||||
</span>
|
||||
<span className="max-w-70 truncate font-mono text-sm">
|
||||
{fileName}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
const passwordField = (
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
aria-label="Backup password"
|
||||
autoComplete="off"
|
||||
className={cn(
|
||||
"font-mono",
|
||||
isSpotlight
|
||||
? "h-14 rounded-2xl border-black/20 bg-white px-14 text-center text-lg text-black/80 shadow-none placeholder:text-black/55 focus-visible:ring-black/35"
|
||||
: "h-10 bg-background pr-10",
|
||||
)}
|
||||
data-testid="backup-test-password"
|
||||
disabled={isVerifying}
|
||||
onChange={(event) => setAttempt(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
void handleVerify();
|
||||
}
|
||||
}}
|
||||
placeholder="Your backup password"
|
||||
ref={passwordInputRef}
|
||||
type={isRevealed ? "text" : "password"}
|
||||
value={attempt}
|
||||
/>
|
||||
<Button
|
||||
aria-label={isRevealed ? "Hide password" : "Reveal password"}
|
||||
className={cn(
|
||||
"absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
||||
isSpotlight &&
|
||||
"text-black/55 hover:bg-black/5 hover:text-black/80",
|
||||
)}
|
||||
data-testid="backup-test-password-reveal-toggle"
|
||||
disabled={isVerifying}
|
||||
onClick={() => setIsRevealed((revealed) => !revealed)}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{isRevealed ? (
|
||||
<EyeOff aria-hidden="true" className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye aria-hidden="true" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
{error ? (
|
||||
<p
|
||||
className="absolute left-1 top-full mt-1 text-xs text-destructive animate-in fade-in duration-200 motion-reduce:animate-none"
|
||||
data-testid="backup-test-error"
|
||||
role="alert"
|
||||
>
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
if (!isSpotlight) {
|
||||
return (
|
||||
<>
|
||||
<Spinner className="h-4 w-4 border-2" />
|
||||
Checking…
|
||||
{fileRow}
|
||||
<p className="text-center text-sm leading-6 text-muted-foreground">
|
||||
Enter the password to prove you can unlock this backup.
|
||||
</p>
|
||||
{passwordField}
|
||||
</>
|
||||
) : (
|
||||
"Verify backup"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
className={
|
||||
isSpotlight
|
||||
? ONBOARDING_SECONDARY_CTA_CLASS
|
||||
: "h-7 px-2 text-xs text-muted-foreground hover:text-foreground"
|
||||
}
|
||||
data-testid="backup-test-use-different-file"
|
||||
disabled={isVerifying}
|
||||
onClick={() => {
|
||||
requestRef.current += 1;
|
||||
setAttempt("");
|
||||
setError(null);
|
||||
setIsRevealed(false);
|
||||
onProgressChange(initialBackupTestProgress);
|
||||
}}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
Use a different file
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center">
|
||||
<CircleHelp
|
||||
aria-hidden="true"
|
||||
className="size-10 text-foreground/85"
|
||||
/>
|
||||
<VerificationConnector
|
||||
delayOffset={VERIFICATION_CONNECTOR_DOTS.length}
|
||||
reduceMotion={reduceMotion}
|
||||
/>
|
||||
{passwordField}
|
||||
<VerificationConnector
|
||||
delayOffset={0}
|
||||
reduceMotion={reduceMotion}
|
||||
/>
|
||||
{fileRow}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{(() => {
|
||||
const verifyButton = (
|
||||
<Button
|
||||
className={cn(
|
||||
"font-medium",
|
||||
isSpotlight
|
||||
? ONBOARDING_SECURITY_PRIMARY_CTA_CLASS
|
||||
: "h-9 px-6 text-sm text-primary-foreground",
|
||||
)}
|
||||
data-testid="backup-test-verify"
|
||||
disabled={!attempt || isVerifying}
|
||||
onClick={() => void handleVerify()}
|
||||
type="button"
|
||||
>
|
||||
{isVerifying ? (
|
||||
<>
|
||||
<Spinner className="h-4 w-4 border-2" />
|
||||
Checking…
|
||||
</>
|
||||
) : (
|
||||
"Verify backup"
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
if (verifyButtonPortal === undefined) {
|
||||
return (
|
||||
<div className="flex justify-center pt-2">{verifyButton}</div>
|
||||
);
|
||||
}
|
||||
return verifyButtonPortal
|
||||
? createPortal(verifyButton, verifyButtonPortal)
|
||||
: null;
|
||||
})()}
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { motion, useReducedMotion } from "motion/react";
|
||||
import * as React from "react";
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/shared/ui/alert-dialog";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
ONBOARDING_SECURITY_PRIMARY_CTA_CLASS,
|
||||
@@ -41,7 +51,16 @@ export function DownloadKeyStep({
|
||||
// Once the encrypted payload is saved, the creator advances to its guided
|
||||
// backup test while this surface keeps its own navigation.
|
||||
const hasCreated = session.created;
|
||||
const [createButtonSlot, setCreateButtonSlot] =
|
||||
const hasSelectedBackup = session.test.stage === "password";
|
||||
const hasUnsavedProgress =
|
||||
!hasCreated &&
|
||||
(session.state.passphrase.length > 0 ||
|
||||
session.state.requestId !== null ||
|
||||
session.state.encrypted !== null ||
|
||||
session.state.ncryptsec !== null ||
|
||||
session.state.downloadPending);
|
||||
const [confirmBack, setConfirmBack] = React.useState(false);
|
||||
const [primaryActionSlot, setPrimaryActionSlot] =
|
||||
React.useState<HTMLElement | null>(null);
|
||||
|
||||
return (
|
||||
@@ -61,14 +80,18 @@ export function DownloadKeyStep({
|
||||
{/* Plain string concat: cn()'s tailwind-merge misreads the custom
|
||||
text-title size token as conflicting with text-foreground. */}
|
||||
<h1 className="text-title font-normal text-foreground">
|
||||
{hasCreated
|
||||
? "Now, test your backup"
|
||||
: "Backup your key with a password"}
|
||||
{hasSelectedBackup
|
||||
? "That’s your backup file"
|
||||
: hasCreated
|
||||
? "Optionally, test your backup"
|
||||
: "Backup your key with a password"}
|
||||
</h1>
|
||||
<p className="mt-5 text-sm leading-6 text-foreground/80">
|
||||
{hasCreated
|
||||
? "Make sure your backup works: drop the file you just saved and unlock it with your password."
|
||||
: "Keep the downloaded file private — you need both it and your password to restore your identity. Save the backup password somewhere safe; Buzz cannot reset it if lost."}
|
||||
{hasSelectedBackup
|
||||
? "Now enter your password to prove you can unlock it."
|
||||
: hasCreated
|
||||
? "Learn how your backup works. Drop the file you just saved and unlock it with your password."
|
||||
: "Keep the downloaded file private — you need both it and your password to restore your identity. Save the backup password somewhere safe; Buzz cannot reset it if lost."}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -90,9 +113,10 @@ export function DownloadKeyStep({
|
||||
>
|
||||
<EncryptedBackupCreator
|
||||
createButtonClassName={ONBOARDING_SECURITY_PRIMARY_CTA_CLASS}
|
||||
createButtonPortal={createButtonSlot}
|
||||
createButtonPortal={primaryActionSlot}
|
||||
session={session}
|
||||
variant="spotlight"
|
||||
verifyButtonPortal={primaryActionSlot}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -100,23 +124,59 @@ export function DownloadKeyStep({
|
||||
</div>
|
||||
|
||||
<OnboardingFooter>
|
||||
{hasCreated ? null : (
|
||||
<div
|
||||
className="flex justify-center"
|
||||
data-testid="onboarding-create-slot"
|
||||
ref={setCreateButtonSlot}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="flex justify-center"
|
||||
data-testid={
|
||||
hasCreated ? "onboarding-verify-slot" : "onboarding-create-slot"
|
||||
}
|
||||
ref={setPrimaryActionSlot}
|
||||
/>
|
||||
<Button
|
||||
className={ONBOARDING_SECONDARY_CTA_CLASS}
|
||||
data-testid="onboarding-back"
|
||||
onClick={onBack}
|
||||
onClick={() => {
|
||||
if (hasUnsavedProgress) {
|
||||
setConfirmBack(true);
|
||||
return;
|
||||
}
|
||||
onBack();
|
||||
}}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
Back
|
||||
{hasCreated ? "Skip for now" : "Back"}
|
||||
</Button>
|
||||
</OnboardingFooter>
|
||||
<AlertDialog open={confirmBack} onOpenChange={setConfirmBack}>
|
||||
<AlertDialogContent
|
||||
className="buzz-onboarding-security-theme"
|
||||
data-testid="backup-discard-dialog"
|
||||
surface="textured"
|
||||
textureSize="compact"
|
||||
textureTone="dark"
|
||||
>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Start over?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Going back now clears any password you’ve entered and unfinished
|
||||
backup progress. If you haven’t downloaded the file, you’ll need
|
||||
to create it again.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className={ONBOARDING_SECONDARY_CTA_CLASS}>
|
||||
Keep working
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className={ONBOARDING_SECURITY_PRIMARY_CTA_CLASS}
|
||||
data-testid="backup-discard-confirm"
|
||||
onClick={onBack}
|
||||
>
|
||||
Discard and go back
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</OnboardingSlideTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -322,6 +322,17 @@ export function backupSessionToPasswordEntry(
|
||||
session.setTest(initialBackupTestProgress);
|
||||
}
|
||||
|
||||
/** Discard all backup-creation and verification progress. */
|
||||
export function resetEncryptedBackupSession(
|
||||
session: EncryptedBackupSession,
|
||||
): void {
|
||||
session.dispatch({ type: "start-new-backup" });
|
||||
session.setVerified(false);
|
||||
session.setSavedPath(null);
|
||||
session.savedForRef.current = null;
|
||||
session.setTest(initialBackupTestProgress);
|
||||
}
|
||||
|
||||
type EncryptedBackupCreatorProps = {
|
||||
/** "spotlight" is the onboarding treatment; "boxed" fits settings cards. */
|
||||
variant?: "spotlight" | "boxed";
|
||||
@@ -330,6 +341,8 @@ type EncryptedBackupCreatorProps = {
|
||||
* rendering inline.
|
||||
*/
|
||||
createButtonPortal?: HTMLElement | null;
|
||||
/** Optional onboarding footer target for the guided-test verification CTA. */
|
||||
verifyButtonPortal?: HTMLElement | null;
|
||||
/** Extra classes for the "Download" button. */
|
||||
createButtonClassName?: string;
|
||||
/**
|
||||
@@ -534,6 +547,7 @@ function PassphraseGeneratorPopover({
|
||||
export function EncryptedBackupCreator({
|
||||
variant = "spotlight",
|
||||
createButtonPortal,
|
||||
verifyButtonPortal,
|
||||
createButtonClassName,
|
||||
session: sessionProp,
|
||||
onCreated,
|
||||
@@ -692,8 +706,8 @@ export function EncryptedBackupCreator({
|
||||
onVerified={handleVerified}
|
||||
progress={test}
|
||||
saveError={saveError}
|
||||
savedPath={savedPath}
|
||||
variant={variant}
|
||||
verifyButtonPortal={verifyButtonPortal}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { DefaultConfigStep } from "./DefaultConfigStep";
|
||||
import { DownloadKeyStep } from "./DownloadKeyStep";
|
||||
import {
|
||||
backupSessionToPasswordEntry,
|
||||
resetEncryptedBackupSession,
|
||||
useEncryptedBackupSession,
|
||||
} from "./EncryptedBackupCreator";
|
||||
import { IdentityKeyHelpDialog } from "./IdentityKeyHelpDialog";
|
||||
@@ -279,6 +280,7 @@ export function MachineOnboardingFlow({
|
||||
<DownloadKeyStep
|
||||
direction={backupDirection}
|
||||
onBack={() => {
|
||||
resetEncryptedBackupSession(backupSession);
|
||||
setBackupDirection("backward");
|
||||
setReturningFromSecurity(false);
|
||||
setBackupSubview("options");
|
||||
@@ -292,6 +294,7 @@ export function MachineOnboardingFlow({
|
||||
onBack={() => setPage("identity")}
|
||||
onNext={() => setPage("setup")}
|
||||
onOpenPasswordBackup={() => {
|
||||
resetEncryptedBackupSession(backupSession);
|
||||
setBackupDirection("forward");
|
||||
setReturningFromSecurity(false);
|
||||
setBackupSubview("password");
|
||||
|
||||
Reference in New Issue
Block a user