Files
calendar/packages/features/auth/lib/onboardingUtils.ts
T
sean-brydonandGitHub 6cbe1c1cf1 chore: Implement onboarding redirect logic in event-types and main page components (#24785)
## What does this PR do?

Currently we load /event-types for a few seconds while the onboarding hook catches up. This adds that logic to the serverside and not just client side to ensure onboarding is triggered

Adds server-side onboarding redirect checks to prevent users from accessing event types pages before completing onboarding. This implementation:

- Creates a new `checkOnboardingRedirect` utility function in a dedicated file
- Applies the redirect check on both the root page and event-types page
- Optimizes performance by using organizationId from session when available
- Handles email verification requirements before redirecting to onboarding
- Supports both legacy and v3 onboarding paths based on feature flags

## How should this be tested?

- Create a new user account that hasn't completed onboarding
- Attempt to access the root page or event-types page directly
- Verify you're redirected to the appropriate onboarding flow
- Test with email verification feature flag enabled/disabled
- Test with onboarding-v3 feature flag enabled/disabled
- Verify users who have completed onboarding can access event-types normally
- Verify organization users aren't redirected to onboarding

## Video Demo

Before:

[CleanShot 2025-10-30 at 10.54.41.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/user-attachments/thumbnails/8282decc-a00d-4bc8-9215-fe1c4809fd8f.mp4" />](https://app.graphite.dev/user-attachments/video/8282decc-a00d-4bc8-9215-fe1c4809fd8f.mp4)

After

## [CleanShot 2025-10-30 at 10.54.06.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/user-attachments/thumbnails/2acc7601-6c8c-496a-af4d-08dadef9aa2d.mp4" />](https://app.graphite.dev/user-attachments/video/2acc7601-6c8c-496a-af4d-08dadef9aa2d.mp4)

## Checklist

- [x] I have self-reviewed the code
- [x] I have updated the developer docs in /docs if this PR makes changes that would require a documentation change
- [x] I confirm automated tests are in place that prove my fix is effective or that my feature works
2025-10-30 13:44:11 +00:00

69 lines
2.5 KiB
TypeScript

import dayjs from "@calcom/dayjs";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import { prisma } from "@calcom/prisma";
const ONBOARDING_INTRODUCED_AT = dayjs("September 1 2021").toISOString();
/**
* Checks if a user needs onboarding on the server side.
* Returns the onboarding path if redirect is needed, null otherwise.
*
* @param userId - The user ID to check
* @param options - Optional configuration
* @param options.checkEmailVerification - Whether to check if email verification is required
* @param options.organizationId - Optional organizationId from session (to avoid extra query)
*/
export async function checkOnboardingRedirect(
userId: number,
options?: {
checkEmailVerification?: boolean;
organizationId?: number | null;
}
): Promise<string | null> {
// Query user data needed for onboarding check using UserRepository
const userRepository = new UserRepository(prisma);
const user = await userRepository.findById({ id: userId });
if (!user) {
return null;
}
// Use provided organizationId or query it via ProfileRepository
let organizationId: number | null;
if (options?.organizationId !== undefined) {
organizationId = options.organizationId;
} else {
const profile = await ProfileRepository.findFirstForUserId({ userId });
organizationId = profile?.organizationId ?? null;
}
// Check if user should be shown onboarding
const shouldShowOnboarding =
!user.completedOnboarding && !organizationId && dayjs(user.createdDate).isAfter(ONBOARDING_INTRODUCED_AT);
if (!shouldShowOnboarding) {
return null;
}
// Check email verification if needed
const featuresRepository = new FeaturesRepository(prisma);
if (options?.checkEmailVerification) {
const emailVerificationEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally(
"email-verification"
);
if (!user.emailVerified && user.identityProvider === "CAL" && emailVerificationEnabled) {
// User needs email verification, redirect to verification page
return "/auth/verify-email";
}
}
// Determine which onboarding path to use
const onboardingV3Enabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("onboarding-v3");
return onboardingV3Enabled ? "/onboarding/getting-started" : "/getting-started";
}