refactor(onboarding): clarify Nostr identity preview

Signed-off-by: Clay Delk <clay.delk@gmail.com>
This commit is contained in:
Clay Delk
2026-08-27 16:04:11 -04:00
parent 750dcfccc7
commit 381a3e95b7
3 changed files with 131 additions and 53 deletions

View File

@@ -1,4 +1,12 @@
import { Check, Copy, Eye, EyeOff, Info, ShieldCheck } from "lucide-react";
import {
Check,
Copy,
Download,
Eye,
EyeOff,
Info,
ShieldCheck,
} from "lucide-react";
import { useReducedMotion } from "motion/react";
import * as React from "react";
@@ -14,6 +22,7 @@ import {
ONBOARDING_PRIMARY_CTA_CLASS,
ONBOARDING_SECONDARY_CTA_CLASS,
} from "./OnboardingChrome";
import { IdentityKeyHelpDialog } from "./IdentityKeyHelpDialog";
import { OnboardingFooter } from "./OnboardingFooter";
import {
type OnboardingTransitionDirection,
@@ -190,12 +199,12 @@ export function BackupStep({
>
<div className="flex w-full max-w-140 shrink-0 flex-col text-center">
<h1 className="text-title font-normal text-foreground">
Backup options
{previewMode ? "Back up your identity" : "Backup options"}
</h1>
<p className="mt-5 text-sm leading-6 text-foreground/75">
Your identity key works like a password for your Buzz account. Keep
a copy somewhere safe. You can create a backup file and lock it with
a password you can remember.
{previewMode
? "Buzz keeps your identity key protected on this device, but you should make a separate backup in case you lose access."
: "Your identity key works like a password for your Buzz account. Keep a copy somewhere safe. You can create a backup file and lock it with a password you can remember."}
</p>
</div>
@@ -219,7 +228,9 @@ export function BackupStep({
data-testid="backup-option-panel"
>
<span className="text-lg font-medium">
Saved in your password manager
{previewMode
? "Save in your password manager"
: "Saved in your password manager"}
</span>
<span className="mt-3 block text-sm leading-6 text-foreground/65">
Copy your identity key, then save it in a password manager like
@@ -256,7 +267,9 @@ export function BackupStep({
data-testid="backup-option-panel"
>
<span className="text-lg font-medium">
Locked in a backup file
{previewMode
? "Create a locked backup"
: "Locked in a backup file"}
</span>
<span className="mt-3 block text-sm leading-6 text-foreground/65">
Create a backup file and choose a password you can remember.
@@ -306,9 +319,11 @@ export function BackupStep({
className={`text-title font-normal text-foreground ${REVEAL_ANIMATION_CLASS}`}
key={created ? "created" : "creating"}
>
{created
? "Your unique identity key has been created"
: "Creating your identity key"}
{created && previewMode
? "Your identity key is ready"
: created
? "Your unique identity key has been created"
: "Creating your identity key"}
</h1>
{created ? (
<p
@@ -317,16 +332,25 @@ export function BackupStep({
REVEAL_ANIMATION_CLASS,
)}
>
{introStorageDescription} You can continue now, or{" "}
<button
className="rounded-sm font-medium underline decoration-foreground/40 underline-offset-4 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
data-testid="backup-options-link"
onClick={onShowOptions}
type="button"
>
review backup options
</button>{" "}
for ways to restore your account.
{previewMode ? (
<>
This works as the login and password for your Buzz account. Use
it to access Buzz from any device.
</>
) : (
<>
{introStorageDescription} You can continue now, or{" "}
<button
className="rounded-sm font-medium underline decoration-foreground/40 underline-offset-4 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
data-testid="backup-options-link"
onClick={onShowOptions}
type="button"
>
review backup options
</button>{" "}
for ways to restore your account.
</>
)}
</p>
) : null}
</div>
@@ -384,6 +408,28 @@ export function BackupStep({
<Eye className="h-6 w-6" aria-hidden="true" />
)}
</Button>
{previewMode ? (
<Button
aria-label={
copyState === "copied"
? "Private key copied"
: "Copy private key"
}
className="h-10 w-10 shrink-0 text-muted-foreground hover:text-foreground"
data-testid="backup-key-copy"
disabled={copyState === "copying"}
onClick={() => void copyKeyToClipboard()}
size="icon"
type="button"
variant="ghost"
>
{copyState === "copied" ? (
<Check className="h-5 w-5" aria-hidden="true" />
) : (
<Copy className="h-5 w-5" aria-hidden="true" />
)}
</Button>
) : null}
</div>
</Card>
@@ -401,15 +447,33 @@ export function BackupStep({
<p className="mx-auto mt-5 flex max-w-[440px] items-start justify-center gap-1.5 text-center text-xs leading-5 text-[var(--buzz-onboarding-backup-ink)]">
<Info className="mt-0.5 h-3.5 w-3.5 shrink-0" />
<span>
Never share your private key. Anyone with this key can
impersonate you and access everything in your account.
{previewMode
? "Keep this key private. Anyone who has it can access your account. Your public identity is available anytime in Buzz Settings."
: "Never share your private key. Anyone with this key can impersonate you and access everything in your account."}
</span>
</p>
{previewMode ? (
<div className="mt-3">
<IdentityKeyHelpDialog inline previewMode />
</div>
) : null}
</div>
</div>
)}
<OnboardingFooter className={REVEAL_ANIMATION_CLASS}>
{previewMode && created ? (
<Button
className={cn(ONBOARDING_SECONDARY_CTA_CLASS, "gap-2")}
data-testid="backup-options-link"
onClick={onShowOptions}
type="button"
variant="ghost"
>
<Download className="h-4 w-4" aria-hidden="true" />
Back up your identity
</Button>
) : null}
<Button
className={ONBOARDING_PRIMARY_CTA_CLASS}
data-testid="onboarding-next"

View File

@@ -33,11 +33,19 @@ function rememberIdentityKeyHelpSeen() {
}
}
export function IdentityKeyHelpDialog() {
const [isVisible, setIsVisible] = React.useState(hasSeenIdentityKeyHelp);
export function IdentityKeyHelpDialog({
inline = false,
previewMode = false,
}: {
inline?: boolean;
previewMode?: boolean;
}) {
const [isVisible, setIsVisible] = React.useState(
previewMode ? true : hasSeenIdentityKeyHelp,
);
React.useEffect(() => {
if (isVisible) return;
if (previewMode || isVisible) return;
const timeout = window.setTimeout(() => {
rememberIdentityKeyHelpSeen();
@@ -45,25 +53,31 @@ export function IdentityKeyHelpDialog() {
}, IDENTITY_KEY_HELP_DELAY_MS);
return () => window.clearTimeout(timeout);
}, [isVisible]);
}, [isVisible, previewMode]);
const trigger = (
<DialogTrigger asChild>
<Button
className={`text-foreground/70 transition-opacity duration-300 hover:text-foreground motion-reduce:transition-none ${
isVisible ? "opacity-100" : "pointer-events-none opacity-0"
}`}
data-testid="identity-key-help-trigger"
tabIndex={isVisible ? 0 : -1}
type="button"
variant="link"
>
What’s an identity key?
</Button>
</DialogTrigger>
);
return (
<Dialog>
<OnboardingFooter className="max-w-none">
<DialogTrigger asChild>
<Button
className={`text-foreground/70 transition-opacity duration-300 hover:text-foreground motion-reduce:transition-none ${
isVisible ? "opacity-100" : "pointer-events-none opacity-0"
}`}
data-testid="identity-key-help-trigger"
tabIndex={isVisible ? 0 : -1}
type="button"
variant="link"
>
What’s an identity key?
</Button>
</DialogTrigger>
</OnboardingFooter>
{inline ? (
trigger
) : (
<OnboardingFooter className="max-w-none">{trigger}</OnboardingFooter>
)}
<DialogContent
className="buzz-onboarding-neutral-theme max-w-[47.5rem] -translate-y-5"
closeButtonClassName={ONBOARDING_INK_ICON_CLASS}
@@ -82,18 +96,15 @@ export function IdentityKeyHelpDialog() {
>
<div>
<p>
Buzz uses an identity key instead of a traditional account. It’s
created on your device and represents you whenever you use Buzz.
Your Nostr identity has two parts: a private key that signs you
in and a public key you can safely share. You can find your
public identity anytime in Buzz Settings.
</p>
<p>
Your identity belongs to you, not Buzz. There’s no password to
reset, and Buzz can’t recover your key if you lose it. Keep a
backup somewhere safe and never share it. Anyone with your key
can act as you.
</p>
<p>
If you’re new to Buzz, create a new identity key. If you already
have a Nostr identity, use your existing key.
Your identity belongs to you, not Buzz, and can move with you to
another device or compatible Nostr app. Because only you control
the private key, Buzz can’t reset or recover it. Keep a backup
somewhere safe and never share it.
</p>
</div>
</DialogDescription>

View File

@@ -94,13 +94,16 @@ function Landing({
</div>
<OnboardingFooter className="max-w-none">
<Button
className="text-foreground/70 hover:text-foreground"
className="h-auto flex-col gap-0.5 text-foreground/70 hover:text-foreground"
data-testid="onboarding-preview-without-email"
onClick={() => onNavigate("identity-key")}
type="button"
variant="link"
>
Sign up without email
<span>Sign up without email</span>
<span className="text-2xs font-normal text-foreground/50 no-underline">
Create a Nostr identity
</span>
</Button>
</OnboardingFooter>
</OnboardingSlideTransition>