import type { GetServerSidePropsContext } from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import Head from "next/head"; import { usePathname, useRouter } from "next/navigation"; import type { CSSProperties } from "react"; import { Suspense } from "react"; import { z } from "zod"; import { getLocale } from "@calcom/features/auth/lib/getLocale"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { APP_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; import prisma from "@calcom/prisma"; import { trpc } from "@calcom/trpc"; import { Button, StepCard, Steps } from "@calcom/ui"; import { Loader } from "@calcom/ui/components/icon"; import PageWrapper from "@components/PageWrapper"; import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars"; import { ConnectedVideoStep } from "@components/getting-started/steps-views/ConnectedVideoStep"; import { SetupAvailability } from "@components/getting-started/steps-views/SetupAvailability"; import UserProfile from "@components/getting-started/steps-views/UserProfile"; import { UserSettings } from "@components/getting-started/steps-views/UserSettings"; import { ssrInit } from "@server/lib/ssr"; const INITIAL_STEP = "user-settings"; const steps = [ "user-settings", "connected-calendar", "connected-video", "setup-availability", "user-profile", ] as const; const stepTransform = (step: (typeof steps)[number]) => { const stepIndex = steps.indexOf(step); if (stepIndex > -1) { return steps[stepIndex]; } return INITIAL_STEP; }; const stepRouteSchema = z.object({ step: z.array(z.enum(steps)).default([INITIAL_STEP]), from: z.string().optional(), }); // TODO: Refactor how steps work to be contained in one array/object. Currently we have steps,initalsteps,headers etc. These can all be in one place const OnboardingPage = () => { const pathname = usePathname(); const params = useParamsWithFallback(); const router = useRouter(); const [user] = trpc.viewer.me.useSuspenseQuery(); const { t } = useLocale(); const result = stepRouteSchema.safeParse(params); const currentStep = result.success ? result.data.step[0] : INITIAL_STEP; const from = result.success ? result.data.from : ""; const headers = [ { title: `${t("welcome_to_cal_header", { appName: APP_NAME })}`, subtitle: [`${t("we_just_need_basic_info")}`, `${t("edit_form_later_subtitle")}`], }, { title: `${t("connect_your_calendar")}`, subtitle: [`${t("connect_your_calendar_instructions")}`], skipText: `${t("connect_calendar_later")}`, }, { title: `${t("connect_your_video_app")}`, subtitle: [`${t("connect_your_video_app_instructions")}`], skipText: `${t("set_up_later")}`, }, { title: `${t("set_availability")}`, subtitle: [ `${t("set_availability_getting_started_subtitle_1")}`, `${t("set_availability_getting_started_subtitle_2")}`, ], }, { title: `${t("nearly_there")}`, subtitle: [`${t("nearly_there_instructions")}`], }, ]; // TODO: Add this in when we have solved the ability to move to tokens accept invite and note invitedto // Ability to accept other pending invites if any (low priority) // if (props.hasPendingInvites) { // headers.unshift( // props.hasPendingInvites && { // title: `${t("email_no_user_invite_heading", { appName: APP_NAME })}`, // subtitle: [], // TODO: come up with some subtitle text here // } // ); // } const goToIndex = (index: number) => { const newStep = steps[index]; router.push(`/getting-started/${stepTransform(newStep)}`); }; const currentStepIndex = steps.indexOf(currentStep); return (
{`${APP_NAME} - ${t("getting_started")}`}

{headers[currentStepIndex]?.title || "Undefined title"}

{headers[currentStepIndex]?.subtitle.map((subtitle, index) => (

{subtitle}

))}
}> {currentStep === "user-settings" && ( goToIndex(1)} hideUsername={from === "signup"} /> )} {currentStep === "connected-calendar" && goToIndex(2)} />} {currentStep === "connected-video" && goToIndex(3)} />} {currentStep === "setup-availability" && ( goToIndex(4)} defaultScheduleId={user.defaultScheduleId} /> )} {currentStep === "user-profile" && } {headers[currentStepIndex]?.skipText && (
)}
); }; export const getServerSideProps = async (context: GetServerSidePropsContext) => { const { req, res } = context; const session = await getServerSession({ req, res }); if (!session?.user?.id) { return { redirect: { permanent: false, destination: "/auth/login" } }; } const ssr = await ssrInit(context); await ssr.viewer.me.prefetch(); const user = await prisma.user.findUnique({ where: { id: session.user.id, }, select: { completedOnboarding: true, teams: { select: { accepted: true, team: { select: { id: true, name: true, logo: true, }, }, }, }, }, }); if (!user) { throw new Error("User from session not found"); } if (user.completedOnboarding) { return { redirect: { permanent: false, destination: "/event-types" } }; } const locale = await getLocale(context.req); return { props: { ...(await serverSideTranslations(locale, ["common"])), trpcState: ssr.dehydrate(), hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false, }, }; }; OnboardingPage.isThemeSupported = false; OnboardingPage.PageWrapper = PageWrapper; export default OnboardingPage;