onboarding: extract OnboardingStep shell (P1-11); eliminate 6 copies of motion+header chrome
This commit is contained in:
1 parent
3eb5ed1069
commit
1b11b53f84
6 files changed
+68
-95
No files matched your search
@@ -1,8 +1,7 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Buildings, FolderOpen } from "@phosphor-icons/react";
|
||||
import type { ConnectorId } from "@shared/types";
|
||||
import { ActionButton, Input, TextArea, Select } from "../ui";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { OnboardingStep } from "./OnboardingStep";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface CompanyStepProps {
|
||||
@@ -32,22 +31,11 @@ export function CompanyStep({ onContinue, onBack }: CompanyStepProps) {
|
||||
} = useOnboarding();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="company"
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -18 }}
|
||||
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="bg-card border border-border shadow-sm w-full rounded-lg p-7"
|
||||
<OnboardingStep
|
||||
step="company"
|
||||
eyebrow={t("onboarding.stepLabelCompany")}
|
||||
title={t("onboarding.step1Title")}
|
||||
>
|
||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{t("onboarding.stepLabelCompany")} {getStepNumber("company")} / {TOTAL_STEPS}
|
||||
</div>
|
||||
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{t("onboarding.step1Title")}
|
||||
</div>
|
||||
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
||||
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
label={t("onboarding.companyName")}
|
||||
@@ -139,6 +127,6 @@ export function CompanyStep({ onContinue, onBack }: CompanyStepProps) {
|
||||
disabled={!companyName.trim() || !selectedConnectorId}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</OnboardingStep>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, Brain, UserCircle, type Icon } from "@phosphor-icons/react";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { OnboardingStep } from "./OnboardingStep";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface ExecutionModeStepProps {
|
||||
@@ -12,22 +11,11 @@ export function ExecutionModeStep({ onContinue, onBack }: ExecutionModeStepProps
|
||||
const { executionMode, setExecutionMode, t } = useOnboarding();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="executionmode"
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -18 }}
|
||||
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="bg-card border border-border shadow-sm w-full rounded-lg p-7"
|
||||
<OnboardingStep
|
||||
step="executionmode"
|
||||
eyebrow={t("onboarding.stepLabelMode")}
|
||||
title={t("onboarding.modeStepTitle")}
|
||||
>
|
||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{t("onboarding.stepLabelMode")} {getStepNumber("executionmode")} / {TOTAL_STEPS}
|
||||
</div>
|
||||
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{t("onboarding.modeStepTitle")}
|
||||
</div>
|
||||
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
||||
|
||||
<div className="space-y-3">
|
||||
<ModeCard
|
||||
selected={executionMode === "ai_ceo"}
|
||||
@@ -68,7 +56,7 @@ export function ExecutionModeStep({ onContinue, onBack }: ExecutionModeStepProps
|
||||
<ArrowRight size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</OnboardingStep>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { ActionButton } from "../ui";
|
||||
import { ModelToggleList, StatusBadge } from "../execution/ModelManagementShared";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { OnboardingStep } from "./OnboardingStep";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface ModelPoolStepProps {
|
||||
@@ -31,25 +30,12 @@ export function ModelPoolStep({ onBack, onContinue }: ModelPoolStepProps) {
|
||||
: t("onboarding.modelPoolContinueHint");
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="modelpool"
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -18 }}
|
||||
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="w-full rounded-lg border border-border bg-card p-7 shadow-sm"
|
||||
<OnboardingStep
|
||||
step="modelpool"
|
||||
eyebrow={t("onboarding.stepLabelModelPool")}
|
||||
title={t("onboarding.modelPoolStepTitle")}
|
||||
subtitle={t("onboarding.modelPoolStepSubtitle")}
|
||||
>
|
||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{t("onboarding.stepLabelModelPool")} {getStepNumber("modelpool")} / {TOTAL_STEPS}
|
||||
</div>
|
||||
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{t("onboarding.modelPoolStepTitle")}
|
||||
</div>
|
||||
<div className="mb-3 text-[13px] leading-relaxed text-muted-foreground">
|
||||
{t("onboarding.modelPoolStepSubtitle")}
|
||||
</div>
|
||||
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
||||
|
||||
<div className="mb-4 rounded-lg border border-border bg-background/70 px-4 py-3 text-[12px] leading-relaxed text-muted-foreground">
|
||||
{t("onboarding.modelPoolStepDescription")}
|
||||
</div>
|
||||
@@ -113,6 +99,6 @@ export function ModelPoolStep({ onBack, onContinue }: ModelPoolStepProps) {
|
||||
disabled={!canContinue}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</OnboardingStep>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
@@ -8,7 +7,7 @@ import {
|
||||
} from "@phosphor-icons/react";
|
||||
import { ActionButton } from "../ui";
|
||||
import type { TerminalCommandKey } from "@shared/contracts";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { OnboardingStep } from "./OnboardingStep";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface NodeCheckStepProps {
|
||||
@@ -50,22 +49,11 @@ export function NodeCheckStep({ onContinue, onBack }: NodeCheckStepProps) {
|
||||
const platformInfo = INSTALL_COMMANDS[platform] ?? INSTALL_COMMANDS.linux;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="nodecheck"
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -18 }}
|
||||
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="bg-card border border-border shadow-sm w-full rounded-lg p-7"
|
||||
<OnboardingStep
|
||||
step="nodecheck"
|
||||
eyebrow={`${t("onboarding.stepLabel1")} — ${t("onboarding.nodeCheckStep")}`}
|
||||
title={t("onboarding.nodeCheckTitle")}
|
||||
>
|
||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{t("onboarding.stepLabel1")} {getStepNumber("nodecheck")} / {TOTAL_STEPS} — {t("onboarding.nodeCheckStep")}
|
||||
</div>
|
||||
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{t("onboarding.nodeCheckTitle")}
|
||||
</div>
|
||||
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
||||
|
||||
{/* Status */}
|
||||
<div className="mb-5 flex items-center gap-2.5 text-[13px]">
|
||||
{nodeChecking ? (
|
||||
@@ -132,6 +120,6 @@ export function NodeCheckStep({ onContinue, onBack }: NodeCheckStepProps) {
|
||||
disabled={!nodeStatus?.installed}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</OnboardingStep>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { motion } from "framer-motion";
|
||||
import type { ReactNode } from "react";
|
||||
import { getStepNumber, TOTAL_STEPS, type OnboardingStepName } from "./OnboardingHero";
|
||||
|
||||
interface OnboardingStepProps {
|
||||
step: OnboardingStepName;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
subtitle?: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function OnboardingStep({ step, eyebrow, title, subtitle, children }: OnboardingStepProps) {
|
||||
return (
|
||||
<motion.div
|
||||
key={step}
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -18 }}
|
||||
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="bg-card border border-border shadow-sm w-full rounded-lg p-7"
|
||||
>
|
||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{eyebrow} {getStepNumber(step)} / {TOTAL_STEPS}
|
||||
</div>
|
||||
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{title}
|
||||
</div>
|
||||
{subtitle ? (
|
||||
<div className="mb-4 text-[13px] leading-relaxed text-muted-foreground">{subtitle}</div>
|
||||
) : null}
|
||||
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, FloppyDisk, SignIn } from "@phosphor-icons/react";
|
||||
import { ActionButton } from "../ui";
|
||||
import {
|
||||
@@ -9,7 +8,7 @@ import {
|
||||
StatusBadge,
|
||||
INPUT_CLS,
|
||||
} from "./ProvidersStepShared";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { OnboardingStep } from "./OnboardingStep";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -87,24 +86,12 @@ export function ProvidersStep({ onContinue, onBack }: ProvidersStepProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="providers"
|
||||
initial={{ opacity: 0, y: 18 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -18 }}
|
||||
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="w-full rounded-lg border border-border bg-card p-7 shadow-sm"
|
||||
<OnboardingStep
|
||||
step="providers"
|
||||
eyebrow={t("onboarding.stepLabelProviders")}
|
||||
title={t("onboarding.providersTitle")}
|
||||
subtitle={t("onboarding.providersSubtitle")}
|
||||
>
|
||||
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{t("onboarding.stepLabelProviders")} {getStepNumber("providers")} / {TOTAL_STEPS}
|
||||
</div>
|
||||
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{t("onboarding.providersTitle")}
|
||||
</div>
|
||||
<div className="mb-4 text-[13px] leading-relaxed text-muted-foreground">
|
||||
{t("onboarding.providersSubtitle")}
|
||||
</div>
|
||||
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* ---- Card 1: Claude Code (Official) ---- */}
|
||||
@@ -281,6 +268,6 @@ export function ProvidersStep({ onContinue, onBack }: ProvidersStepProps) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</OnboardingStep>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user