import type { GetServerSidePropsContext } from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import Head from "next/head"; import { useRouter } from "next/router"; import type { CSSProperties } from "react"; import { z } from "zod"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { APP_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; import { Button, StepCard, Steps } from "@calcom/ui"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; 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"; export type IOnboardingPageProps = inferSSRProps; 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]), }); // 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 = (props: IOnboardingPageProps) => { const router = useRouter(); const { user } = props; const { t } = useLocale(); const result = stepRouteSchema.safeParse(router.query); const currentStep = result.success ? result.data.step[0] : INITIAL_STEP; 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( { pathname: `/getting-started/${stepTransform(newStep)}`, }, undefined ); }; 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)} />} {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 crypto = await import("crypto"); const session = await getServerSession({ req, res }); if (!session?.user?.id) { return { redirect: { permanent: false, destination: "/auth/login" } }; } const user = await prisma.user.findUnique({ where: { id: session.user.id, }, select: { id: true, username: true, name: true, email: true, bio: true, avatar: true, timeZone: true, weekStart: true, hideBranding: true, theme: true, brandColor: true, darkBrandColor: true, metadata: true, timeFormat: true, allowDynamicBooking: true, defaultScheduleId: true, 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" } }; } return { props: { ...(await serverSideTranslations(context.locale ?? "", ["common"])), user: { ...user, emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), }, hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false, }, }; }; OnboardingPage.isThemeSupported = false; OnboardingPage.PageWrapper = PageWrapper; export default OnboardingPage;