fix(FN-2704): gate auth onboarding behind setup wizard
- Prevent useAuthOnboarding from opening model onboarding while the setup wizard is active - Add app-level wiring so onboarding flow respects setup wizard visibility state - Add regression tests covering setup wizard gating and non-stacking onboarding behavior
This commit is contained in:
@@ -393,6 +393,7 @@ function AppInner() {
|
||||
// Auth and onboarding bootstrap logic extracted to a dedicated hook.
|
||||
useAuthOnboarding({
|
||||
projectId: currentProject?.id,
|
||||
setupWizardOpen: modalManager.setupWizardOpen,
|
||||
openModelOnboarding: modalManager.openModelOnboarding,
|
||||
openSettings: modalManager.openSettings,
|
||||
});
|
||||
|
||||
@@ -49,6 +49,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -76,6 +77,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -101,6 +103,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -127,6 +130,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -152,6 +156,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -173,6 +178,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -187,6 +193,66 @@ describe("useAuthOnboarding", () => {
|
||||
expect(mockFetchGlobalSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not auto-trigger model onboarding when setupWizardOpen is true", async () => {
|
||||
mockFetchAuthStatus.mockResolvedValue({
|
||||
providers: [{ id: "openai", name: "OpenAI", authenticated: false }],
|
||||
});
|
||||
mockFetchGlobalSettings.mockResolvedValue({
|
||||
modelOnboardingComplete: false,
|
||||
defaultProvider: undefined,
|
||||
defaultModelId: undefined,
|
||||
} as never);
|
||||
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: true,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openModelOnboarding).not.toHaveBeenCalled();
|
||||
expect(mockFetchAuthStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("auto-triggers model onboarding when setupWizardOpen transitions from true to false", async () => {
|
||||
mockFetchAuthStatus.mockResolvedValue({
|
||||
providers: [{ id: "openai", name: "OpenAI", authenticated: false }],
|
||||
});
|
||||
mockFetchGlobalSettings.mockResolvedValue({
|
||||
modelOnboardingComplete: false,
|
||||
defaultProvider: undefined,
|
||||
defaultModelId: undefined,
|
||||
} as never);
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ setupWizardOpen }: { setupWizardOpen: boolean }) =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
{
|
||||
initialProps: { setupWizardOpen: true },
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openModelOnboarding).not.toHaveBeenCalled();
|
||||
expect(mockFetchAuthStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
rerender({ setupWizardOpen: false });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openModelOnboarding).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
// --- One-shot guard ---
|
||||
|
||||
it("does not re-trigger onboarding when projectId changes after initial bootstrap", async () => {
|
||||
@@ -204,6 +270,7 @@ describe("useAuthOnboarding", () => {
|
||||
({ projectId }: { projectId: string }) =>
|
||||
useAuthOnboarding({
|
||||
projectId,
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -238,6 +305,7 @@ describe("useAuthOnboarding", () => {
|
||||
({ open }: { open: () => void }) =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding: open,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -273,6 +341,7 @@ describe("useAuthOnboarding", () => {
|
||||
({ open }: { open: (section?: string) => void }) =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings: open,
|
||||
}),
|
||||
@@ -311,6 +380,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
@@ -341,6 +411,7 @@ describe("useAuthOnboarding", () => {
|
||||
renderHook(() =>
|
||||
useAuthOnboarding({
|
||||
projectId: "proj_123",
|
||||
setupWizardOpen: false,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}),
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SectionId } from "../components/SettingsModal";
|
||||
|
||||
export interface UseAuthOnboardingOptions {
|
||||
projectId?: string;
|
||||
setupWizardOpen: boolean;
|
||||
openModelOnboarding: () => void;
|
||||
openSettings: (section?: SectionId) => void;
|
||||
}
|
||||
@@ -24,6 +25,7 @@ export interface UseAuthOnboardingOptions {
|
||||
*/
|
||||
export function useAuthOnboarding({
|
||||
projectId,
|
||||
setupWizardOpen,
|
||||
openModelOnboarding,
|
||||
openSettings,
|
||||
}: UseAuthOnboardingOptions): void {
|
||||
@@ -32,6 +34,9 @@ export function useAuthOnboarding({
|
||||
const hasTriggeredRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Defer auto-triggering while setup wizard is open.
|
||||
// Important: this must run before consuming the one-shot flag.
|
||||
if (setupWizardOpen) return;
|
||||
// Skip if we've already triggered (one-shot guard)
|
||||
if (hasTriggeredRef.current) return;
|
||||
// Mark as triggered immediately to prevent any race condition on re-runs
|
||||
@@ -82,5 +87,5 @@ export function useAuthOnboarding({
|
||||
// Fail silently - non-blocking behavior preserves dashboard usability.
|
||||
// Onboarding can be manually triggered later via Settings if needed.
|
||||
});
|
||||
}, [projectId, openModelOnboarding, openSettings]);
|
||||
}, [projectId, setupWizardOpen, openModelOnboarding, openSettings]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user