chore: Onboarding path service plus redirects (#24679)
* i18n * WIP icon stuff * More icon work * More gradient tuning * Mvoe plan-icon to planicon.tsx * Fix cubic suggestion * Fix darkmode icon and gradients * Fix type error * Onboarding path service * Update usages of onboarding path and getting started hook
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<string | null>(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);
|
||||
|
||||
@@ -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]`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string> {
|
||||
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.
|
||||
|
||||
@@ -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<string> {
|
||||
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<string, string>
|
||||
): Promise<string> {
|
||||
const basePath = await OnboardingPathService.getGettingStartedPath(prisma);
|
||||
|
||||
if (!queryParams || Object.keys(queryParams).length === 0) {
|
||||
return basePath;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(queryParams);
|
||||
return `${basePath}?${params.toString()}`;
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user