diff --git a/apps/web/lib/pages/auth/verify-email.ts b/apps/web/lib/pages/auth/verify-email.ts index f204688889..720f1b09b9 100644 --- a/apps/web/lib/pages/auth/verify-email.ts +++ b/apps/web/lib/pages/auth/verify-email.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import dayjs from "@calcom/dayjs"; import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository"; import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service"; +import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboarding-path.service"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { IS_STRIPE_ENABLED } from "@calcom/lib/constants"; import { prisma } from "@calcom/prisma"; @@ -172,7 +173,9 @@ export async function handler(req: NextApiRequest, res: NextApiResponse) { await moveUserToMatchingOrg({ email: user.email }); - return res.redirect(`${WEBAPP_URL}/${hasCompletedOnboarding ? "/event-types" : "/getting-started"}`); + const gettingStartedPath = await OnboardingPathService.getGettingStartedPath(prisma); + + return res.redirect(`${WEBAPP_URL}${hasCompletedOnboarding ? "/event-types" : gettingStartedPath}`); } export async function cleanUpVerificationTokens(id: number) { diff --git a/apps/web/modules/auth/verify-email-view.tsx b/apps/web/modules/auth/verify-email-view.tsx index 5ce1474139..a78ca6c41c 100644 --- a/apps/web/modules/auth/verify-email-view.tsx +++ b/apps/web/modules/auth/verify-email-view.tsx @@ -11,6 +11,7 @@ import useEmailVerifyCheck from "@calcom/trpc/react/hooks/useEmailVerifyCheck"; import { showToast } from "@calcom/ui/components/toast"; import { Button } from "@calcom/ui/components/button"; import { EmptyScreen } from "@calcom/ui/components/empty-screen"; +import { useFlagMap } from "@calcom/features/flags/context/provider"; function VerifyEmailPage() { const { data } = useEmailVerifyCheck(); @@ -18,13 +19,15 @@ function VerifyEmailPage() { const router = useRouter(); const { t, isLocaleReady } = useLocale(); const mutation = trpc.viewer.auth.resendVerifyEmail.useMutation(); + const flags = useFlagMap(); useEffect(() => { if (data?.isVerified) { - router.replace("/getting-started"); + const gettingStartedPath = flags["onboarding-v3"] ? "/onboarding/getting-started" : "/getting-started"; + router.replace(gettingStartedPath); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data?.isVerified]); + }, [data?.isVerified, flags]); if (!isLocaleReady) { return null; } diff --git a/apps/web/modules/onboarding/hooks/useCreateTeam.ts b/apps/web/modules/onboarding/hooks/useCreateTeam.ts index 1b89fa0062..85006b05fa 100644 --- a/apps/web/modules/onboarding/hooks/useCreateTeam.ts +++ b/apps/web/modules/onboarding/hooks/useCreateTeam.ts @@ -1,6 +1,7 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; +import { useFlagMap } from "@calcom/features/flags/context/provider"; import { trpc } from "@calcom/trpc/react"; import type { OnboardingState } from "../store/onboarding-store"; @@ -8,6 +9,7 @@ import type { OnboardingState } from "../store/onboarding-store"; export function useCreateTeam() { const router = useRouter(); const [isSubmitting, setIsSubmitting] = useState(false); + const flags = useFlagMap(); const createTeamMutation = trpc.viewer.teams.create.useMutation(); @@ -31,7 +33,11 @@ export function useCreateTeam() { } if (result.team) { - router.push("/getting-started"); + // Not sure we need this flag check - keeping it here for safe keeping as this is called only from v3 onboarding flow + const gettingStartedPath = flags["onboarding-v3"] + ? "/onboarding/getting-started" + : "/getting-started"; + router.push(gettingStartedPath); } } catch (error) { console.error("Failed to create team:", error); diff --git a/apps/web/modules/onboarding/hooks/useSubmitOnboarding.ts b/apps/web/modules/onboarding/hooks/useSubmitOnboarding.ts index 5f6c8b701c..f88b02c74d 100644 --- a/apps/web/modules/onboarding/hooks/useSubmitOnboarding.ts +++ b/apps/web/modules/onboarding/hooks/useSubmitOnboarding.ts @@ -4,6 +4,7 @@ import { useState } from "react"; import { CreationSource } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import { showToast } from "@calcom/ui/components/toast"; +import { useFlagMap } from "@calcom/features/flags/context/provider"; import type { OnboardingState } from "../store/onboarding-store"; @@ -11,6 +12,7 @@ export const useSubmitOnboarding = () => { const router = useRouter(); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); + const flags = useFlagMap(); const intentToCreateOrg = trpc.viewer.organizations.intentToCreateOrg.useMutation(); @@ -80,7 +82,8 @@ export const useSubmitOnboarding = () => { showToast("Organization created successfully!", "success"); // TODO: after this redirect we need to hard refresh the page to see org resetOnboarding(); - router.push("/getting-started"); + const gettingStartedPath = flags["onboarding-v3"] ? "/onboarding/getting-started" : "/getting-started"; + router.push(gettingStartedPath); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Failed to create organization"; setError(errorMessage); diff --git a/apps/web/server/lib/auth/sso/[provider]/getServerSideProps.tsx b/apps/web/server/lib/auth/sso/[provider]/getServerSideProps.tsx index 2f84a5df34..a4754549aa 100644 --- a/apps/web/server/lib/auth/sso/[provider]/getServerSideProps.tsx +++ b/apps/web/server/lib/auth/sso/[provider]/getServerSideProps.tsx @@ -7,6 +7,7 @@ import stripe from "@calcom/features/ee/payments/server/stripe"; import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml"; import { ssoTenantProduct } from "@calcom/features/ee/sso/lib/sso"; import { checkUsername } from "@calcom/features/profile/lib/checkUsername"; +import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboarding-path.service"; import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; @@ -18,7 +19,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const providerParam = asStringOrNull(context.query.provider); const emailParam = asStringOrNull(context.query.email); const usernameParam = asStringOrNull(context.query.username); - const successDestination = `/getting-started${usernameParam ? `?username=${usernameParam}` : ""}`; + + const successDestination = await OnboardingPathService.getGettingStartedPathWithParams( + prisma, + usernameParam ? { username: usernameParam } : undefined + ); + if (!providerParam) { throw new Error(`File is not named sso/[provider]`); } diff --git a/packages/features/auth/lib/hooks/useRedirectToOnboardingIfNeeded.tsx b/packages/features/auth/lib/hooks/useRedirectToOnboardingIfNeeded.tsx index 674a6ae339..27dc555e52 100644 --- a/packages/features/auth/lib/hooks/useRedirectToOnboardingIfNeeded.tsx +++ b/packages/features/auth/lib/hooks/useRedirectToOnboardingIfNeeded.tsx @@ -42,9 +42,10 @@ export function useRedirectToOnboardingIfNeeded() { useEffect(() => { if (canRedirect) { - router.replace("/getting-started"); + const gettingStartedPath = flags["onboarding-v3"] ? "/onboarding/getting-started" : "/getting-started"; + router.replace(gettingStartedPath); } - }, [canRedirect, router]); + }, [canRedirect, router, flags]); return { isLoading, diff --git a/packages/features/ee/teams/services/teamService.ts b/packages/features/ee/teams/services/teamService.ts index b7aa220aba..fd8ad502aa 100644 --- a/packages/features/ee/teams/services/teamService.ts +++ b/packages/features/ee/teams/services/teamService.ts @@ -7,6 +7,7 @@ import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepos import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import { createAProfileForAnExistingUser } from "@calcom/features/profile/lib/createAProfileForAnExistingUser"; import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; +import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboarding-path.service"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { deleteDomain } from "@calcom/lib/domainManager/organization"; import logger from "@calcom/lib/logger"; @@ -76,7 +77,7 @@ export class TeamService { if (!existingToken) throw new TRPCError({ code: "NOT_FOUND", message: "Invite token not found" }); return { token: existingToken.token, - inviteLink: TeamService.buildInviteLink(existingToken.token, isOrganizationOrATeamInOrganization), + inviteLink: await TeamService.buildInviteLink(existingToken.token, isOrganizationOrATeamInOrganization), }; } @@ -93,14 +94,18 @@ export class TeamService { return { token, - inviteLink: TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization), + inviteLink: await TeamService.buildInviteLink(token, isOrganizationOrATeamInOrganization), }; } - private static buildInviteLink(token: string, isOrgContext: boolean): string { + private static async buildInviteLink(token: string, isOrgContext: boolean): Promise { const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}`; - const orgInviteLink = `${WEBAPP_URL}/signup?token=${token}&callbackUrl=/getting-started`; - return isOrgContext ? orgInviteLink : teamInviteLink; + if (!isOrgContext) { + return teamInviteLink; + } + const gettingStartedPath = await OnboardingPathService.getGettingStartedPath(prisma); + const orgInviteLink = `${WEBAPP_URL}/signup?token=${token}&callbackUrl=${gettingStartedPath}`; + return orgInviteLink; } /** * Deletes a team and all its associated data in a safe, transactional order. diff --git a/packages/features/onboarding/lib/onboarding-path.service.ts b/packages/features/onboarding/lib/onboarding-path.service.ts new file mode 100644 index 0000000000..90b48199c7 --- /dev/null +++ b/packages/features/onboarding/lib/onboarding-path.service.ts @@ -0,0 +1,24 @@ +import { FeaturesRepository } from "@calcom/features/flags/features.repository"; +import type { PrismaClient } from "@calcom/prisma"; + +export class OnboardingPathService { + static async getGettingStartedPath(prisma: PrismaClient): Promise { + const featuresRepository = new FeaturesRepository(prisma); + const onboardingV3Enabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("onboarding-v3"); + return onboardingV3Enabled ? "/onboarding/getting-started" : "/getting-started"; + } + + static async getGettingStartedPathWithParams( + prisma: PrismaClient, + queryParams?: Record + ): Promise { + const basePath = await OnboardingPathService.getGettingStartedPath(prisma); + + if (!queryParams || Object.keys(queryParams).length === 0) { + return basePath; + } + + const params = new URLSearchParams(queryParams); + return `${basePath}?${params.toString()}`; + } +} diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts index 3ff0895bc6..9798d86977 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts @@ -9,6 +9,7 @@ import { PermissionCheckService } from "@calcom/features/pbac/services/permissio import { createAProfileForAnExistingUser } from "@calcom/features/profile/lib/createAProfileForAnExistingUser"; import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; +import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboarding-path.service"; import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability"; import { ENABLE_PROFILE_SWITCHER, WEBAPP_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; @@ -503,12 +504,13 @@ export async function sendSignupToOrganizationEmail({ }) { try { const verificationToken = await createVerificationToken(usernameOrEmail, teamId); + const gettingStartedPath = await OnboardingPathService.getGettingStartedPath(prisma); await sendTeamInviteEmail({ language: translation, from: inviterName || `${team.name}'s admin`, to: usernameOrEmail, teamName: team.name, - joinLink: `${WEBAPP_URL}/signup?token=${verificationToken.token}&callbackUrl=/getting-started`, + joinLink: `${WEBAPP_URL}/signup?token=${verificationToken.token}&callbackUrl=${gettingStartedPath}`, isCalcomMember: false, isOrg: isOrg, parentTeamName: team?.parent?.name, @@ -717,7 +719,8 @@ export const sendExistingUserTeamInviteEmails = async ({ if (!user.completedOnboarding && !user.password?.hash && user.identityProvider === "CAL") { const verificationToken = await createVerificationToken(user.email, teamId); - inviteTeamOptions.joinLink = `${WEBAPP_URL}/signup?token=${verificationToken.token}&callbackUrl=/getting-started`; + const gettingStartedPath = await OnboardingPathService.getGettingStartedPath(prisma); + inviteTeamOptions.joinLink = `${WEBAPP_URL}/signup?token=${verificationToken.token}&callbackUrl=${gettingStartedPath}`; inviteTeamOptions.isCalcomMember = false; } else if (!isAutoJoin) { let verificationToken = await prisma.verificationToken.findFirst({ diff --git a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts index 12603c88c6..a92f26fe4d 100644 --- a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts @@ -1,5 +1,6 @@ import { sendTeamInviteEmail } from "@calcom/emails"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; +import { OnboardingPathService } from "@calcom/features/onboarding/lib/onboarding-path.service"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; import { VerificationTokenRepository } from "@calcom/lib/server/repository/verificationToken"; @@ -51,7 +52,8 @@ export const resendInvitationHandler = async ({ ctx, input }: InviteMemberOption if (user?.completedOnboarding) { inviteTeamOptions.joinLink = `${WEBAPP_URL}/teams?token=${verificationToken.token}&autoAccept=true`; } else { - inviteTeamOptions.joinLink = `${WEBAPP_URL}/signup?token=${verificationToken.token}&callbackUrl=/getting-started`; + const gettingStartedPath = await OnboardingPathService.getGettingStartedPath(prisma); + inviteTeamOptions.joinLink = `${WEBAPP_URL}/signup?token=${verificationToken.token}&callbackUrl=${gettingStartedPath}`; inviteTeamOptions.isCalcomMember = false; } } catch (error) {