* 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>
75 lines
2.8 KiB
TypeScript
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";
|
|
}
|