Files
calendar/packages/features/auth/lib/onboardingUtils.ts
T
sean-brydonGitHubsean@cal.com <Sean@brydon.io>sean@cal.com <Sean@brydon.io>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
38ed00d71a fix: add server-side redirect for users with pending invites on onboarding page (#26829)
* fix: add server-side redirect for users with pending invites on onboarding page

When users sign up with an invite token, they were being redirected to
/onboarding/getting-started (plan selection page) instead of going
directly to /onboarding/personal/settings. This caused users to see
the payment prompt for team/org plans before being redirected.

This fix adds a server-side check in the /onboarding/getting-started
page to redirect users with pending invites directly to the personal
onboarding flow, preventing them from seeing the plan selection page.

Also refactored onboardingUtils.ts to use MembershipRepository instead
of direct prisma access.

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* test: add integration tests for hasPendingInviteByUserId method

Co-Authored-By: sean@cal.com <Sean@brydon.io>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-14 09:22:05 -03:00

75 lines
2.8 KiB
TypeScript

import dayjs from "@calcom/dayjs";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
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");
const hasPendingInvite = await MembershipRepository.hasPendingInviteByUserId({ userId });
if (hasPendingInvite && onboardingV3Enabled) {
return "/onboarding/personal/settings";
}
return onboardingV3Enabled ? "/onboarding/getting-started" : "/getting-started";
}