diff --git a/apps/web/src/app/(onboarding)/setup/hooks.ts b/apps/web/src/app/(onboarding)/setup/hooks.ts index 14e6a0624..84816c329 100644 --- a/apps/web/src/app/(onboarding)/setup/hooks.ts +++ b/apps/web/src/app/(onboarding)/setup/hooks.ts @@ -12,7 +12,12 @@ import { import { useTRPC } from '@/trpc/client'; import { useUser } from '@/hooks/useUser'; -import { SETUP_STEPS, getSetupPath, type SetupStep } from './types'; +import { + SETUP_STEPS, + getSetupPath, + getSetupSteps, + type SetupStep, +} from './types'; import { useSetupAsyncSession } from './setup-session'; import { hasSeenSetupWelcome } from './welcome-seen'; @@ -66,22 +71,6 @@ const DEEP_LINK_REVISITABLE_SETUP_STEPS: readonly SetupStep[] = [ 'compute-config', ]; -const EMAIL_PASSWORD_SETUP_STEPS: readonly SetupStep[] = [ - 'welcome', - 'env-vars', - 'source-control-provider', - 'source-control-config', - 'source-control-connect', - 'auth-provider', - 'auth-env-vars', - 'slack', - 'automation-recommendations', - 'compute-provider', - 'compute-config', - 'repo-selection', - 'invoke', -]; - function readUrlEntryContext(): SetupEntryContext { if (typeof window === 'undefined') { return { @@ -295,10 +284,9 @@ export function useSetupFlow( const setupSession = useSetupAsyncSession({ currentTaskId: status?.setupNewState.onboardingTaskId ?? null, }); - const setupSteps = - (pendingAuthProvider ?? status?.setupNewState.authProvider) - ? SETUP_STEPS - : EMAIL_PASSWORD_SETUP_STEPS; + const setupSteps = getSetupSteps( + Boolean(pendingAuthProvider ?? status?.setupNewState.authProvider), + ); stepRef.current = step; const setStepWithTransition = useCallback( diff --git a/apps/web/src/app/(onboarding)/setup/types.test.ts b/apps/web/src/app/(onboarding)/setup/types.test.ts new file mode 100644 index 000000000..a8f0f2006 --- /dev/null +++ b/apps/web/src/app/(onboarding)/setup/types.test.ts @@ -0,0 +1,29 @@ +import { SETUP_STEPS, getSetupSteps } from './types'; + +describe('getSetupSteps', () => { + it('derives email/password ordering without changing the canonical step set', () => { + const emailPasswordSteps = getSetupSteps(false); + + expect(emailPasswordSteps).toHaveLength(SETUP_STEPS.length); + expect(new Set(emailPasswordSteps)).toEqual(new Set(SETUP_STEPS)); + expect(emailPasswordSteps).toEqual([ + 'welcome', + 'env-vars', + 'source-control-provider', + 'source-control-config', + 'source-control-connect', + 'auth-provider', + 'auth-env-vars', + 'slack', + 'automation-recommendations', + 'compute-provider', + 'compute-config', + 'repo-selection', + 'invoke', + ]); + }); + + it('uses the canonical order when communication handled authentication', () => { + expect(getSetupSteps(true)).toBe(SETUP_STEPS); + }); +}); diff --git a/apps/web/src/app/(onboarding)/setup/types.ts b/apps/web/src/app/(onboarding)/setup/types.ts index 9127cd922..009c435b8 100644 --- a/apps/web/src/app/(onboarding)/setup/types.ts +++ b/apps/web/src/app/(onboarding)/setup/types.ts @@ -64,10 +64,40 @@ type SetupStepDefinition = (typeof SETUP_STEP_DEFINITIONS)[number]; export type SetupStep = SetupStepDefinition['id']; -export const SETUP_STEPS: SetupStep[] = SETUP_STEP_DEFINITIONS.map( +export const SETUP_STEPS: readonly SetupStep[] = SETUP_STEP_DEFINITIONS.map( (definition) => definition.id, ); +const EMAIL_PASSWORD_SETUP_ORDER_POLICY = { + move: ['auth-provider', 'auth-env-vars', 'slack'], + after: 'source-control-connect', +} as const satisfies { + move: readonly SetupStep[]; + after: SetupStep; +}; + +const EMAIL_PASSWORD_MOVED_SETUP_STEPS = new Set( + EMAIL_PASSWORD_SETUP_ORDER_POLICY.move, +); + +const EMAIL_PASSWORD_SETUP_STEPS: readonly SetupStep[] = SETUP_STEPS.flatMap( + (step) => { + if (step === EMAIL_PASSWORD_SETUP_ORDER_POLICY.after) { + return [step, ...EMAIL_PASSWORD_SETUP_ORDER_POLICY.move]; + } + + return EMAIL_PASSWORD_MOVED_SETUP_STEPS.has(step) ? [] : [step]; + }, +); + +export function getSetupSteps( + hasCommunicationAuthProvider: boolean, +): readonly SetupStep[] { + return hasCommunicationAuthProvider + ? SETUP_STEPS + : EMAIL_PASSWORD_SETUP_STEPS; +} + const SETUP_STEP_DEFINITION_MAP = Object.fromEntries( SETUP_STEP_DEFINITIONS.map((definition) => [definition.id, definition]), ) as {