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 { sdkActionManager, useEmbedNonStylesConfig, useEmbedStyles, useIsEmbed, } from "@calcom/embed-core/embed-iframe"; import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage"; 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 { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { BadgeCheckIcon, EventTypeDescriptionLazy as EventTypeDescription, Icon } from "@calcom/ui"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import { EmbedProps } from "@lib/withEmbedSsr"; import AvatarGroup from "@components/ui/AvatarGroup"; import { AvatarSSR } from "@components/ui/AvatarSSR"; import { ssrInit } from "@server/lib/ssr"; const HeadSeo = dynamic(() => import("@components/seo/head-seo")); export default function User(props: inferSSRProps & EmbedProps) { 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(props.isEmbed); 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) 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]); const isEventListEmpty = eventTypes.length === 0; return ( <> ({ username, name: dynamicNames[index] })) : [{ username: `${user.username}`, name: `${user.name}` }], }} />
{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}

)) )}
{isEventListEmpty && }
); } User.isThemeSupported = true; const getEventTypesWithHiddenFromDB = async (userId: number) => { return ( await prisma.eventType.findMany({ where: { AND: [ { teamId: null, }, { OR: [ { userId, }, { users: { some: { id: userId, }, }, }, ], }, ], }, orderBy: [ { position: "desc", }, { id: "asc", }, ], select: { ...baseEventTypeSelect, metadata: true, }, }) ).map((eventType) => ({ ...eventType, metadata: EventTypeMetaDataSchema.parse(eventType.metadata), })); }; 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, away: true, verified: true, allowDynamicBooking: true, }, }); if (!users.length) { return { notFound: true, } as { 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); 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: EventTypeMetaDataSchema.parse(eventType.metadata || {}), })); 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, }, }; };