From 6860ee67f06d559d6f929bfa28d138fb446b3c43 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Fri, 22 Dec 2023 13:44:26 +0000 Subject: [PATCH] intuita codemod: app-directory-boilerplate-calcom --- apps/web/app/AppDirSSRHOC.tsx | 10 +- .../app/future/[user]/[type]/embed/page.tsx | 9 + apps/web/app/future/[user]/[type]/page.tsx | 208 +++++++++++++ apps/web/app/future/[user]/embed/page.tsx | 10 + apps/web/app/future/[user]/page.tsx | 202 ++++++++++++ apps/web/app/future/apps/categories/page.tsx | 2 +- apps/web/app/future/apps/page.tsx | 2 +- .../app/future/bookings/[status]/layout.tsx | 2 +- .../getting-started/[[...step]]/page.tsx | 8 +- apps/web/app/future/teams/page.tsx | 2 +- apps/web/app/future/video/[uid]/page.tsx | 2 +- .../lib/[user]/[type]/getServerSideProps.tsx | 202 ++++++++++++ apps/web/lib/[user]/getServerSideProps.tsx | 291 +++++++++++++++++ apps/web/pages/[user].tsx | 294 +----------------- apps/web/pages/[user]/[type].tsx | 207 +----------- apps/web/pages/[user]/[type]/embed.tsx | 5 +- apps/web/pages/[user]/embed.tsx | 5 +- 17 files changed, 952 insertions(+), 509 deletions(-) create mode 100644 apps/web/app/future/[user]/[type]/embed/page.tsx create mode 100644 apps/web/app/future/[user]/[type]/page.tsx create mode 100644 apps/web/app/future/[user]/embed/page.tsx create mode 100644 apps/web/app/future/[user]/page.tsx create mode 100644 apps/web/lib/[user]/[type]/getServerSideProps.tsx create mode 100644 apps/web/lib/[user]/getServerSideProps.tsx diff --git a/apps/web/app/AppDirSSRHOC.tsx b/apps/web/app/AppDirSSRHOC.tsx index 87b419ca49..c173497fa4 100644 --- a/apps/web/app/AppDirSSRHOC.tsx +++ b/apps/web/app/AppDirSSRHOC.tsx @@ -2,16 +2,20 @@ import type { GetServerSideProps, GetServerSidePropsContext } from "next"; import { notFound, redirect } from "next/navigation"; export const withAppDir = - (getServerSideProps: GetServerSideProps) => async (context: GetServerSidePropsContext) => { + >(getServerSideProps: GetServerSideProps) => + async (context: GetServerSidePropsContext): Promise => { const ssrResponse = await getServerSideProps(context); if ("redirect" in ssrResponse) { redirect(ssrResponse.redirect.destination); } - if ("notFound" in ssrResponse) { notFound(); } - return ssrResponse.props; + return { + ...ssrResponse.props, + // includes dehydratedState required for future page trpcPropvider + ...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }), + }; }; diff --git a/apps/web/app/future/[user]/[type]/embed/page.tsx b/apps/web/app/future/[user]/[type]/embed/page.tsx new file mode 100644 index 0000000000..89af50b763 --- /dev/null +++ b/apps/web/app/future/[user]/[type]/embed/page.tsx @@ -0,0 +1,9 @@ +import LegacyPage from "@pages/[user]/[type]"; +import withEmbedSsrAppDir from "app/WithEmbedSSR"; +import { WithLayout } from "app/layoutHOC"; + +import { getPageProps } from "../page"; + +const getEmbedData = withEmbedSsrAppDir(getPageProps); + +export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">; diff --git a/apps/web/app/future/[user]/[type]/page.tsx b/apps/web/app/future/[user]/[type]/page.tsx new file mode 100644 index 0000000000..0193d87cd4 --- /dev/null +++ b/apps/web/app/future/[user]/[type]/page.tsx @@ -0,0 +1,208 @@ +import LegacyPage, { type PageProps } from "@pages/[user]/[type]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { type GetServerSidePropsContext } from "next"; +import { headers, cookies } from "next/headers"; +import { notFound, redirect } from "next/navigation"; +import { z } from "zod"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; +import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking"; +import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { getUsernameList } from "@calcom/lib/defaultEvents"; +import slugify from "@calcom/lib/slugify"; +import prisma from "@calcom/prisma"; +import { RedirectType } from "@calcom/prisma/client"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect"; + +import { ssrInit } from "@server/lib/ssr"; + +export const generateMetadata = async ({ params }: { params: Record }) => { + const pageProps = await getPageProps( + buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext + ); + const { eventData, booking, user, slug } = pageProps; + const rescheduleUid = booking?.uid; + const { trpc } = await import("@calcom/trpc/react"); + const { data: event } = trpc.viewer.public.event.useQuery( + { username: user, eventSlug: slug, isTeamEvent: false, org: eventData.entity.orgSlug ?? null }, + { refetchOnWindowFocus: false } + ); + + const profileName = event?.profile?.name ?? ""; + const title = event?.title ?? ""; + + return await _generateMetadata( + (t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, + (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}` + ); +}; + +const paramsSchema = z.object({ + type: z.string().transform((s) => slugify(s)), + user: z.string().transform((s) => getUsernameList(s)), +}); + +async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { + const session = await getServerSession(context); + const { user: usernames, type: slug } = paramsSchema.parse(context.params); + const { rescheduleUid, bookingUid } = context.query; + + const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + + const users = await prisma.user.findMany({ + where: { + username: { + in: usernames, + }, + organization: isValidOrgDomain + ? { + slug: currentOrgDomain, + } + : null, + }, + select: { + allowDynamicBooking: true, + }, + }); + + if (!users.length) { + return notFound(); + } + const org = isValidOrgDomain ? currentOrgDomain : null; + + let booking: GetBookingType | null = null; + if (rescheduleUid) { + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); + } else if (bookingUid) { + booking = await getBookingForSeatedEvent(`${bookingUid}`); + } + + // We use this to both prefetch the query on the server, + // as well as to check if the event exist, so we c an show a 404 otherwise. + const eventData = await ssr.viewer.public.event.fetch({ + username: usernames.join("+"), + eventSlug: slug, + org, + }); + + if (!eventData) { + return notFound(); + } + + return { + eventData: { + entity: eventData.entity, + length: eventData.length, + metadata: { + ...eventData.metadata, + multipleDuration: [15, 30, 60], + }, + }, + booking, + user: usernames.join("+"), + slug, + away: false, + dehydratedState: ssr.dehydrate(), + isBrandingHidden: false, + isSEOIndexable: true, + themeBasis: null, + bookingUid: bookingUid ? `${bookingUid}` : null, + rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null, + }; +} + +async function getUserPageProps(context: GetServerSidePropsContext) { + const session = await getServerSession(context); + const { user: usernames, type: slug } = paramsSchema.parse(context.params); + const username = usernames[0]; + const { rescheduleUid, bookingUid } = context.query; + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + + const isOrgContext = currentOrgDomain && isValidOrgDomain; + + if (!isOrgContext) { + const redirectObj = await getTemporaryOrgRedirect({ + slug: usernames[0], + redirectType: RedirectType.User, + eventTypeSlug: slug, + currentQuery: context.query, + }); + + if (redirectObj) { + return redirect(redirectObj.redirect.destination); + } + } + + const ssr = await ssrInit(context); + const user = await prisma.user.findFirst({ + where: { + username, + organization: userOrgQuery(context.req, context.params?.orgSlug), + }, + select: { + away: true, + hideBranding: true, + allowSEOIndexing: true, + }, + }); + + if (!user) { + return notFound(); + } + + let booking: GetBookingType | null = null; + if (rescheduleUid) { + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); + } else if (bookingUid) { + booking = await getBookingForSeatedEvent(`${bookingUid}`); + } + + const org = isValidOrgDomain ? currentOrgDomain : null; + // We use this to both prefetch the query on the server, + // as well as to check if the event exist, so we can show a 404 otherwise. + const eventData = await ssr.viewer.public.event.fetch({ + username, + eventSlug: slug, + org, + }); + + if (!eventData) { + return notFound(); + } + + return { + booking, + eventData: { + entity: eventData.entity, + length: eventData.length, + metadata: eventData.metadata, + }, + away: user?.away, + user: username, + slug, + dehydratedState: ssr.dehydrate(), + isBrandingHidden: user?.hideBranding, + isSEOIndexable: user?.allowSEOIndexing, + themeBasis: username, + bookingUid: bookingUid ? `${bookingUid}` : null, + rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null, + }; +} + +export const getPageProps = async (context: GetServerSidePropsContext) => { + const { user } = paramsSchema.parse(context.params); + const isDynamicGroup = user.length > 1; + + return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context); +}; + +// @ts-expect-error arg +export const getData = withAppDir(getPageProps); + +export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">; diff --git a/apps/web/app/future/[user]/embed/page.tsx b/apps/web/app/future/[user]/embed/page.tsx new file mode 100644 index 0000000000..1ebc5e6872 --- /dev/null +++ b/apps/web/app/future/[user]/embed/page.tsx @@ -0,0 +1,10 @@ +import LegacyPage from "@pages/[user]/[type]"; +import withEmbedSsrAppDir from "app/WithEmbedSSR"; +import { WithLayout } from "app/layoutHOC"; + +import { getPageProps } from "../page"; + +const getEmbedData = withEmbedSsrAppDir(getPageProps); + +// @ts-expect-error arg +export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">; diff --git a/apps/web/app/future/[user]/page.tsx b/apps/web/app/future/[user]/page.tsx new file mode 100644 index 0000000000..34f83958e3 --- /dev/null +++ b/apps/web/app/future/[user]/page.tsx @@ -0,0 +1,202 @@ +import LegacyPage, { type UserPageProps } from "@pages/[user]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { type GetServerSidePropsContext } from "next"; +import { notFound } from "next/navigation"; +import { encode } from "querystring"; + +import { getLayout } from "@calcom/features/MainLayoutAppDir"; +import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user"; +import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants"; +import { getUsernameList } from "@calcom/lib/defaultEvents"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; +import { stripMarkdown } from "@calcom/lib/stripMarkdown"; +import prisma from "@calcom/prisma"; +import { RedirectType } from "@calcom/prisma/client"; +import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import { getEventTypesWithHiddenFromDB } from "@lib/[user]/getServerSideProps"; +import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect"; + +import { ssrInit } from "@server/lib/ssr"; + +export const generateMetadata = async () => + await _generateMetadata( + (t) => t("workflows"), + (t) => t("workflows_to_automate_notifications") + ); + +export const getPageProps = async (context: GetServerSidePropsContext) => { + const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + const usernameList = getUsernameList(context.query.user as string); + const isOrgContext = isValidOrgDomain && currentOrgDomain; + const dataFetchStart = Date.now(); + let outOfOffice = false; + + if (usernameList.length === 1) { + const result = await handleUserRedirection({ username: usernameList[0] }); + if (result && result.outOfOffice) { + outOfOffice = true; + } + if (result && result.redirect?.destination) { + return result; + } + } + + const usersWithoutAvatar = await prisma.user.findMany({ + where: { + username: { + in: usernameList, + }, + organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null, + }, + select: { + id: true, + username: true, + email: true, + name: true, + bio: true, + metadata: true, + brandColor: true, + darkBrandColor: true, + avatarUrl: true, + organizationId: true, + organization: { + select: { + slug: true, + name: true, + metadata: true, + }, + }, + theme: true, + away: true, + verified: true, + allowDynamicBooking: true, + allowSEOIndexing: true, + }, + }); + + const isDynamicGroup = usersWithoutAvatar.length > 1; + if (isDynamicGroup) { + return { + redirect: { + permanent: false, + destination: `/${usernameList.join("+")}/dynamic`, + }, + } as { + redirect: { + permanent: false; + destination: string; + }; + }; + } + + const users = usersWithoutAvatar.map((user) => ({ + ...user, + organization: { + ...user.organization, + metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null, + }, + avatar: `/${user.username}/avatar.png`, + })); + + if (!isOrgContext) { + const redirect = await getTemporaryOrgRedirect({ + slug: usernameList[0], + redirectType: RedirectType.User, + eventTypeSlug: null, + currentQuery: context.query, + }); + + if (redirect) { + return redirect; + } + } + + if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) { + return notFound(); + } + + const [user] = users; //to be used when dealing with single user, not dynamic group + + const profile = { + name: user.name || user.username || "", + image: user.avatar, + theme: user.theme, + brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR, + avatarUrl: user.avatarUrl, + darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR, + allowSEOIndexing: user.allowSEOIndexing ?? true, + username: user.username, + organization: { + id: user.organizationId, + slug: user.organization?.slug ?? null, + requestedSlug: user.organization?.metadata?.requestedSlug ?? null, + }, + }; + + const eventTypesWithHidden = 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 || {}), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), + })); + + // if profile only has one public event-type, redirect to it + if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) { + // Redirect but don't change the URL + const urlDestination = `/${user.username}/${eventTypes[0].slug}`; + const { query } = context; + const urlQuery = new URLSearchParams(encode(query)); + + return { + redirect: { + permanent: false, + destination: `${urlDestination}?${urlQuery}`, + }, + }; + } + + const safeBio = markdownToSafeHTML(user.bio) || ""; + + const markdownStrippedBio = stripMarkdown(user?.bio || ""); + const org = usersWithoutAvatar[0].organization; + + return { + users: users.map((user) => ({ + name: user.name, + username: user.username, + bio: user.bio, + avatarUrl: user.avatarUrl, + away: usernameList.length === 1 ? outOfOffice : user.away, + verified: user.verified, + })), + entity: { + isUnpublished: org?.slug === null, + orgSlug: currentOrgDomain, + name: org?.name ?? null, + }, + eventTypes, + safeBio, + profile, + // Dynamic group has no theme preference right now. It uses system theme. + themeBasis: user.username, + dehydratedState: ssr.dehydrate(), + markdownStrippedBio, + }; +}; + +// @ts-expect-error arg +export const getData = withAppDir(getPageProps); + +export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">; diff --git a/apps/web/app/future/apps/categories/page.tsx b/apps/web/app/future/apps/categories/page.tsx index 0ef5547ee4..7df8bf3770 100644 --- a/apps/web/app/future/apps/categories/page.tsx +++ b/apps/web/app/future/apps/categories/page.tsx @@ -1,7 +1,7 @@ import LegacyPage from "@pages/apps/categories/index"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import type { GetServerSidePropsContext } from "next"; +import { type GetServerSidePropsContext } from "next"; import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; diff --git a/apps/web/app/future/apps/page.tsx b/apps/web/app/future/apps/page.tsx index cdccef7bdf..d53e2252ae 100644 --- a/apps/web/app/future/apps/page.tsx +++ b/apps/web/app/future/apps/page.tsx @@ -1,7 +1,7 @@ import AppsPage from "@pages/apps"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import type { GetServerSidePropsContext } from "next"; +import { type GetServerSidePropsContext } from "next"; import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; import { getLayout } from "@calcom/features/MainLayoutAppDir"; diff --git a/apps/web/app/future/bookings/[status]/layout.tsx b/apps/web/app/future/bookings/[status]/layout.tsx index 70fe487437..ff7907bf39 100644 --- a/apps/web/app/future/bookings/[status]/layout.tsx +++ b/apps/web/app/future/bookings/[status]/layout.tsx @@ -1,6 +1,6 @@ import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import type { GetServerSidePropsContext } from "next"; +import { type GetServerSidePropsContext } from "next"; import { notFound } from "next/navigation"; import { z } from "zod"; diff --git a/apps/web/app/future/getting-started/[[...step]]/page.tsx b/apps/web/app/future/getting-started/[[...step]]/page.tsx index 1665eca319..b8ad1b6c84 100644 --- a/apps/web/app/future/getting-started/[[...step]]/page.tsx +++ b/apps/web/app/future/getting-started/[[...step]]/page.tsx @@ -1,7 +1,6 @@ import LegacyPage from "@pages/getting-started/[[...step]]"; import { WithLayout } from "app/layoutHOC"; -import type { GetServerSidePropsContext } from "next"; -import { cookies, headers } from "next/headers"; +import { type GetServerSidePropsContext } from "next"; import { redirect } from "next/navigation"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; @@ -10,10 +9,7 @@ import prisma from "@calcom/prisma"; import { ssrInit } from "@server/lib/ssr"; const getData = async (ctx: GetServerSidePropsContext) => { - const req = { headers: headers(), cookies: cookies() }; - - //@ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest - const session = await getServerSession({ req }); + const session = await getServerSession({ req: ctx.req }); if (!session?.user?.id) { return redirect("/auth/login"); diff --git a/apps/web/app/future/teams/page.tsx b/apps/web/app/future/teams/page.tsx index 6453ac8e01..93e3b360ab 100644 --- a/apps/web/app/future/teams/page.tsx +++ b/apps/web/app/future/teams/page.tsx @@ -1,7 +1,7 @@ import OldPage from "@pages/teams/index"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import type { GetServerSidePropsContext } from "next"; +import { type GetServerSidePropsContext } from "next"; import { redirect } from "next/navigation"; import { getLayout } from "@calcom/features/MainLayoutAppDir"; diff --git a/apps/web/app/future/video/[uid]/page.tsx b/apps/web/app/future/video/[uid]/page.tsx index 7625bd0125..824c18baec 100644 --- a/apps/web/app/future/video/[uid]/page.tsx +++ b/apps/web/app/future/video/[uid]/page.tsx @@ -2,7 +2,7 @@ import OldPage from "@pages/video/[uid]"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import MarkdownIt from "markdown-it"; -import type { GetServerSidePropsContext } from "next"; +import { type GetServerSidePropsContext } from "next"; import { redirect } from "next/navigation"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; diff --git a/apps/web/lib/[user]/[type]/getServerSideProps.tsx b/apps/web/lib/[user]/[type]/getServerSideProps.tsx new file mode 100644 index 0000000000..7e07aff3dd --- /dev/null +++ b/apps/web/lib/[user]/[type]/getServerSideProps.tsx @@ -0,0 +1,202 @@ +import { type GetServerSidePropsContext } from "next"; +import { z } from "zod"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { handleTypeRedirection } from "@calcom/features/booking-redirect/handle-type"; +import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking"; +import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; +import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { getUsernameList } from "@calcom/lib/defaultEvents"; +import slugify from "@calcom/lib/slugify"; +import prisma from "@calcom/prisma"; +import { RedirectType } from "@calcom/prisma/client"; + +import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect"; + +async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { + const session = await getServerSession(context); + const { user: usernames, type: slug } = paramsSchema.parse(context.params); + const { rescheduleUid, bookingUid } = context.query; + + const { ssrInit } = await import("@server/lib/ssr"); + const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + + const users = await prisma.user.findMany({ + where: { + username: { + in: usernames, + }, + organization: isValidOrgDomain + ? { + slug: currentOrgDomain, + } + : null, + }, + select: { + allowDynamicBooking: true, + }, + }); + + if (!users.length) { + return { + notFound: true, + } as const; + } + const org = isValidOrgDomain ? currentOrgDomain : null; + + let booking: GetBookingType | null = null; + if (rescheduleUid) { + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); + } else if (bookingUid) { + booking = await getBookingForSeatedEvent(`${bookingUid}`); + } + + // We use this to both prefetch the query on the server, + // as well as to check if the event exist, so we c an show a 404 otherwise. + const eventData = await ssr.viewer.public.event.fetch({ + username: usernames.join("+"), + eventSlug: slug, + org, + }); + + if (!eventData) { + return { + notFound: true, + } as const; + } + + return { + props: { + eventData: { + entity: eventData.entity, + length: eventData.length, + metadata: { + ...eventData.metadata, + multipleDuration: [15, 30, 60], + }, + }, + booking, + user: usernames.join("+"), + slug, + away: false, + trpcState: ssr.dehydrate(), + isBrandingHidden: false, + isSEOIndexable: true, + themeBasis: null, + bookingUid: bookingUid ? `${bookingUid}` : null, + rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null, + }, + }; +} + +async function getUserPageProps(context: GetServerSidePropsContext) { + const session = await getServerSession(context); + const { user: usernames, type: slug } = paramsSchema.parse(context.params); + const username = usernames[0]; + const { rescheduleUid, bookingUid } = context.query; + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + let outOfOffice = false; + const isOrgContext = currentOrgDomain && isValidOrgDomain; + + if (!isOrgContext) { + const redirect = await getTemporaryOrgRedirect({ + slug: usernames[0], + redirectType: RedirectType.User, + eventTypeSlug: slug, + currentQuery: context.query, + }); + + if (redirect) { + return redirect; + } + } + + const { ssrInit } = await import("@server/lib/ssr"); + const ssr = await ssrInit(context); + const user = await prisma.user.findFirst({ + where: { + username, + organization: userOrgQuery(context.req, context.params?.orgSlug), + }, + select: { + id: true, + hideBranding: true, + allowSEOIndexing: true, + }, + }); + + if (!user) { + return { + notFound: true, + } as const; + } + // If user is found, quickly verify bookingRedirects + const result = await handleTypeRedirection({ + userId: user.id, + username, + slug, + }); + if (result && result.outOfOffice) { + outOfOffice = true; + } + if (result && result.redirect?.destination) { + return result; + } + + let booking: GetBookingType | null = null; + if (rescheduleUid) { + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); + } else if (bookingUid) { + booking = await getBookingForSeatedEvent(`${bookingUid}`); + } + + const org = isValidOrgDomain ? currentOrgDomain : null; + // We use this to both prefetch the query on the server, + // as well as to check if the event exist, so we can show a 404 otherwise. + const eventData = await ssr.viewer.public.event.fetch({ + username, + eventSlug: slug, + org, + }); + + if (!eventData) { + return { + notFound: true, + } as const; + } + + return { + props: { + booking, + eventData: { + entity: eventData.entity, + length: eventData.length, + metadata: eventData.metadata, + }, + away: outOfOffice, + user: username, + slug, + trpcState: ssr.dehydrate(), + isBrandingHidden: user?.hideBranding, + isSEOIndexable: user?.allowSEOIndexing, + themeBasis: username, + bookingUid: bookingUid ? `${bookingUid}` : null, + rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null, + }, + }; +} + +// Booker page fetches a tiny bit of data server side, to determine early +// whether the page should show an away state or dynamic booking not allowed. +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const { user } = paramsSchema.parse(context.params); + const isDynamicGroup = user.length > 1; + + return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context); +}; + +const paramsSchema = z.object({ + type: z.string().transform((s) => slugify(s)), + user: z.string().transform((s) => getUsernameList(s)), +}); diff --git a/apps/web/lib/[user]/getServerSideProps.tsx b/apps/web/lib/[user]/getServerSideProps.tsx new file mode 100644 index 0000000000..eedef65607 --- /dev/null +++ b/apps/web/lib/[user]/getServerSideProps.tsx @@ -0,0 +1,291 @@ +import type { DehydratedState } from "@tanstack/react-query"; +import type { GetServerSideProps } from "next"; +import { encode } from "querystring"; +import type { z } from "zod"; + +import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user"; +import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants"; +import { getUsernameList } from "@calcom/lib/defaultEvents"; +import logger from "@calcom/lib/logger"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; +import { stripMarkdown } from "@calcom/lib/stripMarkdown"; +import prisma from "@calcom/prisma"; +import { RedirectType, type EventType, type User } from "@calcom/prisma/client"; +import { baseEventTypeSelect } from "@calcom/prisma/selects"; +import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect"; +import type { EmbedProps } from "@lib/withEmbedSsr"; + +import { ssrInit } from "@server/lib/ssr"; + +export type UserPageProps = { + trpcState: DehydratedState; + profile: { + name: string; + image: string; + theme: string | null; + brandColor: string; + darkBrandColor: string; + organization: { + requestedSlug: string | null; + slug: string | null; + id: number | null; + }; + allowSEOIndexing: boolean; + username: string | null; + }; + users: Pick[]; + themeBasis: string | null; + markdownStrippedBio: string; + safeBio: string; + entity: { + isUnpublished?: boolean; + orgSlug?: string | null; + name?: string | null; + }; + eventTypes: ({ + descriptionAsSafeHTML: string; + metadata: z.infer; + } & Pick< + EventType, + | "id" + | "title" + | "slug" + | "length" + | "hidden" + | "lockTimeZoneToggleOnBookingPage" + | "requiresConfirmation" + | "requiresBookerEmailVerification" + | "price" + | "currency" + | "recurringEvent" + >)[]; +} & EmbedProps; + +export const getEventTypesWithHiddenFromDB = async (userId: number) => { + const eventTypes = await prisma.eventType.findMany({ + where: { + AND: [ + { + teamId: null, + }, + { + OR: [ + { + userId, + }, + { + users: { + some: { + id: userId, + }, + }, + }, + ], + }, + ], + }, + orderBy: [ + { + position: "desc", + }, + { + id: "asc", + }, + ], + select: { + ...baseEventTypeSelect, + metadata: true, + }, + }); + // map and filter metadata, exclude eventType entirely when faulty metadata is found. + // report error to exception so we don't lose the error. + return eventTypes.reduce((eventTypes, eventType) => { + const parsedMetadata = EventTypeMetaDataSchema.safeParse(eventType.metadata); + if (!parsedMetadata.success) { + logger.error(parsedMetadata.error); + return eventTypes; + } + eventTypes.push({ + ...eventType, + metadata: parsedMetadata.data, + }); + return eventTypes; + }, []); +}; + +export const getServerSideProps: GetServerSideProps = async (context) => { + const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + const usernameList = getUsernameList(context.query.user as string); + const isOrgContext = isValidOrgDomain && currentOrgDomain; + const dataFetchStart = Date.now(); + let outOfOffice = false; + + if (usernameList.length === 1) { + const result = await handleUserRedirection({ username: usernameList[0] }); + if (result && result.outOfOffice) { + outOfOffice = true; + } + if (result && result.redirect?.destination) { + return result; + } + } + + const usersWithoutAvatar = await prisma.user.findMany({ + where: { + username: { + in: usernameList, + }, + organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null, + }, + select: { + id: true, + username: true, + email: true, + name: true, + bio: true, + metadata: true, + brandColor: true, + darkBrandColor: true, + avatarUrl: true, + organizationId: true, + organization: { + select: { + slug: true, + name: true, + metadata: true, + }, + }, + theme: true, + away: true, + verified: true, + allowDynamicBooking: true, + allowSEOIndexing: true, + }, + }); + + const isDynamicGroup = usersWithoutAvatar.length > 1; + if (isDynamicGroup) { + return { + redirect: { + permanent: false, + destination: `/${usernameList.join("+")}/dynamic`, + }, + } as { + redirect: { + permanent: false; + destination: string; + }; + }; + } + + const users = usersWithoutAvatar.map((user) => ({ + ...user, + organization: { + ...user.organization, + metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null, + }, + avatar: `/${user.username}/avatar.png`, + })); + + if (!isOrgContext) { + const redirect = await getTemporaryOrgRedirect({ + slug: usernameList[0], + redirectType: RedirectType.User, + eventTypeSlug: null, + currentQuery: context.query, + }); + + if (redirect) { + return redirect; + } + } + + if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) { + return { + notFound: true, + } as { + notFound: true; + }; + } + + const [user] = users; //to be used when dealing with single user, not dynamic group + + const profile = { + name: user.name || user.username || "", + image: user.avatar, + theme: user.theme, + brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR, + avatarUrl: user.avatarUrl, + darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR, + allowSEOIndexing: user.allowSEOIndexing ?? true, + username: user.username, + organization: { + id: user.organizationId, + slug: user.organization?.slug ?? null, + requestedSlug: user.organization?.metadata?.requestedSlug ?? null, + }, + }; + + const eventTypesWithHidden = 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 || {}), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), + })); + + // if profile only has one public event-type, redirect to it + if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) { + // Redirect but don't change the URL + const urlDestination = `/${user.username}/${eventTypes[0].slug}`; + const { query } = context; + const urlQuery = new URLSearchParams(encode(query)); + + return { + redirect: { + permanent: false, + destination: `${urlDestination}?${urlQuery}`, + }, + }; + } + + const safeBio = markdownToSafeHTML(user.bio) || ""; + + const markdownStrippedBio = stripMarkdown(user?.bio || ""); + const org = usersWithoutAvatar[0].organization; + + return { + props: { + users: users.map((user) => ({ + name: user.name, + username: user.username, + bio: user.bio, + avatarUrl: user.avatarUrl, + away: usernameList.length === 1 ? outOfOffice : user.away, + verified: user.verified, + })), + entity: { + isUnpublished: org?.slug === null, + orgSlug: currentOrgDomain, + name: org?.name ?? null, + }, + eventTypes, + safeBio, + profile, + // Dynamic group has no theme preference right now. It uses system theme. + themeBasis: user.username, + trpcState: ssr.dehydrate(), + markdownStrippedBio, + }, + }; +}; diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index 5c8b506074..325779fd94 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -1,11 +1,10 @@ -import type { DehydratedState } from "@tanstack/react-query"; +"use client"; + import classNames from "classnames"; -import type { GetServerSideProps, InferGetServerSidePropsType } from "next"; +import type { InferGetServerSidePropsType } from "next"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; -import { encode } from "querystring"; import { Toaster } from "react-hot-toast"; -import type { z } from "zod"; import { sdkActionManager, @@ -13,34 +12,20 @@ import { useEmbedStyles, useIsEmbed, } from "@calcom/embed-core/embed-iframe"; -import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user"; -import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; -import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components"; import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage"; -import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants"; -import { getUsernameList } from "@calcom/lib/defaultEvents"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; -import logger from "@calcom/lib/logger"; -import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; -import { stripMarkdown } from "@calcom/lib/stripMarkdown"; -import prisma from "@calcom/prisma"; -import { RedirectType, type EventType, type User } from "@calcom/prisma/client"; -import { baseEventTypeSelect } from "@calcom/prisma/selects"; -import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { HeadSeo, UnpublishedEntity } from "@calcom/ui"; import { UserAvatar } from "@calcom/ui"; import { Verified, ArrowRight } from "@calcom/ui/components/icon"; -import type { EmbedProps } from "@lib/withEmbedSsr"; +import { getServerSideProps, type UserPageProps } from "@lib/[user]/getServerSideProps"; import PageWrapper from "@components/PageWrapper"; -import { ssrInit } from "@server/lib/ssr"; - -import { getTemporaryOrgRedirect } from "../lib/getTemporaryOrgRedirect"; +export { getServerSideProps, UserPageProps }; export function UserPage(props: InferGetServerSidePropsType) { const { users, profile, eventTypes, markdownStrippedBio, entity } = props; @@ -215,273 +200,4 @@ export function UserPage(props: InferGetServerSidePropsType { - const eventTypes = await prisma.eventType.findMany({ - where: { - AND: [ - { - teamId: null, - }, - { - OR: [ - { - userId, - }, - { - users: { - some: { - id: userId, - }, - }, - }, - ], - }, - ], - }, - orderBy: [ - { - position: "desc", - }, - { - id: "asc", - }, - ], - select: { - ...baseEventTypeSelect, - metadata: true, - }, - }); - // map and filter metadata, exclude eventType entirely when faulty metadata is found. - // report error to exception so we don't lose the error. - return eventTypes.reduce((eventTypes, eventType) => { - const parsedMetadata = EventTypeMetaDataSchema.safeParse(eventType.metadata); - if (!parsedMetadata.success) { - logger.error(parsedMetadata.error); - return eventTypes; - } - eventTypes.push({ - ...eventType, - metadata: parsedMetadata.data, - }); - return eventTypes; - }, []); -}; - -export type UserPageProps = { - trpcState: DehydratedState; - profile: { - name: string; - image: string; - theme: string | null; - brandColor: string; - darkBrandColor: string; - organization: { - requestedSlug: string | null; - slug: string | null; - id: number | null; - }; - allowSEOIndexing: boolean; - username: string | null; - }; - users: Pick[]; - themeBasis: string | null; - markdownStrippedBio: string; - safeBio: string; - entity: { - isUnpublished?: boolean; - orgSlug?: string | null; - name?: string | null; - }; - eventTypes: ({ - descriptionAsSafeHTML: string; - metadata: z.infer; - } & Pick< - EventType, - | "id" - | "title" - | "slug" - | "length" - | "hidden" - | "lockTimeZoneToggleOnBookingPage" - | "requiresConfirmation" - | "requiresBookerEmailVerification" - | "price" - | "currency" - | "recurringEvent" - >)[]; -} & EmbedProps; - -export const getServerSideProps: GetServerSideProps = async (context) => { - const ssr = await ssrInit(context); - const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); - const usernameList = getUsernameList(context.query.user as string); - const isOrgContext = isValidOrgDomain && currentOrgDomain; - const dataFetchStart = Date.now(); - let outOfOffice = false; - - if (usernameList.length === 1) { - const result = await handleUserRedirection({ username: usernameList[0] }); - if (result && result.outOfOffice) { - outOfOffice = true; - } - if (result && result.redirect?.destination) { - return result; - } - } - - const usersWithoutAvatar = await prisma.user.findMany({ - where: { - username: { - in: usernameList, - }, - organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null, - }, - select: { - id: true, - username: true, - email: true, - name: true, - bio: true, - metadata: true, - brandColor: true, - darkBrandColor: true, - avatarUrl: true, - organizationId: true, - organization: { - select: { - slug: true, - name: true, - metadata: true, - }, - }, - theme: true, - away: true, - verified: true, - allowDynamicBooking: true, - allowSEOIndexing: true, - }, - }); - - const isDynamicGroup = usersWithoutAvatar.length > 1; - if (isDynamicGroup) { - return { - redirect: { - permanent: false, - destination: `/${usernameList.join("+")}/dynamic`, - }, - } as { - redirect: { - permanent: false; - destination: string; - }; - }; - } - - const users = usersWithoutAvatar.map((user) => ({ - ...user, - organization: { - ...user.organization, - metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null, - }, - avatar: `/${user.username}/avatar.png`, - })); - - if (!isOrgContext) { - const redirect = await getTemporaryOrgRedirect({ - slug: usernameList[0], - redirectType: RedirectType.User, - eventTypeSlug: null, - currentQuery: context.query, - }); - - if (redirect) { - return redirect; - } - } - - if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) { - return { - notFound: true, - } as { - notFound: true; - }; - } - - const [user] = users; //to be used when dealing with single user, not dynamic group - - const profile = { - name: user.name || user.username || "", - image: user.avatar, - theme: user.theme, - brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR, - avatarUrl: user.avatarUrl, - darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR, - allowSEOIndexing: user.allowSEOIndexing ?? true, - username: user.username, - organization: { - id: user.organizationId, - slug: user.organization?.slug ?? null, - requestedSlug: user.organization?.metadata?.requestedSlug ?? null, - }, - }; - - const eventTypesWithHidden = 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 || {}), - descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), - })); - - // if profile only has one public event-type, redirect to it - if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) { - // Redirect but don't change the URL - const urlDestination = `/${user.username}/${eventTypes[0].slug}`; - const { query } = context; - const urlQuery = new URLSearchParams(encode(query)); - - return { - redirect: { - permanent: false, - destination: `${urlDestination}?${urlQuery}`, - }, - }; - } - - const safeBio = markdownToSafeHTML(user.bio) || ""; - - const markdownStrippedBio = stripMarkdown(user?.bio || ""); - const org = usersWithoutAvatar[0].organization; - - return { - props: { - users: users.map((user) => ({ - name: user.name, - username: user.username, - bio: user.bio, - avatarUrl: user.avatarUrl, - away: usernameList.length === 1 ? outOfOffice : user.away, - verified: user.verified, - })), - entity: { - isUnpublished: org?.slug === null, - orgSlug: currentOrgDomain, - name: org?.name ?? null, - }, - eventTypes, - safeBio, - profile, - // Dynamic group has no theme preference right now. It uses system theme. - themeBasis: user.username, - trpcState: ssr.dehydrate(), - markdownStrippedBio, - }, - }; -}; - export default UserPage; diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index f36ecdc15b..5d669e1e57 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -1,28 +1,19 @@ -import type { GetServerSidePropsContext } from "next"; +"use client"; + import { useSearchParams } from "next/navigation"; -import { z } from "zod"; import { Booker } from "@calcom/atoms"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import { handleTypeRedirection } from "@calcom/features/booking-redirect/handle-type"; import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses"; import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; -import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking"; -import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; -import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains"; -import { getUsernameList } from "@calcom/lib/defaultEvents"; -import slugify from "@calcom/lib/slugify"; -import prisma from "@calcom/prisma"; -import { RedirectType } from "@calcom/prisma/client"; +import { getServerSideProps } from "@lib/[user]/[type]/getServerSideProps"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; import type { EmbedProps } from "@lib/withEmbedSsr"; import PageWrapper from "@components/PageWrapper"; -import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect"; - -export type PageProps = inferSSRProps & EmbedProps; +export type PageProps = Omit, "trpcState"> & EmbedProps; +export { getServerSideProps }; export const getMultipleDurationValue = ( multipleDurationConfig: number[] | undefined, @@ -81,191 +72,3 @@ export default function Type({ Type.isBookingPage = true; Type.PageWrapper = PageWrapper; - -async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { - const session = await getServerSession(context); - const { user: usernames, type: slug } = paramsSchema.parse(context.params); - const { rescheduleUid, bookingUid } = context.query; - - const { ssrInit } = await import("@server/lib/ssr"); - const ssr = await ssrInit(context); - const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); - - const users = await prisma.user.findMany({ - where: { - username: { - in: usernames, - }, - organization: isValidOrgDomain - ? { - slug: currentOrgDomain, - } - : null, - }, - select: { - allowDynamicBooking: true, - }, - }); - - if (!users.length) { - return { - notFound: true, - } as const; - } - const org = isValidOrgDomain ? currentOrgDomain : null; - - let booking: GetBookingType | null = null; - if (rescheduleUid) { - booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); - } else if (bookingUid) { - booking = await getBookingForSeatedEvent(`${bookingUid}`); - } - - // We use this to both prefetch the query on the server, - // as well as to check if the event exist, so we c an show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ - username: usernames.join("+"), - eventSlug: slug, - org, - }); - - if (!eventData) { - return { - notFound: true, - } as const; - } - - return { - props: { - eventData: { - entity: eventData.entity, - length: eventData.length, - metadata: { - ...eventData.metadata, - multipleDuration: [15, 30, 60], - }, - }, - booking, - user: usernames.join("+"), - slug, - away: false, - trpcState: ssr.dehydrate(), - isBrandingHidden: false, - isSEOIndexable: true, - themeBasis: null, - bookingUid: bookingUid ? `${bookingUid}` : null, - rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null, - }, - }; -} - -async function getUserPageProps(context: GetServerSidePropsContext) { - const session = await getServerSession(context); - const { user: usernames, type: slug } = paramsSchema.parse(context.params); - const username = usernames[0]; - const { rescheduleUid, bookingUid } = context.query; - const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); - let outOfOffice = false; - const isOrgContext = currentOrgDomain && isValidOrgDomain; - - if (!isOrgContext) { - const redirect = await getTemporaryOrgRedirect({ - slug: usernames[0], - redirectType: RedirectType.User, - eventTypeSlug: slug, - currentQuery: context.query, - }); - - if (redirect) { - return redirect; - } - } - - const { ssrInit } = await import("@server/lib/ssr"); - const ssr = await ssrInit(context); - const user = await prisma.user.findFirst({ - where: { - username, - organization: userOrgQuery(context.req, context.params?.orgSlug), - }, - select: { - id: true, - hideBranding: true, - allowSEOIndexing: true, - }, - }); - - if (!user) { - return { - notFound: true, - } as const; - } - // If user is found, quickly verify bookingRedirects - const result = await handleTypeRedirection({ - userId: user.id, - username, - slug, - }); - if (result && result.outOfOffice) { - outOfOffice = true; - } - if (result && result.redirect?.destination) { - return result; - } - - let booking: GetBookingType | null = null; - if (rescheduleUid) { - booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); - } else if (bookingUid) { - booking = await getBookingForSeatedEvent(`${bookingUid}`); - } - - const org = isValidOrgDomain ? currentOrgDomain : null; - // We use this to both prefetch the query on the server, - // as well as to check if the event exist, so we can show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ - username, - eventSlug: slug, - org, - }); - - if (!eventData) { - return { - notFound: true, - } as const; - } - - return { - props: { - booking, - eventData: { - entity: eventData.entity, - length: eventData.length, - metadata: eventData.metadata, - }, - away: outOfOffice, - user: username, - slug, - trpcState: ssr.dehydrate(), - isBrandingHidden: user?.hideBranding, - isSEOIndexable: user?.allowSEOIndexing, - themeBasis: username, - bookingUid: bookingUid ? `${bookingUid}` : null, - rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null, - }, - }; -} - -const paramsSchema = z.object({ - type: z.string().transform((s) => slugify(s)), - user: z.string().transform((s) => getUsernameList(s)), -}); - -// Booker page fetches a tiny bit of data server side, to determine early -// whether the page should show an away state or dynamic booking not allowed. -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const { user } = paramsSchema.parse(context.params); - const isDynamicGroup = user.length > 1; - - return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context); -}; diff --git a/apps/web/pages/[user]/[type]/embed.tsx b/apps/web/pages/[user]/[type]/embed.tsx index 63fb82b309..c786e6815c 100644 --- a/apps/web/pages/[user]/[type]/embed.tsx +++ b/apps/web/pages/[user]/[type]/embed.tsx @@ -1,6 +1,7 @@ -import withEmbedSsr from "@lib/withEmbedSsr"; +"use client"; -import { getServerSideProps as _getServerSideProps } from "../[type]"; +import { getServerSideProps as _getServerSideProps } from "@lib/[user]/[type]/getServerSideProps"; +import withEmbedSsr from "@lib/withEmbedSsr"; export { default } from "../[type]"; diff --git a/apps/web/pages/[user]/embed.tsx b/apps/web/pages/[user]/embed.tsx index 5c62c6117e..04b66f10d4 100644 --- a/apps/web/pages/[user]/embed.tsx +++ b/apps/web/pages/[user]/embed.tsx @@ -1,6 +1,7 @@ -import withEmbedSsr from "@lib/withEmbedSsr"; +"use client"; -import { getServerSideProps as _getServerSideProps } from "../[user]"; +import { getServerSideProps as _getServerSideProps } from "@lib/[user]/getServerSideProps"; +import withEmbedSsr from "@lib/withEmbedSsr"; export { default } from "../[user]";