## 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
69 lines
2.5 KiB
TypeScript
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";
|
|
}
|