Files
buzz/desktop/src/features/onboarding/ui/DownloadKeyStep.tsx
Taylor Ho bfc79dfb59 fix(desktop): scope inverted buttons to backup flows
- Restore the standard primary CTA treatment for general onboarding steps.
- Add a dedicated inverted CTA style for dark backup-security surfaces.
- Apply the white button treatment only to backup creation, file selection, verification, and password-reset actions.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-07-29 23:52:31 -07:00

123 lines
4.2 KiB
TypeScript

import { motion, useReducedMotion } from "motion/react";
import * as React from "react";
import { Button } from "@/shared/ui/button";
import {
ONBOARDING_SECURITY_PRIMARY_CTA_CLASS,
ONBOARDING_SECONDARY_CTA_CLASS,
} from "./OnboardingChrome";
import { OnboardingFooter } from "./OnboardingFooter";
import {
type OnboardingTransitionDirection,
OnboardingSlideTransition,
} from "./OnboardingSlideTransition";
import {
type EncryptedBackupSession,
EncryptedBackupCreator,
} from "./EncryptedBackupCreator";
type DownloadKeyStepProps = {
direction: OnboardingTransitionDirection;
/**
* Backup state owned by the parent flow so Back navigation (which unmounts
* this step) doesn't discard the created backup, the entered password, or
* the backup-test progress.
*/
session: EncryptedBackupSession;
onBack: () => void;
};
/**
* Password-backup security subview within the identity-key onboarding step.
* The raw key never enters this component: Rust builds the NIP-49 payload
* locally and the native save dialog produces the user-owned file.
*/
export function DownloadKeyStep({
direction,
session,
onBack,
}: DownloadKeyStepProps) {
const reduceMotion = useReducedMotion() ?? false;
// 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] =
React.useState<HTMLElement | null>(null);
return (
<OnboardingSlideTransition
className="flex min-h-0 w-full flex-col items-center"
data-testid="onboarding-page-download"
direction={direction}
transitionKey={`download-${direction}`}
>
<motion.div
animate={{ opacity: 1, y: 0 }}
className="flex w-full max-w-[500px] shrink-0 flex-col text-center"
initial={reduceMotion ? false : { opacity: 0, y: 10 }}
key={hasCreated ? "test-heading" : "password-heading"}
transition={{ duration: reduceMotion ? 0 : 0.3, ease: "easeOut" }}
>
{/* 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"}
</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."}
</p>
</motion.div>
<div className="flex w-full max-w-[1040px] flex-1 flex-col justify-center py-10">
<div className="w-full">
<motion.div
animate={{ opacity: 1, y: 0 }}
initial={reduceMotion ? false : { opacity: 0, y: 12 }}
key={hasCreated ? "test-panel" : "password-panel"}
transition={{
delay: reduceMotion ? 0 : 0.12,
duration: reduceMotion ? 0 : 0.4,
ease: "easeOut",
}}
>
<div
className="mx-auto w-full max-w-140 px-6 py-5"
data-testid="backup-password-panel"
>
<EncryptedBackupCreator
createButtonClassName={ONBOARDING_SECURITY_PRIMARY_CTA_CLASS}
createButtonPortal={createButtonSlot}
session={session}
variant="spotlight"
/>
</div>
</motion.div>
</div>
</div>
<OnboardingFooter>
{hasCreated ? null : (
<div
className="flex justify-center"
data-testid="onboarding-create-slot"
ref={setCreateButtonSlot}
/>
)}
<Button
className={ONBOARDING_SECONDARY_CTA_CLASS}
data-testid="onboarding-back"
onClick={onBack}
type="button"
variant="ghost"
>
Back
</Button>
</OnboardingFooter>
</OnboardingSlideTransition>
);
}