fix(FN-2402): preserve onboarding stepper done state on completion
- Treat the completion view as an effective final step so all prior indicators/connectors remain marked done - Mark the first-task step as completed before tracking onboarding completion and clear it from skipped state - Replace hardcoded success colors in setup wizard stepper done styles with theme-aware done status tokens - Add regression assertions verifying all step indicators and connectors stay in done state on the completion screen
This commit is contained in:
@@ -614,8 +614,10 @@ export function ModelOnboardingModal({
|
|||||||
{ key: "first-task" as const, label: "First Task" },
|
{ key: "first-task" as const, label: "First Task" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Get current step index for progress indicator
|
// Get current step index for progress indicator.
|
||||||
|
// Keep the stepper in a fully-complete visual state when the completion screen is shown.
|
||||||
const currentStepIndex = steps.findIndex((s) => s.key === step);
|
const currentStepIndex = steps.findIndex((s) => s.key === step);
|
||||||
|
const effectiveStepIndex = step === "complete" ? steps.length : currentStepIndex;
|
||||||
|
|
||||||
// Persist step state whenever it changes (for resume functionality)
|
// Persist step state whenever it changes (for resume functionality)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1327,10 +1329,14 @@ export function ModelOnboardingModal({
|
|||||||
|
|
||||||
// Complete onboarding
|
// Complete onboarding
|
||||||
const handleComplete = useCallback(async () => {
|
const handleComplete = useCallback(async () => {
|
||||||
|
const nextCompletedSteps = [...new Set([...completedSteps, "first-task"])] as OnboardingStep[];
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await completeOnboarding();
|
await completeOnboarding();
|
||||||
trackOnboardingEvent("onboarding:completed", { completedSteps, skippedSteps });
|
trackOnboardingEvent("onboarding:completed", { completedSteps: nextCompletedSteps, skippedSteps });
|
||||||
|
setCompletedSteps(nextCompletedSteps);
|
||||||
|
setSkippedSteps((prev) => prev.filter((s) => s !== "first-task"));
|
||||||
setStep("complete");
|
setStep("complete");
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
@@ -1757,9 +1763,10 @@ export function ModelOnboardingModal({
|
|||||||
{/* Step indicator - 3 progress steps + complete */}
|
{/* Step indicator - 3 progress steps + complete */}
|
||||||
<div className="model-onboarding-steps">
|
<div className="model-onboarding-steps">
|
||||||
{steps.map((s, index) => {
|
{steps.map((s, index) => {
|
||||||
// A step is done if it's in completedSteps AND is before current position
|
// A step is done/skipped only once we have progressed beyond it.
|
||||||
const isDone = completedSteps.includes(s.key) && currentStepIndex > index;
|
const hasProgressedPastStep = effectiveStepIndex > index;
|
||||||
const isSkipped = skippedSteps.includes(s.key) && !completedSteps.includes(s.key) && currentStepIndex > index;
|
const isDone = completedSteps.includes(s.key) && hasProgressedPastStep;
|
||||||
|
const isSkipped = skippedSteps.includes(s.key) && !completedSteps.includes(s.key) && hasProgressedPastStep;
|
||||||
// Clickable if it's a completed/skipped step (can review)
|
// Clickable if it's a completed/skipped step (can review)
|
||||||
const isClickable = isDone || isSkipped;
|
const isClickable = isDone || isSkipped;
|
||||||
return (
|
return (
|
||||||
@@ -1767,7 +1774,7 @@ export function ModelOnboardingModal({
|
|||||||
{index > 0 && (
|
{index > 0 && (
|
||||||
<div
|
<div
|
||||||
className={`model-onboarding-step-connector ${
|
className={`model-onboarding-step-connector ${
|
||||||
index <= currentStepIndex ? "done" : ""
|
index <= effectiveStepIndex ? "done" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -565,8 +565,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.model-onboarding-step-indicator.done .step-number {
|
.model-onboarding-step-indicator.done .step-number {
|
||||||
background: var(--success, #22c55e);
|
background: var(--status-done-bg-deep);
|
||||||
color: #fff;
|
border: 1px solid color-mix(in srgb, var(--done) 45%, transparent);
|
||||||
|
color: var(--done);
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-onboarding-step-indicator.done {
|
.model-onboarding-step-indicator.done {
|
||||||
@@ -618,7 +619,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.model-onboarding-step-connector.done {
|
.model-onboarding-step-connector.done {
|
||||||
background: var(--success, #22c55e);
|
background: var(--done);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content area */
|
/* Content area */
|
||||||
|
|||||||
@@ -2094,6 +2094,15 @@ describe("ModelOnboardingModal", () => {
|
|||||||
expect(screen.getByText("All Set!")).toBeTruthy();
|
expect(screen.getByText("All Set!")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Completed indicators should remain in done state on the completion screen.
|
||||||
|
const aiSetupIndicator = screen.getByRole("button", { name: "Go back to AI Setup" });
|
||||||
|
const githubIndicator = screen.getByRole("button", { name: "Go back to GitHub" });
|
||||||
|
const firstTaskIndicator = screen.getByRole("button", { name: "Go back to First Task" });
|
||||||
|
expect(aiSetupIndicator).toHaveClass("done");
|
||||||
|
expect(githubIndicator).toHaveClass("done");
|
||||||
|
expect(firstTaskIndicator).toHaveClass("done");
|
||||||
|
expect(document.querySelectorAll(".model-onboarding-step-connector.done")).toHaveLength(2);
|
||||||
|
|
||||||
// Click Get Started to close
|
// Click Get Started to close
|
||||||
fireEvent.click(screen.getByText("Get Started"));
|
fireEvent.click(screen.getByText("Get Started"));
|
||||||
expect(onComplete).toHaveBeenCalled();
|
expect(onComplete).toHaveBeenCalled();
|
||||||
|
|||||||
Reference in New Issue
Block a user