import { UserPlan } from "@prisma/client"; import classNames from "classnames"; import { GetServerSidePropsContext } from "next"; import dynamic from "next/dynamic"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect } from "react"; import { Toaster } from "react-hot-toast"; import { JSONObject } from "superjson/dist/types"; import { sdkActionManager, useEmbedNonStylesConfig, useEmbedStyles, useIsEmbed, } from "@calcom/embed-core/embed-iframe"; import CustomBranding from "@calcom/lib/CustomBranding"; import defaultEvents, { getDynamicEventDescription, getGroupName, getUsernameList, getUsernameSlugLink, } from "@calcom/lib/defaultEvents"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import prisma from "@calcom/prisma"; import { baseEventTypeSelect } from "@calcom/prisma/selects"; import { BadgeCheckIcon, Icon } from "@calcom/ui/Icon"; import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import AvatarGroup from "@components/ui/AvatarGroup"; import { AvatarSSR } from "@components/ui/AvatarSSR"; import { ssrInit } from "@server/lib/ssr"; const EventTypeDescription = dynamic(() => import("@calcom/ui/v2/modules/event-types/EventTypeDescription")); const HeadSeo = dynamic(() => import("@components/seo/head-seo")); export default function User(props: inferSSRProps) { const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props; const [user] = users; //To be used when we only have a single user, not dynamic group useTheme(user.theme); const { t } = useLocale(); const router = useRouter(); const groupEventTypes = props.users.some((user) => !user.allowDynamicBooking) ? (

{" " + t("unavailable")}

{t("user_dynamic_booking_disabled") as string}

) : ( ); const isEmbed = useIsEmbed(); const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem"); const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; const query = { ...router.query }; delete query.user; // So it doesn't display in the Link (and make tests fail) useExposePlanGlobally("PRO"); const nameOrUsername = user.name || user.username || ""; const telemetry = useTelemetry(); useEffect(() => { if (top !== window) { //page_view will be collected automatically by _middleware.ts telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/[user]")); } }, [telemetry, router.asPath]); return ( <>
{isSingleUser && ( // When we deal with a single user, not dynamic group

{nameOrUsername} {user.verified && ( )}

{user.bio}

)}
{user.away ? (

😴{" " + t("user_away")}

{t("user_away_description") as string}

) : isDynamicGroup ? ( //When we deal with dynamic group (users > 1) groupEventTypes ) : ( eventTypes.map((type) => (
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */} { sdkActionManager?.fire("eventTypeSelected", { eventType: type, }); }} className="block w-full p-5" data-testid="event-type-link">

{type.title}

{`/${user.username}/${type.slug}`}

)) )}
{eventTypes.length === 0 && (

{t("uh_oh") as string}

{t("no_event_types_have_been_setup") as string}

)}
); } User.isThemeSupported = true; const getEventTypesWithHiddenFromDB = async (userId: number, plan: UserPlan) => { return await prisma.eventType.findMany({ where: { AND: [ { teamId: null, }, { OR: [ { userId, }, { users: { some: { id: userId, }, }, }, ], }, ], }, orderBy: [ { position: "desc", }, { id: "asc", }, ], select: { metadata: true, ...baseEventTypeSelect, }, }); }; export const getServerSideProps = async (context: GetServerSidePropsContext) => { const ssr = await ssrInit(context); const crypto = await import("crypto"); const usernameList = getUsernameList(context.query.user as string); const dataFetchStart = Date.now(); const users = await prisma.user.findMany({ where: { username: { in: usernameList, }, }, select: { id: true, username: true, email: true, name: true, bio: true, brandColor: true, darkBrandColor: true, avatar: true, theme: true, plan: true, away: true, verified: true, allowDynamicBooking: true, }, }); if (!users.length) { return { notFound: true, }; } const isDynamicGroup = users.length > 1; const dynamicNames = isDynamicGroup ? users.map((user) => { return user.name || ""; }) : []; const [user] = users; //to be used when dealing with single user, not dynamic group const profile = isDynamicGroup ? { name: getGroupName(dynamicNames), image: null, theme: null, weekStart: "Sunday", brandColor: "", darkBrandColor: "", allowDynamicBooking: !users.some((user) => { return !user.allowDynamicBooking; }), } : { name: user.name || user.username, image: user.avatar, theme: user.theme, brandColor: user.brandColor, darkBrandColor: user.darkBrandColor, }; const eventTypesWithHidden = isDynamicGroup ? [] : await getEventTypesWithHiddenFromDB(user.id, user.plan); const dataFetchEnd = Date.now(); if (context.query.log === "1") { context.res.setHeader("X-Data-Fetch-Time", `${dataFetchEnd - dataFetchStart}ms`); } const eventTypesRaw = eventTypesWithHidden.filter((evt) => !evt.hidden); const eventTypes = eventTypesRaw.map((eventType) => ({ ...eventType, metadata: (eventType.metadata || {}) as JSONObject, })); const isSingleUser = users.length === 1; const dynamicUsernames = isDynamicGroup ? users.map((user) => { return user.username || ""; }) : []; return { props: { users, profile, user: { emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), }, eventTypes: isDynamicGroup ? defaultEvents.map((event) => { event.description = getDynamicEventDescription(dynamicUsernames, event.slug); return event; }) : eventTypes, trpcState: ssr.dehydrate(), isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser, }, }; };