mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
feat(onboarding): refine V3 identity and welcome flow
Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -17,8 +17,8 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"width": 900,
|
||||
"height": 650,
|
||||
"maximized": true,
|
||||
"visible": false,
|
||||
"transparent": false,
|
||||
@@ -30,8 +30,8 @@
|
||||
"y": 25
|
||||
},
|
||||
"backgroundThrottling": "disabled",
|
||||
"minWidth": 800,
|
||||
"minHeight": 500
|
||||
"minWidth": 900,
|
||||
"minHeight": 650
|
||||
}
|
||||
],
|
||||
"macOSPrivateApi": true,
|
||||
|
||||
@@ -29,7 +29,10 @@ import {
|
||||
import { IdentityKeyHelpDialog } from "./IdentityKeyHelpDialog";
|
||||
import { OnboardingFooter } from "./OnboardingFooter";
|
||||
import { useOnboardingPreviewCardLayout } from "./OnboardingPreviewShell";
|
||||
import { ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS } from "./onboardingPreviewCardStyles";
|
||||
import {
|
||||
ONBOARDING_PREVIEW_NEUTRAL_SURFACE_CLASS,
|
||||
ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS,
|
||||
} from "./onboardingPreviewCardStyles";
|
||||
import {
|
||||
type OnboardingTransitionDirection,
|
||||
OnboardingSlideTransition,
|
||||
@@ -53,6 +56,10 @@ let introPlayed = false;
|
||||
|
||||
const REVEAL_ANIMATION_CLASS =
|
||||
"animate-in fade-in duration-700 motion-reduce:animate-none";
|
||||
const V3_KEY_GUIDANCE_ICON_CLASS = cn(
|
||||
"flex h-11 w-11 shrink-0 items-center justify-center rounded-full text-foreground/80",
|
||||
ONBOARDING_PREVIEW_NEUTRAL_SURFACE_CLASS,
|
||||
);
|
||||
|
||||
const BACKUP_OPTION_CLASS =
|
||||
"flex min-h-48 w-full flex-col items-start justify-start px-6 py-5 text-left text-foreground";
|
||||
@@ -222,8 +229,8 @@ export function BackupStep({
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full max-w-140 shrink-0 flex-col",
|
||||
cardLayout ? "text-left" : "text-center",
|
||||
"mx-auto flex w-full shrink-0 flex-col",
|
||||
cardLayout ? "max-w-[500px] text-left" : "max-w-140 text-center",
|
||||
)}
|
||||
>
|
||||
<h1 className="text-title font-normal text-foreground">
|
||||
@@ -256,13 +263,13 @@ export function BackupStep({
|
||||
</div>
|
||||
|
||||
{v3Presentation ? (
|
||||
<div className="flex w-full max-w-[560px] flex-1 items-center py-10">
|
||||
<div className="mx-auto flex w-full max-w-[500px] flex-1 items-center py-10">
|
||||
<div
|
||||
className="w-full space-y-6"
|
||||
data-testid="onboarding-preview-key-guidance"
|
||||
>
|
||||
<div className="flex min-h-14 items-center gap-4 text-left">
|
||||
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-foreground/10 text-foreground/80">
|
||||
<span className={V3_KEY_GUIDANCE_ICON_CLASS}>
|
||||
<ShieldCheck className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<p className="text-base leading-6 text-foreground">
|
||||
@@ -270,7 +277,7 @@ export function BackupStep({
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex min-h-14 items-center gap-4 text-left">
|
||||
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-foreground/10 text-foreground/80">
|
||||
<span className={V3_KEY_GUIDANCE_ICON_CLASS}>
|
||||
<CircleSlash2 className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<p className="text-base leading-6 text-foreground">
|
||||
@@ -278,7 +285,7 @@ export function BackupStep({
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex min-h-14 items-center gap-4 text-left">
|
||||
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-foreground/10 text-foreground/80">
|
||||
<span className={V3_KEY_GUIDANCE_ICON_CLASS}>
|
||||
<HardDriveDownload className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<p className="text-base leading-6 text-foreground">
|
||||
@@ -486,117 +493,94 @@ export function BackupStep({
|
||||
className={cn("py-6", cardLayout ? "px-0" : "px-8")}
|
||||
variant="textured"
|
||||
>
|
||||
<div className="mx-auto flex w-full min-w-0 max-w-[832px] items-center gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
{canUseSpoilerParticles ? (
|
||||
<div
|
||||
className="buzz-onboarding-private-key-spoiler"
|
||||
data-testid="backup-key-spoiler"
|
||||
<div className="mx-auto w-full min-w-0 max-w-[832px]">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
{canUseSpoilerParticles ? (
|
||||
<div
|
||||
className="buzz-onboarding-private-key-spoiler"
|
||||
data-testid="backup-key-spoiler"
|
||||
>
|
||||
<SpoilerInline block revealOnHover>
|
||||
<p
|
||||
className={cn(
|
||||
ONBOARDING_KEY_TEXT_CLASS,
|
||||
v3Presentation && "buzz-onboarding-key-text-v3",
|
||||
"select-text",
|
||||
)}
|
||||
data-testid="backup-key-value"
|
||||
>
|
||||
{PREVIEW_KEY_TEXT}
|
||||
</p>
|
||||
</SpoilerInline>
|
||||
</div>
|
||||
) : (
|
||||
<p
|
||||
className={cn(
|
||||
ONBOARDING_KEY_TEXT_CLASS,
|
||||
v3Presentation && "buzz-onboarding-key-text-v3",
|
||||
isRevealed && nsec
|
||||
? "select-text"
|
||||
: "select-none blur-[4px]",
|
||||
)}
|
||||
data-testid="backup-key-value"
|
||||
>
|
||||
{isRevealed && nsec ? nsec : maskedKey}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{canUseSpoilerParticles ? null : (
|
||||
<Button
|
||||
aria-label={
|
||||
isRevealed ? "Hide private key" : "Reveal private key"
|
||||
}
|
||||
className="h-10 w-10 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
data-testid="backup-key-reveal-toggle"
|
||||
onClick={() => void toggleReveal()}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<SpoilerInline block revealOnHover>
|
||||
<p
|
||||
className={cn(
|
||||
ONBOARDING_KEY_TEXT_CLASS,
|
||||
"select-text",
|
||||
)}
|
||||
data-testid="backup-key-value"
|
||||
>
|
||||
{PREVIEW_KEY_TEXT}
|
||||
</p>
|
||||
</SpoilerInline>
|
||||
</div>
|
||||
) : (
|
||||
<p
|
||||
className={cn(
|
||||
ONBOARDING_KEY_TEXT_CLASS,
|
||||
isRevealed && nsec
|
||||
? "select-text"
|
||||
: "select-none blur-[4px]",
|
||||
{isRevealed ? (
|
||||
<EyeOff className="h-6 w-6" aria-hidden="true" />
|
||||
) : (
|
||||
<Eye className="h-6 w-6" aria-hidden="true" />
|
||||
)}
|
||||
data-testid="backup-key-value"
|
||||
>
|
||||
{isRevealed && nsec ? nsec : maskedKey}
|
||||
</p>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{canUseSpoilerParticles ? null : (
|
||||
<Button
|
||||
aria-label={
|
||||
isRevealed ? "Hide private key" : "Reveal private key"
|
||||
}
|
||||
className="h-10 w-10 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
data-testid="backup-key-reveal-toggle"
|
||||
onClick={() => void toggleReveal()}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{isRevealed ? (
|
||||
<EyeOff className="h-6 w-6" aria-hidden="true" />
|
||||
) : (
|
||||
<Eye className="h-6 w-6" aria-hidden="true" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{v3Presentation ? (
|
||||
<div className="mt-4 flex justify-start">
|
||||
<Button
|
||||
className={cn(
|
||||
ONBOARDING_SECONDARY_CTA_CLASS,
|
||||
"-ml-5 gap-2 bg-transparent px-5 text-foreground/80 hover:bg-[#e2e2e2]/50 hover:text-foreground",
|
||||
)}
|
||||
data-testid="backup-copy-key"
|
||||
disabled={copyState === "copying"}
|
||||
onClick={() => void copyKeyToClipboard()}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{copyState === "copying" ? (
|
||||
<Spinner className="h-4 w-4 border-2" />
|
||||
) : copyState === "copied" ? (
|
||||
<Check className="h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
{copyState === "copying"
|
||||
? "Copying…"
|
||||
: copyState === "copied"
|
||||
? "Copied to clipboard"
|
||||
: "Copy to clipboard"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{v3Presentation ? (
|
||||
<div
|
||||
className={cn(
|
||||
"mt-8 flex flex-wrap items-center gap-3",
|
||||
cardLayout ? "justify-start" : "justify-center",
|
||||
)}
|
||||
data-testid="backup-options"
|
||||
>
|
||||
<Button
|
||||
className={cn(
|
||||
ONBOARDING_SECONDARY_CTA_CLASS,
|
||||
cardLayout && ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS,
|
||||
"gap-2 px-5",
|
||||
)}
|
||||
data-testid="backup-copy-key"
|
||||
disabled={copyState === "copying"}
|
||||
onClick={() => void copyKeyToClipboard()}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{copyState === "copying" ? (
|
||||
<Spinner className="h-4 w-4 border-2" />
|
||||
) : copyState === "copied" ? (
|
||||
<Check className="h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
{copyState === "copying"
|
||||
? "Copying…"
|
||||
: copyState === "copied"
|
||||
? "Copied to clipboard"
|
||||
: "Copy to clipboard"}
|
||||
</Button>
|
||||
<Button
|
||||
className={cn(
|
||||
ONBOARDING_SECONDARY_CTA_CLASS,
|
||||
cardLayout && ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS,
|
||||
"gap-2 px-5",
|
||||
)}
|
||||
data-testid="backup-option-password"
|
||||
onClick={onOpenPasswordBackup}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{lockedBackupCreated ? (
|
||||
<Check className="h-5 w-5" aria-hidden="true" />
|
||||
) : (
|
||||
<ShieldCheck className="h-5 w-5" aria-hidden="true" />
|
||||
)}
|
||||
{lockedBackupCreated
|
||||
? "Created locked backup"
|
||||
: "Create locked backup"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{copyError ? (
|
||||
<p
|
||||
className="mt-4 text-center text-sm text-destructive"
|
||||
@@ -621,7 +605,24 @@ export function BackupStep({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<OnboardingFooter className={REVEAL_ANIMATION_CLASS}>
|
||||
<OnboardingFooter className={cn(REVEAL_ANIMATION_CLASS, "flex-nowrap")}>
|
||||
{v3Presentation && created ? (
|
||||
<Button
|
||||
className={cn(
|
||||
ONBOARDING_SECONDARY_CTA_CLASS,
|
||||
cardLayout && ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS,
|
||||
"px-4",
|
||||
)}
|
||||
data-testid="backup-option-password"
|
||||
onClick={onOpenPasswordBackup}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{lockedBackupCreated
|
||||
? "Created locked backup"
|
||||
: "Create locked backup"}
|
||||
</Button>
|
||||
) : null}
|
||||
{v3Presentation && created && showPreviewBackupShortcut ? (
|
||||
<Button
|
||||
className={cn(ONBOARDING_SECONDARY_CTA_CLASS, "gap-2")}
|
||||
|
||||
@@ -58,7 +58,6 @@ import {
|
||||
import { OnboardingPreviewControls } from "./OnboardingPreviewControls";
|
||||
import { OnboardingSlideTransition } from "./OnboardingSlideTransition";
|
||||
import { SetupStepPreview } from "./SetupStepPreview";
|
||||
import { ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS } from "./onboardingPreviewCardStyles";
|
||||
|
||||
const EMAIL_SIGNUP_POLICY: JoinPolicy = {
|
||||
ageAttestationRequired: true,
|
||||
@@ -335,30 +334,16 @@ function EmailSignIn({
|
||||
passwordId="onboarding-preview-sign-in-password"
|
||||
passwordPlaceholder="Enter your password"
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col gap-3 pt-1",
|
||||
cardLayout ? "items-start" : "items-center",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
aria-hidden
|
||||
className="flex w-full max-w-64 items-center gap-3 text-xs text-foreground/70"
|
||||
>
|
||||
<span className="h-px flex-1 bg-foreground/20" />
|
||||
<span>or</span>
|
||||
<span className="h-px flex-1 bg-foreground/20" />
|
||||
</div>
|
||||
<div className="flex w-full items-baseline gap-1.5 pt-1 text-sm text-foreground/70">
|
||||
<span>or</span>
|
||||
<Button
|
||||
className={cn(
|
||||
ONBOARDING_SECONDARY_CTA_CLASS,
|
||||
cardLayout && ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS,
|
||||
)}
|
||||
className="h-auto p-0 text-sm text-foreground"
|
||||
data-testid="onboarding-preview-sign-in-with-key"
|
||||
onClick={onSignInWithKey}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
variant="link"
|
||||
>
|
||||
Sign in with a key
|
||||
Sign in with private key
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -478,6 +463,7 @@ export function OnboardingPreviewApp() {
|
||||
} else if (page === "identity-key") {
|
||||
content = (
|
||||
<OnboardingPreviewStep
|
||||
allowHorizontalActionOverflow={variant === "v3"}
|
||||
onBack={() => setPage(variant === "v3" ? "backup-options" : "landing")}
|
||||
testId="onboarding-preview-identity-key"
|
||||
total={journey.totalSteps}
|
||||
@@ -540,7 +526,11 @@ export function OnboardingPreviewApp() {
|
||||
content = (
|
||||
<BackupPasswordPreview
|
||||
onBack={() => setPage("identity-key")}
|
||||
onDone={() => setPage("identity-key")}
|
||||
onDone={() =>
|
||||
variant === "v3"
|
||||
? continueFromAccount("identity-key")
|
||||
: setPage("identity-key")
|
||||
}
|
||||
session={backupSession}
|
||||
total={journey.totalSteps}
|
||||
v3Presentation={variant === "v3"}
|
||||
|
||||
@@ -238,12 +238,17 @@ export function PreviewCredentialsFields({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
className="mb-2 block text-sm font-medium text-foreground"
|
||||
htmlFor={passwordId}
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<div className="mb-2 flex items-center justify-between gap-3">
|
||||
<label
|
||||
className="block text-sm font-medium text-foreground"
|
||||
htmlFor={passwordId}
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
{passwordHelp ? (
|
||||
<div className="shrink-0 text-right">{passwordHelp}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<OnboardingPreviewInput
|
||||
aria-describedby={
|
||||
@@ -270,9 +275,6 @@ export function PreviewCredentialsFields({
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{passwordHelp ? (
|
||||
<div className="mt-2 text-left">{passwordHelp}</div>
|
||||
) : null}
|
||||
{onConfirmPasswordChange ? (
|
||||
<div className="mt-5">
|
||||
<label
|
||||
|
||||
@@ -29,6 +29,7 @@ export function useOnboardingPreviewCardLayout() {
|
||||
}
|
||||
|
||||
export function OnboardingPreviewStep({
|
||||
allowHorizontalActionOverflow = false,
|
||||
children,
|
||||
current = 2,
|
||||
onBack,
|
||||
@@ -36,6 +37,7 @@ export function OnboardingPreviewStep({
|
||||
testId,
|
||||
total,
|
||||
}: {
|
||||
allowHorizontalActionOverflow?: boolean;
|
||||
children: React.ReactNode;
|
||||
current?: number;
|
||||
onBack?: () => void;
|
||||
@@ -51,7 +53,12 @@ export function OnboardingPreviewStep({
|
||||
className={cn(
|
||||
"buzz-onboarding-step-frame relative flex w-full flex-col",
|
||||
cardLayout
|
||||
? "-mx-2 min-h-0 w-[calc(100%+1rem)] max-w-none flex-1 items-stretch overflow-hidden px-2 text-left"
|
||||
? cn(
|
||||
"min-h-0 max-w-none flex-1 items-stretch overflow-x-hidden overflow-y-auto overscroll-contain text-left",
|
||||
allowHorizontalActionOverflow
|
||||
? "-mx-6 w-[calc(100%+3rem)] px-6"
|
||||
: "-mx-2 w-[calc(100%+1rem)] px-2",
|
||||
)
|
||||
: "max-w-[1040px] flex-1 items-center text-center",
|
||||
)}
|
||||
>
|
||||
@@ -75,7 +82,7 @@ export function OnboardingPreviewStep({
|
||||
{cardLayout ? (
|
||||
<Card
|
||||
className={cn(
|
||||
"flex h-[min(41.5rem,calc(100dvh-3rem))] w-full max-w-[47rem] flex-col overflow-hidden rounded-[2rem] bg-white px-6 pb-6 pt-[4.5rem] text-left shadow-lg [--buzz-onboarding-cta-label:#fff] sm:px-[7.5rem] [&_.buzz-onboarding-transition-content]:!text-left [&_.buzz-onboarding-transition-line]:justify-start [&_h1+p]:!mx-0 [&_h1+p]:!mt-2 [&_h1+p]:!text-left [&_h1+p]:!text-base [&_h1+p]:!leading-6 [&_h1]:!text-left [&_h1]:!text-2xl [&_h1]:!leading-8 [&_h1]:!text-foreground",
|
||||
"flex h-[min(41.5rem,calc(100dvh-3rem))] w-full max-w-[47rem] flex-col overflow-hidden rounded-[2rem] bg-white px-6 pb-6 pt-[4.5rem] text-left shadow-lg [--buzz-onboarding-cta-label:#fff] sm:px-[7.5rem] [&_.buzz-onboarding-transition-content]:!text-left [&_.buzz-onboarding-transition-line]:justify-center [&_h1+p]:!mx-0 [&_h1+p]:!mt-2 [&_h1+p]:!text-left [&_h1+p]:!text-base [&_h1+p]:!leading-6 [&_h1]:!text-left [&_h1]:!text-2xl [&_h1]:!leading-8 [&_h1]:!text-foreground",
|
||||
)}
|
||||
data-testid="onboarding-preview-content-card"
|
||||
ref={cardRef}
|
||||
|
||||
@@ -24,7 +24,6 @@ import { ComposerDockBackdrop } from "@/features/messages/ui/ComposerDockBackdro
|
||||
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { BuzzMark } from "@/shared/ui/buzz-logo/BuzzMark";
|
||||
import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion";
|
||||
|
||||
const CHANNEL_ROW_CLASS =
|
||||
@@ -34,9 +33,6 @@ const WELCOME_TEAM = [
|
||||
{ name: "Honey", image: "/onboarding/starter-team/honey.png" },
|
||||
{ name: "Pollen", image: "/onboarding/starter-team/pollen.png" },
|
||||
] as const;
|
||||
const WELCOME_PREVIEW_MESSAGE =
|
||||
"Hi, I'm Fizz. Welcome to Buzz.\n\nI can help you get oriented, answer questions, and make the first few steps feel less mysterious.\n\nFeel free to ask me what else you can do in Buzz, or just talk through what you want to build.";
|
||||
|
||||
type PreviewThemeStyle = React.CSSProperties & Record<`--${string}`, string>;
|
||||
|
||||
const PREVIEW_APP_THEME: PreviewThemeStyle = {
|
||||
@@ -104,45 +100,6 @@ function PreviewAvatar({
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewCommunityRail({ communityName }: { communityName: string }) {
|
||||
return (
|
||||
<nav
|
||||
aria-label="Communities"
|
||||
className="flex w-14 shrink-0 flex-col items-center gap-2.5 px-2.5 pb-5 pt-12 text-black"
|
||||
>
|
||||
<button
|
||||
aria-label={communityName}
|
||||
className="relative flex size-9 items-center justify-center rounded-xl bg-black text-[#f4ef82]"
|
||||
type="button"
|
||||
>
|
||||
<span className="absolute -left-2.5 h-5 w-1 rounded-r-full bg-black" />
|
||||
<BuzzMark className="h-5 w-7" />
|
||||
</button>
|
||||
{WELCOME_TEAM.map((agent) => (
|
||||
<button
|
||||
aria-label={`${agent.name} community`}
|
||||
className="size-9 overflow-hidden rounded-xl bg-white/65"
|
||||
key={agent.name}
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
className="h-full w-full object-contain"
|
||||
src={agent.image}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
aria-label="Add community"
|
||||
className="flex size-9 items-center justify-center rounded-2xl bg-white/65 text-black/65 transition-[border-radius,background-color] hover:rounded-xl hover:bg-black hover:text-white"
|
||||
type="button"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewSidebar({
|
||||
avatarUrl,
|
||||
channelName,
|
||||
@@ -275,10 +232,11 @@ export function WelcomeChannelAppPreview({
|
||||
}) {
|
||||
const [draft, setDraft] = React.useState("");
|
||||
const [messages, setMessages] = React.useState<string[]>([]);
|
||||
const [channelName, setChannelName] = React.useState("Welcome");
|
||||
const [showRenamePrompt, setShowRenamePrompt] = React.useState(true);
|
||||
const [activePreviewAction, setActivePreviewAction] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
const channelName = "Welcome";
|
||||
const profileName = displayName.trim() || "Your profile";
|
||||
const submitDraft = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
@@ -321,7 +279,6 @@ export function WelcomeChannelAppPreview({
|
||||
style={PREVIEW_APP_THEME}
|
||||
>
|
||||
<StartupWindowDragRegion />
|
||||
<PreviewCommunityRail communityName={communityName} />
|
||||
<PreviewSidebar
|
||||
avatarUrl={avatarUrl}
|
||||
channelName={channelName}
|
||||
@@ -387,22 +344,98 @@ export function WelcomeChannelAppPreview({
|
||||
<div className="mx-auto w-full max-w-[920px]">
|
||||
<ChannelIntroBlock intro={intro} />
|
||||
|
||||
<div className="mt-8 flex items-start gap-3 px-3 text-left">
|
||||
<img
|
||||
alt="Fizz"
|
||||
className="size-9 rounded-lg object-contain"
|
||||
src="/onboarding/starter-team/fizz.png"
|
||||
/>
|
||||
<div className="min-w-0 max-w-[680px]">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<p className="text-sm font-semibold">Fizz</p>
|
||||
<p className="text-xs text-muted-foreground">Now</p>
|
||||
<section
|
||||
className="mx-3 mt-7 max-w-[680px] border-t border-border/60 pt-6 text-left"
|
||||
data-testid="welcome-preview-community-message"
|
||||
>
|
||||
<p className="text-lg font-semibold">
|
||||
Welcome to {communityName}
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Everyone starts with a private channel to get settled. This one
|
||||
is yours. Use it to try out features, draft messages, or work
|
||||
privately with agents.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{showRenamePrompt ? (
|
||||
<section
|
||||
className="mx-3 mt-6 flex max-w-[680px] items-center gap-4 rounded-2xl border border-border/70 bg-background/70 px-5 py-4 text-left"
|
||||
data-testid="welcome-preview-rename-prompt"
|
||||
>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
||||
<Lock className="size-4" aria-hidden />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold">Make this space yours</p>
|
||||
<p className="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
This private channel is yours to keep. Rename it for how you
|
||||
want to use it.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
className="h-9 shrink-0 rounded-full px-5"
|
||||
onClick={() => {
|
||||
setChannelName(
|
||||
profileName === "Your profile"
|
||||
? "My space"
|
||||
: `${profileName}’s space`,
|
||||
);
|
||||
setShowRenamePrompt(false);
|
||||
setActivePreviewAction("Channel details opened");
|
||||
}}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
Rename channel
|
||||
</Button>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section
|
||||
className="mx-3 mt-6 max-w-[680px] overflow-hidden rounded-2xl border border-border/70 bg-muted/35 text-left"
|
||||
data-testid="welcome-preview-agent-activation"
|
||||
>
|
||||
<div className="flex items-center gap-4 p-5">
|
||||
<div className="flex shrink-0 -space-x-3">
|
||||
{WELCOME_TEAM.map((agent) => (
|
||||
<img
|
||||
alt={agent.name}
|
||||
className="size-14 rounded-2xl border-2 border-background bg-background object-contain"
|
||||
key={agent.name}
|
||||
src={agent.image}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-base font-semibold">
|
||||
Bring your starter team online
|
||||
</p>
|
||||
<span className="rounded-full bg-foreground/[0.08] px-2 py-0.5 text-xs font-medium text-muted-foreground">
|
||||
Not connected
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1.5 text-sm leading-5 text-muted-foreground">
|
||||
Connect an AI provider to start Fizz, Honey, and Pollen.
|
||||
They can help you learn Buzz and work through something
|
||||
you’re building.
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-1 whitespace-pre-line text-sm leading-5">
|
||||
{WELCOME_PREVIEW_MESSAGE}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-border/60 bg-background/45 px-5 py-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
You can change providers or agents later.
|
||||
</p>
|
||||
<Button
|
||||
className="h-9 rounded-full px-5"
|
||||
onClick={() => setActivePreviewAction("Connect AI provider")}
|
||||
type="button"
|
||||
>
|
||||
Connect AI provider
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{messages.map((message, index) => (
|
||||
<div
|
||||
|
||||
@@ -2,5 +2,6 @@ export const ONBOARDING_PREVIEW_CARD_INPUT_CLASS =
|
||||
"h-12 rounded-xl border-[#e2e2e2] bg-[#f9f9f9] px-4 text-[oklch(0.22213_0_0)] shadow-none transition-[border-color,box-shadow] duration-200 ease-out placeholder:text-[oklch(0.6901_0_0)] placeholder:opacity-60 hover:border-[#d6d6d6] focus-visible:border-[#e2e2e2] focus-visible:ring-0 focus-visible:shadow-[0_0_0_3px_white,0_0_0_6px_rgba(0,0,0,0.06)] motion-reduce:transition-none";
|
||||
|
||||
/** Quiet V3 actions use the same translucent neutral treatment as Back. */
|
||||
export const ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS =
|
||||
"bg-[#e2e2e2]/30 text-foreground hover:bg-[#e2e2e2]/50 hover:text-foreground";
|
||||
export const ONBOARDING_PREVIEW_NEUTRAL_SURFACE_CLASS = "bg-[#e2e2e2]/30";
|
||||
|
||||
export const ONBOARDING_PREVIEW_SECONDARY_CTA_CLASS = `${ONBOARDING_PREVIEW_NEUTRAL_SURFACE_CLASS} text-foreground hover:bg-[#e2e2e2]/50 hover:text-foreground`;
|
||||
|
||||
@@ -391,6 +391,10 @@
|
||||
color: var(--buzz-onboarding-backup-ink);
|
||||
}
|
||||
|
||||
.buzz-onboarding-key-text-v3 {
|
||||
@apply max-w-[32ch] text-2xl font-medium text-foreground;
|
||||
}
|
||||
|
||||
.dark .buzz-onboarding-neutral-theme:not(.buzz-startup-shell) {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
Reference in New Issue
Block a user