mirror of
https://github.com/block/buzz.git
synced 2026-11-01 23:11:58 +00:00
feat(onboarding): rewire machine onboarding step graph around signup-first landing
Replaces the identity-first landing with the onboarding v2 entry: the landing now offers Get started (signup), Log in, and a bottom-docked Sign up without email tertiary entry. Signup and login render placeholder screens until the account-credentials PR fills in the shared form; the no-email path routes to a new advanced-identity page carrying today's create-key/import actions (real Tauri identity commands) plus the identity-key help dialog moved off the landing. New machineOnboardingPages module owns the page union, chrome-position mapping (landing renders no chrome; TOTAL_ONBOARDING_PAGES stays 7), and back-target helpers, with unit tests. An accountEntryPage state makes Backup's Back return to whichever entry created the key, and key-import's Back returns to advanced-identity. Import to setup still skips the backup screen. Enum contract for the advanced-entry PR: advanced-identity is the create/import choice state (placeholder content for now — the no-email path is paused); key-import and backup are unchanged. E2e specs and helpers route via the new advanced-identity entry (new enterAdvancedIdentity helper). PR 2 of the onboarding v2 sequence. Co-authored-by: npub19x6jnl6rhepymwyl2xlltz3ce7rfg2ktllle3g2vu59n3s490k8s9n40l3 <29b529ff43be424db89f51bff58a38cf86942acbffff98a14ce50b38c2a57d8f@buzz.block.builderlab.xyz> Signed-off-by: npub19x6jnl6rhepymwyl2xlltz3ce7rfg2ktllle3g2vu59n3s490k8s9n40l3 <29b529ff43be424db89f51bff58a38cf86942acbffff98a14ce50b38c2a57d8f@buzz.block.builderlab.xyz>
This commit is contained in:
parent
747720cff9
commit
fc6600be9a
@@ -15,18 +15,28 @@ import { LandingBees } from "./LandingBees";
|
||||
import { NostrKeyImportForm } from "./NostrKeyImportForm";
|
||||
import {
|
||||
ONBOARDING_LANDING_CTA_CLASS,
|
||||
ONBOARDING_PRIMARY_CTA_CLASS,
|
||||
OnboardingChrome,
|
||||
} from "./OnboardingChrome";
|
||||
import { OnboardingFooterProvider } from "./OnboardingFooter";
|
||||
import { OnboardingFooter, OnboardingFooterProvider } from "./OnboardingFooter";
|
||||
import { OnboardingSlideTransition } from "./OnboardingSlideTransition";
|
||||
import { SetupStep } from "./SetupStep";
|
||||
import {
|
||||
machineOnboardingChromePosition,
|
||||
type MachineOnboardingPage,
|
||||
machineSetupBackTarget,
|
||||
} from "./machineOnboardingPages";
|
||||
|
||||
export type MachineOnboardingPage =
|
||||
| "identity"
|
||||
| "key-import"
|
||||
| "backup"
|
||||
| "setup"
|
||||
| "config";
|
||||
export type { MachineOnboardingPage } from "./machineOnboardingPages";
|
||||
|
||||
/**
|
||||
* The two screens that can kick off key creation. Backup's Back button
|
||||
* returns to whichever one the user came from.
|
||||
*/
|
||||
type AccountEntryPage = Extract<
|
||||
MachineOnboardingPage,
|
||||
"signup" | "advanced-identity"
|
||||
>;
|
||||
|
||||
export function MachineOnboardingFlow({
|
||||
complete,
|
||||
@@ -44,6 +54,11 @@ export function MachineOnboardingFlow({
|
||||
const [page, setPage] = React.useState<MachineOnboardingPage>(
|
||||
identityLost ? "key-import" : (initialPage ?? "identity"),
|
||||
);
|
||||
// Which account-entry screen led to key creation. Backup's Back button
|
||||
// returns here, so a "Sign up without email" user is never dropped onto the
|
||||
// signup form (and vice versa).
|
||||
const [accountEntryPage, setAccountEntryPage] =
|
||||
React.useState<AccountEntryPage>("advanced-identity");
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [isPending, setIsPending] = React.useState(false);
|
||||
const [identityWasImported, setIdentityWasImported] = React.useState(false);
|
||||
@@ -109,6 +124,8 @@ export function MachineOnboardingFlow({
|
||||
[continueWithIdentity, queryClient],
|
||||
);
|
||||
|
||||
const chromePosition = machineOnboardingChromePosition(page);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`buzz-onboarding-neutral-theme buzz-startup-shell flex max-h-dvh items-start justify-center overflow-x-hidden overflow-y-auto px-4 text-foreground ${
|
||||
@@ -120,10 +137,8 @@ export function MachineOnboardingFlow({
|
||||
>
|
||||
<StartupWindowDragRegion />
|
||||
{page === "identity" ? <LandingBees /> : null}
|
||||
{page !== "identity" ? (
|
||||
<OnboardingChrome
|
||||
current={page === "config" ? 4 : page === "setup" ? 3 : 2}
|
||||
/>
|
||||
{chromePosition !== null ? (
|
||||
<OnboardingChrome current={chromePosition} />
|
||||
) : null}
|
||||
<OnboardingFooterProvider>
|
||||
<div
|
||||
@@ -153,8 +168,133 @@ export function MachineOnboardingFlow({
|
||||
<div className="mt-10 flex flex-col items-center gap-3">
|
||||
<Button
|
||||
className={ONBOARDING_LANDING_CTA_CLASS}
|
||||
onClick={() => setPage("signup")}
|
||||
type="button"
|
||||
>
|
||||
Get started
|
||||
</Button>
|
||||
<Button
|
||||
className="h-9 rounded-full bg-foreground/10 px-5 hover:bg-foreground/15"
|
||||
onClick={() => setPage("login")}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</div>
|
||||
{/* Tertiary entry to the no-email flow, docked to the bottom of
|
||||
the viewport via the shared footer slot rather than stacked
|
||||
under the primary CTAs. */}
|
||||
<OnboardingFooter>
|
||||
<button
|
||||
className="text-sm text-foreground/70 hover:text-foreground"
|
||||
onClick={() => setPage("advanced-identity")}
|
||||
type="button"
|
||||
>
|
||||
Sign up without email
|
||||
</button>
|
||||
</OnboardingFooter>
|
||||
</OnboardingSlideTransition>
|
||||
) : page === "signup" ? (
|
||||
<OnboardingSlideTransition
|
||||
className="flex w-full max-w-[720px] flex-col items-center text-center"
|
||||
direction="forward"
|
||||
effect="fade"
|
||||
transitionKey="machine-signup"
|
||||
>
|
||||
<h1 className="text-title font-normal text-foreground">
|
||||
Sign up for Buzz
|
||||
</h1>
|
||||
<p className="mt-5 max-w-[440px] text-sm leading-6 text-foreground/80">
|
||||
Create an account with your email and a password. Your unique
|
||||
identity key is created along with your account.
|
||||
</p>
|
||||
{error ? (
|
||||
<p className="mt-4 text-sm text-destructive">{error}</p>
|
||||
) : null}
|
||||
{/* Placeholder — the email + password form lands in the
|
||||
signup/login PR. "Create account" runs the same key creation
|
||||
the account will be tied to, keeping the flow walkable. */}
|
||||
<OnboardingFooter>
|
||||
<Button
|
||||
className={ONBOARDING_PRIMARY_CTA_CLASS}
|
||||
data-testid="onboarding-next"
|
||||
disabled={isPending}
|
||||
onClick={() => void loadFreshIdentity()}
|
||||
onClick={() => {
|
||||
setAccountEntryPage("signup");
|
||||
void loadFreshIdentity();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{isPending ? "Creating account…" : "Create account"}
|
||||
</Button>
|
||||
<Button
|
||||
className="h-9 rounded-full bg-foreground/10 px-6 hover:bg-foreground/15"
|
||||
data-testid="onboarding-back"
|
||||
disabled={isPending}
|
||||
onClick={() => setPage("identity")}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</OnboardingFooter>
|
||||
</OnboardingSlideTransition>
|
||||
) : page === "login" ? (
|
||||
<OnboardingSlideTransition
|
||||
className="flex w-full max-w-[720px] flex-col items-center text-center"
|
||||
direction="forward"
|
||||
effect="fade"
|
||||
transitionKey="machine-login"
|
||||
>
|
||||
<h1 className="text-title font-normal text-foreground">
|
||||
Log in to Buzz
|
||||
</h1>
|
||||
<p className="mt-5 max-w-[440px] text-sm leading-6 text-foreground/80">
|
||||
Welcome back. Log in with the email and password you signed up
|
||||
with.
|
||||
</p>
|
||||
{/* Placeholder — the login form lands in the signup/login PR. */}
|
||||
<OnboardingFooter>
|
||||
<Button
|
||||
className="h-9 rounded-full bg-foreground/10 px-6 hover:bg-foreground/15"
|
||||
data-testid="onboarding-back"
|
||||
onClick={() => setPage("identity")}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</OnboardingFooter>
|
||||
</OnboardingSlideTransition>
|
||||
) : page === "advanced-identity" ? (
|
||||
<OnboardingSlideTransition
|
||||
className="flex w-full max-w-[720px] flex-col items-center text-center"
|
||||
direction="forward"
|
||||
effect="fade"
|
||||
transitionKey="machine-advanced-identity"
|
||||
>
|
||||
{/* Placeholder — the advanced-entry PR replaces this content
|
||||
with the full create/import choice screen. The two actions
|
||||
below keep the no-email path walkable meanwhile. */}
|
||||
<h1 className="text-title font-normal text-foreground">
|
||||
Sign up without email
|
||||
</h1>
|
||||
<p className="mt-5 max-w-[440px] text-sm leading-6 text-foreground/80">
|
||||
Use Buzz with an identity key alone — create a new key, or bring
|
||||
one you already have.
|
||||
</p>
|
||||
{error ? (
|
||||
<p className="mt-4 text-sm text-destructive">{error}</p>
|
||||
) : null}
|
||||
<div className="mt-10 flex flex-col items-center gap-3">
|
||||
<Button
|
||||
className={ONBOARDING_PRIMARY_CTA_CLASS}
|
||||
disabled={isPending}
|
||||
onClick={() => {
|
||||
setAccountEntryPage("advanced-identity");
|
||||
void loadFreshIdentity();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{isPending ? "Saving identity…" : "Create a new identity key"}
|
||||
@@ -170,6 +310,18 @@ export function MachineOnboardingFlow({
|
||||
</Button>
|
||||
</div>
|
||||
<IdentityKeyHelpDialog />
|
||||
<OnboardingFooter>
|
||||
<Button
|
||||
className="h-9 rounded-full bg-foreground/10 px-6 hover:bg-foreground/15"
|
||||
data-testid="onboarding-back"
|
||||
disabled={isPending}
|
||||
onClick={() => setPage("identity")}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</OnboardingFooter>
|
||||
</OnboardingSlideTransition>
|
||||
) : page === "key-import" ? (
|
||||
<OnboardingSlideTransition
|
||||
@@ -196,7 +348,7 @@ export function MachineOnboardingFlow({
|
||||
onBack={
|
||||
identityLost
|
||||
? () => void replaceLostIdentity()
|
||||
: () => setPage("identity")
|
||||
: () => setPage("advanced-identity")
|
||||
}
|
||||
onImport={importExistingIdentity}
|
||||
variant="spotlight"
|
||||
@@ -206,14 +358,14 @@ export function MachineOnboardingFlow({
|
||||
) : page === "backup" ? (
|
||||
<BackupStep
|
||||
direction="forward"
|
||||
onBack={() => setPage("identity")}
|
||||
onBack={() => setPage(accountEntryPage)}
|
||||
onNext={() => setPage("setup")}
|
||||
/>
|
||||
) : page === "setup" ? (
|
||||
<SetupStep
|
||||
actions={{
|
||||
back: () =>
|
||||
setPage(identityWasImported ? "key-import" : "backup"),
|
||||
setPage(machineSetupBackTarget(identityWasImported)),
|
||||
next: (runtimeIds) => {
|
||||
setReadyRuntimeIds(Array.from(runtimeIds));
|
||||
setPage("config");
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { BuzzMark } from "@/shared/ui/buzz-logo/BuzzMark";
|
||||
|
||||
/**
|
||||
* Positions in the first-launch flow: landing, identity/key, harness setup,
|
||||
* default config, community choice, community profile, meet the team. Used as
|
||||
* the default pagination length when a flow doesn't pass an explicit total.
|
||||
* Positions in the first-launch flow: account entry (signup / login /
|
||||
* sign-up-without-email), identity key, harness setup, default config,
|
||||
* community choice, community profile, meet the team. The landing screen
|
||||
* renders no chrome and holds no position. Used as the default pagination
|
||||
* length when a flow doesn't pass an explicit total.
|
||||
*/
|
||||
export const TOTAL_ONBOARDING_PAGES = 7;
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Tests for the machine onboarding page graph (onboarding v2): chrome
|
||||
* positions on the pagination track and the setup Back target. Pure-logic
|
||||
* tests — no React rendering needed.
|
||||
*
|
||||
* The page names asserted here are a cross-PR contract (the advanced-entry
|
||||
* PR builds against `advanced-identity`; the signup/login PR fills `signup`
|
||||
* and `login`) — renames must be coordinated in #buzz-onboarding-v2.
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
machineOnboardingChromePosition,
|
||||
machineSetupBackTarget,
|
||||
} from "./machineOnboardingPages.ts";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Chrome positions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("landing_renders_no_chrome", () => {
|
||||
assert.equal(machineOnboardingChromePosition("identity"), null);
|
||||
});
|
||||
|
||||
test("account_entry_screens_share_position_1", () => {
|
||||
// Signup, login, and the no-email entry are branches of the same flow
|
||||
// position — the track counts positions, not branches.
|
||||
assert.equal(machineOnboardingChromePosition("signup"), 1);
|
||||
assert.equal(machineOnboardingChromePosition("login"), 1);
|
||||
assert.equal(machineOnboardingChromePosition("advanced-identity"), 1);
|
||||
});
|
||||
|
||||
test("key_screens_share_position_2", () => {
|
||||
assert.equal(machineOnboardingChromePosition("key-import"), 2);
|
||||
assert.equal(machineOnboardingChromePosition("backup"), 2);
|
||||
});
|
||||
|
||||
test("setup_is_position_3_and_config_is_4", () => {
|
||||
assert.equal(machineOnboardingChromePosition("setup"), 3);
|
||||
assert.equal(machineOnboardingChromePosition("config"), 4);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Setup Back target — import-skips-backup asymmetry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("setup_back_returns_to_key_import_for_imported_identities", () => {
|
||||
// Imported keys skip the backup step on the way in (the user already has
|
||||
// their key saved); Back must never surface backup for them.
|
||||
assert.equal(machineSetupBackTarget(true), "key-import");
|
||||
});
|
||||
|
||||
test("setup_back_returns_to_backup_for_fresh_identities", () => {
|
||||
assert.equal(machineSetupBackTarget(false), "backup");
|
||||
});
|
||||
62
desktop/src/features/onboarding/ui/machineOnboardingPages.ts
Normal file
62
desktop/src/features/onboarding/ui/machineOnboardingPages.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Pure page-graph data for the machine onboarding flow (onboarding v2).
|
||||
*
|
||||
* State names are a cross-PR contract — the advanced (no-email) entry PR
|
||||
* builds against `advanced-identity`, and the signup/login PR fills `signup`
|
||||
* and `login`. Do not rename without coordinating in #buzz-onboarding-v2.
|
||||
*/
|
||||
|
||||
export type MachineOnboardingPage =
|
||||
/** Landing screen: wordmark + tagline + entry CTAs. Renders no chrome. */
|
||||
| "identity"
|
||||
/** Standard flow: email + password signup (placeholder until PR 3). */
|
||||
| "signup"
|
||||
/** Returning users: email + password login (placeholder until PR 3). */
|
||||
| "login"
|
||||
/**
|
||||
* Advanced (no-email) flow behind "Sign up without email": create/import
|
||||
* key choice. Placeholder until the advanced-entry PR fills it.
|
||||
*/
|
||||
| "advanced-identity"
|
||||
| "key-import"
|
||||
| "backup"
|
||||
| "setup"
|
||||
| "config";
|
||||
|
||||
/**
|
||||
* Position of a machine-flow page on the shared onboarding pagination track
|
||||
* (`OnboardingChrome`), or `null` for the landing screen, which renders no
|
||||
* chrome. The account-entry screens (signup / login / advanced-identity)
|
||||
* share position 1; key screens share position 2 — the track counts flow
|
||||
* positions, not branches.
|
||||
*/
|
||||
export function machineOnboardingChromePosition(
|
||||
page: MachineOnboardingPage,
|
||||
): number | null {
|
||||
switch (page) {
|
||||
case "identity":
|
||||
return null;
|
||||
case "signup":
|
||||
case "login":
|
||||
case "advanced-identity":
|
||||
return 1;
|
||||
case "key-import":
|
||||
case "backup":
|
||||
return 2;
|
||||
case "setup":
|
||||
return 3;
|
||||
case "config":
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Back target from the setup (providers) step. Imported identities skip the
|
||||
* backup step on the way in (the user already has their key saved), so Back
|
||||
* must return to key-import — never surface backup for an imported key.
|
||||
*/
|
||||
export function machineSetupBackTarget(
|
||||
identityWasImported: boolean,
|
||||
): MachineOnboardingPage {
|
||||
return identityWasImported ? "key-import" : "backup";
|
||||
}
|
||||
@@ -11,6 +11,8 @@ test("identity key help explains the first-run choice", async ({ page }) => {
|
||||
skipOnboardingSeed: true,
|
||||
});
|
||||
await page.goto("/");
|
||||
// The help trigger lives on the advanced (no-email) identity screen.
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
|
||||
const trigger = page.getByTestId("identity-key-help-trigger");
|
||||
// No initial opacity-0 assertion: on a slow runner the 2s reveal timer can
|
||||
@@ -51,6 +53,7 @@ test("identity key help explains the first-run choice", async ({ page }) => {
|
||||
await expect(trigger).toHaveCSS("opacity", "1");
|
||||
|
||||
await page.reload();
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await expect(page.getByTestId("identity-key-help-trigger")).toHaveCSS(
|
||||
"opacity",
|
||||
"1",
|
||||
|
||||
@@ -20,9 +20,11 @@ test("normal first launch uses the already-persisted identity", async ({
|
||||
// Landing carries a subtle dot-grid pattern over the chartreuse fill.
|
||||
await expect(gate).toHaveCSS("background-image", /radial-gradient/);
|
||||
await expect(gate).toHaveCSS("color", "rgb(23, 23, 23)");
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Create a new identity key" }),
|
||||
).toHaveCSS("background-color", "rgb(23, 23, 23)");
|
||||
await expect(page.getByRole("button", { name: "Get started" })).toHaveCSS(
|
||||
"background-color",
|
||||
"rgb(23, 23, 23)",
|
||||
);
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Create a new identity key" }).click();
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -18,6 +18,7 @@ test("key import masks the key with a reveal toggle", async ({ page }) => {
|
||||
});
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Use an existing key" }).click();
|
||||
const input = page.getByTestId("nostr-import-nsec-input");
|
||||
await expect(input).toBeVisible();
|
||||
|
||||
@@ -38,6 +38,7 @@ function runtime(
|
||||
async function navigateToSetupPage(
|
||||
page: Parameters<typeof installMockBridge>[0],
|
||||
) {
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Create a new identity key" }).click();
|
||||
await passThroughBackupStep(page);
|
||||
await expect(page.getByTestId("onboarding-page-2")).toBeVisible();
|
||||
|
||||
@@ -8,6 +8,7 @@ async function enterMachineBackup(page: import("@playwright/test").Page) {
|
||||
skipOnboardingSeed: true,
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Create a new identity key" }).click();
|
||||
}
|
||||
|
||||
@@ -103,6 +104,7 @@ test("backup step shows error banner and retry button when get_nsec fails", asyn
|
||||
{ skipCommunitySeed: true, skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Create a new identity key" }).click();
|
||||
|
||||
await expect(page.getByTestId("onboarding-page-backup")).toBeVisible();
|
||||
@@ -127,6 +129,7 @@ test("backup step retry succeeds and shows key after initial failure", async ({
|
||||
{ skipCommunitySeed: true, skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Create a new identity key" }).click();
|
||||
|
||||
await expect(page.getByTestId("backup-load-error")).toBeVisible();
|
||||
|
||||
@@ -27,6 +27,7 @@ test("machine onboarding: landing, backup, setup docked CTAs", async ({
|
||||
await waitForAnimations(page);
|
||||
await page.screenshot({ path: `${SHOT_DIR}/01-landing.png` });
|
||||
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Use an existing key" }).click();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Enter your private key" }),
|
||||
@@ -82,6 +83,7 @@ test("machine key import remains usable in a short viewport", async ({
|
||||
skipOnboardingSeed: true,
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Use an existing key" }).click();
|
||||
|
||||
const heading = page.getByRole("heading", { name: "Enter your private key" });
|
||||
|
||||
@@ -550,6 +550,7 @@ test("first-launch key import continues to machine setup", async ({ page }) => {
|
||||
});
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await page.getByRole("button", { name: "Use an existing key" }).click();
|
||||
const importedNsec = nsecEncode(hexToBytes(TEST_IDENTITIES.alice.privateKey));
|
||||
await page.getByTestId("nostr-import-nsec-input").fill(importedNsec);
|
||||
|
||||
@@ -16,6 +16,23 @@ export async function seedActiveIdentity(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* From the landing screen, enter the advanced (no-email) identity screen —
|
||||
* the create-key / import-key choice behind "Sign up without email".
|
||||
*/
|
||||
export async function enterAdvancedIdentity(page: Page) {
|
||||
await page.getByRole("button", { name: "Sign up without email" }).click();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Create a new identity key" }),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
/** From the landing screen, create a fresh key via the advanced path. */
|
||||
export async function createFreshIdentityKey(page: Page) {
|
||||
await enterAdvancedIdentity(page);
|
||||
await page.getByRole("button", { name: "Create a new identity key" }).click();
|
||||
}
|
||||
|
||||
/** Navigate through the backup step (fresh-key path). */
|
||||
export async function passThroughBackupStep(page: Page) {
|
||||
await expect(page.getByTestId("onboarding-page-backup")).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user