From 684575d0a48e68110f702786b62fb634880037d9 Mon Sep 17 00:00:00 2001 From: DmytroHryshyn <125881252+DmytroHryshyn@users.noreply.github.com> Date: Thu, 11 Jan 2024 23:20:03 +0200 Subject: [PATCH] chore: [app-router-migration 15] migrate bookings/* page group (#13038) * codemod: remove-get-static-props * replace ssrInit * manual: extract getServerSideProps into a file * manual: Export getServerSideProps from legacy page --------- Co-authored-by: Benny Joo --- apps/web/app/WithEmbedSSR.tsx | 57 +++ .../app/future/booking/[uid]/embed/page.tsx | 10 + apps/web/app/future/booking/[uid]/page.tsx | 204 ++++++++++ apps/web/lib/booking.ts | 170 ++++++++ .../lib/booking/[uid]/getServerSideProps.tsx | 199 ++++++++++ apps/web/pages/booking/[uid].tsx | 374 +----------------- apps/web/pages/booking/[uid]/embed.tsx | 5 +- 7 files changed, 664 insertions(+), 355 deletions(-) create mode 100644 apps/web/app/WithEmbedSSR.tsx create mode 100644 apps/web/app/future/booking/[uid]/embed/page.tsx create mode 100644 apps/web/app/future/booking/[uid]/page.tsx create mode 100644 apps/web/lib/booking.ts create mode 100644 apps/web/lib/booking/[uid]/getServerSideProps.tsx diff --git a/apps/web/app/WithEmbedSSR.tsx b/apps/web/app/WithEmbedSSR.tsx new file mode 100644 index 0000000000..c19ead200d --- /dev/null +++ b/apps/web/app/WithEmbedSSR.tsx @@ -0,0 +1,57 @@ +import type { GetServerSidePropsContext } from "next"; +import { isNotFoundError } from "next/dist/client/components/not-found"; +import { getURLFromRedirectError, isRedirectError } from "next/dist/client/components/redirect"; +import { notFound, redirect } from "next/navigation"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; + +export type EmbedProps = { + isEmbed?: boolean; +}; + +export default function withEmbedSsrAppDir>( + getData: (context: GetServerSidePropsContext) => Promise +) { + return async (context: GetServerSidePropsContext): Promise => { + const { embed, layout } = context.query; + + try { + const props = await getData(context); + + return { + ...props, + isEmbed: true, + }; + } catch (e) { + if (isRedirectError(e)) { + const destinationUrl = getURLFromRedirectError(e); + let urlPrefix = ""; + + // Get the URL parsed from URL so that we can reliably read pathname and searchParams from it. + const destinationUrlObj = new URL(destinationUrl, WEBAPP_URL); + + // If it's a complete URL, use the origin as the prefix to ensure we redirect to the same domain. + if (destinationUrl.search(/^(http:|https:).*/) !== -1) { + urlPrefix = destinationUrlObj.origin; + } else { + // Don't use any prefix for relative URLs to ensure we stay on the same domain + urlPrefix = ""; + } + + const destinationQueryStr = destinationUrlObj.searchParams.toString(); + // Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace + const newDestinationUrl = `${urlPrefix}${destinationUrlObj.pathname}/embed?${ + destinationQueryStr ? `${destinationQueryStr}&` : "" + }layout=${layout}&embed=${embed}`; + + redirect(newDestinationUrl); + } + + if (isNotFoundError(e)) { + notFound(); + } + + throw e; + } + }; +} diff --git a/apps/web/app/future/booking/[uid]/embed/page.tsx b/apps/web/app/future/booking/[uid]/embed/page.tsx new file mode 100644 index 0000000000..d3fcef1feb --- /dev/null +++ b/apps/web/app/future/booking/[uid]/embed/page.tsx @@ -0,0 +1,10 @@ +import OldPage from "@pages/booking/[uid]"; +import withEmbedSsrAppDir from "app/WithEmbedSSR"; +import { WithLayout } from "app/layoutHOC"; + +import { getData } from "../page"; + +const getEmbedData = withEmbedSsrAppDir(getData); + +// @ts-expect-error Type '(context: GetServerSidePropsContext) => Promise' is not assignable to type '(arg: { +export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage }); diff --git a/apps/web/app/future/booking/[uid]/page.tsx b/apps/web/app/future/booking/[uid]/page.tsx new file mode 100644 index 0000000000..e0db2efa6f --- /dev/null +++ b/apps/web/app/future/booking/[uid]/page.tsx @@ -0,0 +1,204 @@ +import OldPage from "@pages/booking/[uid]"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import type { GetServerSidePropsContext } from "next"; +import { notFound } from "next/navigation"; +import { z } from "zod"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking"; +import { parseRecurringEvent } from "@calcom/lib"; +import { getDefaultEvent } from "@calcom/lib/defaultEvents"; +import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; +import prisma from "@calcom/prisma"; +import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; + +import { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } from "@lib/booking"; + +import { ssrInit } from "@server/lib/ssr"; + +const stringToBoolean = z + .string() + .optional() + .transform((val) => val === "true"); + +const querySchema = z.object({ + uid: z.string(), + email: z.string().optional(), + eventTypeSlug: z.string().optional(), + cancel: stringToBoolean, + allRemainingBookings: stringToBoolean, + changes: stringToBoolean, + reschedule: stringToBoolean, + isSuccessBookingPage: stringToBoolean, + formerTime: z.string().optional(), + seatReferenceUid: z.string().optional(), +}); + +export const generateMetadata = async () => + await _generateMetadata( + () => "", + () => "" + ); + +export const getData = async (context: GetServerSidePropsContext) => { + const ssr = await ssrInit(context); + const session = await getServerSession(context); + let tz: string | null = null; + let userTimeFormat: number | null = null; + let requiresLoginToUpdate = false; + if (session) { + const user = await ssr.viewer.me.fetch(); + tz = user.timeZone; + userTimeFormat = user.timeFormat; + } + + const parsedQuery = querySchema.safeParse(context.query); + + if (!parsedQuery.success) { + notFound(); + } + + const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data; + + const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid); + const bookingInfoRaw = await prisma.booking.findFirst({ + where: { + uid: maybeUid, + }, + select: { + title: true, + id: true, + uid: true, + description: true, + customInputs: true, + smsReminderNumber: true, + recurringEventId: true, + startTime: true, + endTime: true, + location: true, + status: true, + metadata: true, + cancellationReason: true, + responses: true, + rejectionReason: true, + user: { + select: { + id: true, + name: true, + email: true, + username: true, + timeZone: true, + }, + }, + attendees: { + select: { + name: true, + email: true, + timeZone: true, + }, + }, + eventTypeId: true, + eventType: { + select: { + eventName: true, + slug: true, + timeZone: true, + }, + }, + seatsReferences: { + select: { + referenceUid: true, + }, + }, + }, + }); + + if (!bookingInfoRaw) { + notFound(); + } + + const eventTypeRaw = !bookingInfoRaw.eventTypeId + ? getDefaultEvent(eventTypeSlug || "") + : await getEventTypesFromDB(bookingInfoRaw.eventTypeId); + if (!eventTypeRaw) { + notFound(); + } + + if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) { + requiresLoginToUpdate = true; + } + + const bookingInfo = getBookingWithResponses(bookingInfoRaw); + // @NOTE: had to do this because Server side cant return [Object objects] + // probably fixable with json.stringify -> json.parse + bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date; + bookingInfo["endTime"] = (bookingInfo?.endTime as Date)?.toISOString() as unknown as Date; + + eventTypeRaw.users = !!eventTypeRaw.hosts?.length + ? eventTypeRaw.hosts.map((host) => host.user) + : eventTypeRaw.users; + + if (!eventTypeRaw.users.length) { + if (!eventTypeRaw.owner) { + notFound(); + } + + eventTypeRaw.users.push({ + ...eventTypeRaw.owner, + }); + } + + const eventType = { + ...eventTypeRaw, + periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null, + periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null, + metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata), + recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent), + customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs), + }; + + const profile = { + name: eventType.team?.name || eventType.users[0]?.name || null, + email: eventType.team ? null : eventType.users[0].email || null, + theme: (!eventType.team?.name && eventType.users[0]?.theme) || null, + brandColor: eventType.team ? null : eventType.users[0].brandColor || null, + darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null, + slug: eventType.team?.slug || eventType.users[0]?.username || null, + }; + + if (bookingInfo !== null && eventType.seatsPerTimeSlot) { + await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id); + } + + const payment = await prisma.payment.findFirst({ + where: { + bookingId: bookingInfo.id, + }, + select: { + success: true, + refunded: true, + currency: true, + amount: true, + paymentOption: true, + }, + }); + + return { + themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username, + hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding, + profile, + eventType, + recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId), + dehydratedState: ssr.dehydrate(), + dynamicEventName: bookingInfo?.eventType?.eventName || "", + bookingInfo, + paymentStatus: payment, + ...(tz && { tz }), + userTimeFormat, + requiresLoginToUpdate, + }; +}; + +// @ts-expect-error Argument of type '{ req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. +export default WithLayout({ getLayout: null, getData, Page: OldPage }); diff --git a/apps/web/lib/booking.ts b/apps/web/lib/booking.ts new file mode 100644 index 0000000000..1bff1aa195 --- /dev/null +++ b/apps/web/lib/booking.ts @@ -0,0 +1,170 @@ +import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; +import prisma from "@calcom/prisma"; +import type { Prisma } from "@calcom/prisma/client"; +import { BookingStatus } from "@calcom/prisma/enums"; +import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; + +export const getEventTypesFromDB = async (id: number) => { + const userSelect = { + id: true, + name: true, + username: true, + hideBranding: true, + theme: true, + brandColor: true, + darkBrandColor: true, + email: true, + timeZone: true, + }; + const eventType = await prisma.eventType.findUnique({ + where: { + id, + }, + select: { + id: true, + title: true, + description: true, + length: true, + eventName: true, + recurringEvent: true, + requiresConfirmation: true, + userId: true, + successRedirectUrl: true, + customInputs: true, + locations: true, + price: true, + currency: true, + bookingFields: true, + disableGuests: true, + timeZone: true, + owner: { + select: userSelect, + }, + users: { + select: userSelect, + }, + hosts: { + select: { + user: { + select: userSelect, + }, + }, + }, + team: { + select: { + slug: true, + name: true, + hideBranding: true, + }, + }, + workflows: { + select: { + workflow: { + select: { + id: true, + steps: true, + }, + }, + }, + }, + metadata: true, + seatsPerTimeSlot: true, + seatsShowAttendees: true, + seatsShowAvailabilityCount: true, + periodStartDate: true, + periodEndDate: true, + }, + }); + + if (!eventType) { + return eventType; + } + + const metadata = EventTypeMetaDataSchema.parse(eventType.metadata); + + return { + isDynamic: false, + ...eventType, + bookingFields: getBookingFieldsWithSystemFields(eventType), + metadata, + }; +}; + +export const handleSeatsEventTypeOnBooking = async ( + eventType: { + seatsPerTimeSlot?: number | null; + seatsShowAttendees: boolean | null; + seatsShowAvailabilityCount: boolean | null; + [x: string | number | symbol]: unknown; + }, + bookingInfo: Partial< + Prisma.BookingGetPayload<{ + include: { + attendees: { select: { name: true; email: true } }; + seatsReferences: { select: { referenceUid: true } }; + user: { + select: { + id: true; + name: true; + email: true; + username: true; + timeZone: true; + }; + }; + }; + }> + >, + seatReferenceUid?: string, + userId?: number +) => { + if (eventType?.seatsPerTimeSlot !== null) { + // @TODO: right now bookings with seats doesn't save every description that its entered by every user + delete bookingInfo.description; + } else { + return; + } + // @TODO: If handling teams, we need to do more check ups for this. + if (bookingInfo?.user?.id === userId) { + return; + } + + if (!eventType.seatsShowAttendees) { + const seatAttendee = await prisma.bookingSeat.findFirst({ + where: { + referenceUid: seatReferenceUid, + }, + include: { + attendee: { + select: { + name: true, + email: true, + }, + }, + }, + }); + + if (seatAttendee) { + const attendee = bookingInfo?.attendees?.find((a) => { + return a.email === seatAttendee.attendee?.email; + }); + bookingInfo["attendees"] = attendee ? [attendee] : []; + } else { + bookingInfo["attendees"] = []; + } + } + return bookingInfo; +}; + +export async function getRecurringBookings(recurringEventId: string | null) { + if (!recurringEventId) return null; + const recurringBookings = await prisma.booking.findMany({ + where: { + recurringEventId, + status: BookingStatus.ACCEPTED, + }, + select: { + startTime: true, + }, + }); + return recurringBookings.map((obj) => obj.startTime.toString()); +} diff --git a/apps/web/lib/booking/[uid]/getServerSideProps.tsx b/apps/web/lib/booking/[uid]/getServerSideProps.tsx new file mode 100644 index 0000000000..e10af55fb1 --- /dev/null +++ b/apps/web/lib/booking/[uid]/getServerSideProps.tsx @@ -0,0 +1,199 @@ +import type { GetServerSidePropsContext } from "next"; +import { z } from "zod"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking"; +import { parseRecurringEvent } from "@calcom/lib"; +import { getDefaultEvent } from "@calcom/lib/defaultEvents"; +import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; +import prisma from "@calcom/prisma"; +import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; + +import { ssrInit } from "@server/lib/ssr"; + +const stringToBoolean = z + .string() + .optional() + .transform((val) => val === "true"); + +const querySchema = z.object({ + uid: z.string(), + email: z.string().optional(), + eventTypeSlug: z.string().optional(), + cancel: stringToBoolean, + allRemainingBookings: stringToBoolean, + changes: stringToBoolean, + reschedule: stringToBoolean, + isSuccessBookingPage: stringToBoolean, + formerTime: z.string().optional(), + seatReferenceUid: z.string().optional(), +}); + +export async function getServerSideProps(context: GetServerSidePropsContext) { + // this is needed to prevent bundling of lib/booking to the client bundle + // usually functions that are used in getServerSideProps are tree shaken from client bundle + // but not in case when they are exported. So we have to dynamically load them, or to copy paste them to the /future/page. + + const { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } = await import( + "@lib/booking" + ); + + const ssr = await ssrInit(context); + const session = await getServerSession(context); + let tz: string | null = null; + let userTimeFormat: number | null = null; + let requiresLoginToUpdate = false; + if (session) { + const user = await ssr.viewer.me.fetch(); + tz = user.timeZone; + userTimeFormat = user.timeFormat; + } + + const parsedQuery = querySchema.safeParse(context.query); + + if (!parsedQuery.success) return { notFound: true } as const; + const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data; + + const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid); + const bookingInfoRaw = await prisma.booking.findFirst({ + where: { + uid: maybeUid, + }, + select: { + title: true, + id: true, + uid: true, + description: true, + customInputs: true, + smsReminderNumber: true, + recurringEventId: true, + startTime: true, + endTime: true, + location: true, + status: true, + metadata: true, + cancellationReason: true, + responses: true, + rejectionReason: true, + user: { + select: { + id: true, + name: true, + email: true, + username: true, + timeZone: true, + }, + }, + attendees: { + select: { + name: true, + email: true, + timeZone: true, + }, + }, + eventTypeId: true, + eventType: { + select: { + eventName: true, + slug: true, + timeZone: true, + }, + }, + seatsReferences: { + select: { + referenceUid: true, + }, + }, + }, + }); + if (!bookingInfoRaw) { + return { + notFound: true, + } as const; + } + + const eventTypeRaw = !bookingInfoRaw.eventTypeId + ? getDefaultEvent(eventTypeSlug || "") + : await getEventTypesFromDB(bookingInfoRaw.eventTypeId); + if (!eventTypeRaw) { + return { + notFound: true, + } as const; + } + + if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) { + requiresLoginToUpdate = true; + } + + const bookingInfo = getBookingWithResponses(bookingInfoRaw); + // @NOTE: had to do this because Server side cant return [Object objects] + // probably fixable with json.stringify -> json.parse + bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date; + bookingInfo["endTime"] = (bookingInfo?.endTime as Date)?.toISOString() as unknown as Date; + + eventTypeRaw.users = !!eventTypeRaw.hosts?.length + ? eventTypeRaw.hosts.map((host) => host.user) + : eventTypeRaw.users; + + if (!eventTypeRaw.users.length) { + if (!eventTypeRaw.owner) + return { + notFound: true, + } as const; + eventTypeRaw.users.push({ + ...eventTypeRaw.owner, + }); + } + + const eventType = { + ...eventTypeRaw, + periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null, + periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null, + metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata), + recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent), + customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs), + }; + + const profile = { + name: eventType.team?.name || eventType.users[0]?.name || null, + email: eventType.team ? null : eventType.users[0].email || null, + theme: (!eventType.team?.name && eventType.users[0]?.theme) || null, + brandColor: eventType.team ? null : eventType.users[0].brandColor || null, + darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null, + slug: eventType.team?.slug || eventType.users[0]?.username || null, + }; + + if (bookingInfo !== null && eventType.seatsPerTimeSlot) { + await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id); + } + + const payment = await prisma.payment.findFirst({ + where: { + bookingId: bookingInfo.id, + }, + select: { + success: true, + refunded: true, + currency: true, + amount: true, + paymentOption: true, + }, + }); + + return { + props: { + themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username, + hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding, + profile, + eventType, + recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId), + trpcState: ssr.dehydrate(), + dynamicEventName: bookingInfo?.eventType?.eventName || "", + bookingInfo, + paymentStatus: payment, + ...(tz && { tz }), + userTimeFormat, + requiresLoginToUpdate, + }, + }; +} diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index afada8d7a1..cdb7f67978 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -1,7 +1,8 @@ +"use client"; + import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; import classNames from "classnames"; import { createEvent } from "ics"; -import type { GetServerSidePropsContext } from "next"; import { useSession } from "next-auth/react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; @@ -22,35 +23,28 @@ import { useIsBackgroundTransparent, useIsEmbed, } from "@calcom/embed-core/embed-iframe"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { Price } from "@calcom/features/bookings/components/event-meta/Price"; import { SMS_REMINDER_NUMBER_FIELD, SystemField } from "@calcom/features/bookings/lib/SystemField"; -import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking"; -import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; -import { parseRecurringEvent } from "@calcom/lib"; import { APP_NAME } from "@calcom/lib/constants"; import { formatToLocalizedDate, formatToLocalizedTime, formatToLocalizedTimezone, } from "@calcom/lib/date-fns"; -import { getDefaultEvent } from "@calcom/lib/defaultEvents"; import useGetBrandingColours from "@calcom/lib/getBrandColours"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; import { getEveryFreqFor } from "@calcom/lib/recurringStrings"; -import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat"; import { localStorage } from "@calcom/lib/webstorage"; -import prisma from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; -import { bookingMetadataSchema, customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; +import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import { Alert, Badge, Button, EmailInput, HeadSeo, useCalcomTheme } from "@calcom/ui"; import { AlertCircle, Calendar, Check, ChevronLeft, ExternalLink, X } from "@calcom/ui/components/icon"; +import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps"; import { timeZone } from "@lib/clock"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; @@ -58,23 +52,7 @@ import PageWrapper from "@components/PageWrapper"; import CancelBooking from "@components/booking/CancelBooking"; import EventReservationSchema from "@components/schemas/EventReservationSchema"; -import { ssrInit } from "@server/lib/ssr"; - -const useBrandColors = ({ - brandColor, - darkBrandColor, -}: { - brandColor?: string | null; - darkBrandColor?: string | null; -}) => { - const brandTheme = useGetBrandingColours({ - lightVal: brandColor, - darkVal: darkBrandColor, - }); - useCalcomTheme(brandTheme); -}; - -type SuccessProps = inferSSRProps; +export { getServerSideProps }; const stringToBoolean = z .string() @@ -94,6 +72,22 @@ const querySchema = z.object({ seatReferenceUid: z.string().optional(), }); +const useBrandColors = ({ + brandColor, + darkBrandColor, +}: { + brandColor?: string | null; + darkBrandColor?: string | null; +}) => { + const brandTheme = useGetBrandingColours({ + lightVal: brandColor, + darkVal: darkBrandColor, + }); + useCalcomTheme(brandTheme); +}; + +type SuccessProps = inferSSRProps; + export default function Success(props: SuccessProps) { const { t } = useLocale(); const router = useRouter(); @@ -925,329 +919,3 @@ export function RecurringBookings({ ); } - -const getEventTypesFromDB = async (id: number) => { - const userSelect = { - id: true, - name: true, - username: true, - hideBranding: true, - theme: true, - brandColor: true, - darkBrandColor: true, - email: true, - timeZone: true, - }; - const eventType = await prisma.eventType.findUnique({ - where: { - id, - }, - select: { - id: true, - title: true, - description: true, - length: true, - eventName: true, - recurringEvent: true, - requiresConfirmation: true, - userId: true, - successRedirectUrl: true, - customInputs: true, - locations: true, - price: true, - currency: true, - bookingFields: true, - disableGuests: true, - timeZone: true, - owner: { - select: userSelect, - }, - users: { - select: userSelect, - }, - hosts: { - select: { - user: { - select: userSelect, - }, - }, - }, - team: { - select: { - slug: true, - name: true, - hideBranding: true, - }, - }, - workflows: { - select: { - workflow: { - select: { - id: true, - steps: true, - }, - }, - }, - }, - metadata: true, - seatsPerTimeSlot: true, - seatsShowAttendees: true, - seatsShowAvailabilityCount: true, - periodStartDate: true, - periodEndDate: true, - }, - }); - - if (!eventType) { - return eventType; - } - - const metadata = EventTypeMetaDataSchema.parse(eventType.metadata); - - return { - isDynamic: false, - ...eventType, - bookingFields: getBookingFieldsWithSystemFields(eventType), - metadata, - }; -}; - -const handleSeatsEventTypeOnBooking = async ( - eventType: { - seatsPerTimeSlot?: number | null; - seatsShowAttendees: boolean | null; - seatsShowAvailabilityCount: boolean | null; - [x: string | number | symbol]: unknown; - }, - bookingInfo: Partial< - Prisma.BookingGetPayload<{ - include: { - attendees: { select: { name: true; email: true } }; - seatsReferences: { select: { referenceUid: true } }; - user: { - select: { - id: true; - name: true; - email: true; - username: true; - timeZone: true; - }; - }; - }; - }> - >, - seatReferenceUid?: string, - userId?: number -) => { - if (eventType?.seatsPerTimeSlot !== null) { - // @TODO: right now bookings with seats doesn't save every description that its entered by every user - delete bookingInfo.description; - } else { - return; - } - // @TODO: If handling teams, we need to do more check ups for this. - if (bookingInfo?.user?.id === userId) { - return; - } - - if (!eventType.seatsShowAttendees) { - const seatAttendee = await prisma.bookingSeat.findFirst({ - where: { - referenceUid: seatReferenceUid, - }, - include: { - attendee: { - select: { - name: true, - email: true, - }, - }, - }, - }); - - if (seatAttendee) { - const attendee = bookingInfo?.attendees?.find((a) => { - return a.email === seatAttendee.attendee?.email; - }); - bookingInfo["attendees"] = attendee ? [attendee] : []; - } else { - bookingInfo["attendees"] = []; - } - } - return bookingInfo; -}; - -export async function getServerSideProps(context: GetServerSidePropsContext) { - const ssr = await ssrInit(context); - const session = await getServerSession(context); - let tz: string | null = null; - let userTimeFormat: number | null = null; - let requiresLoginToUpdate = false; - if (session) { - const user = await ssr.viewer.me.fetch(); - tz = user.timeZone; - userTimeFormat = user.timeFormat; - } - - const parsedQuery = querySchema.safeParse(context.query); - - if (!parsedQuery.success) return { notFound: true } as const; - const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data; - - const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid); - const bookingInfoRaw = await prisma.booking.findFirst({ - where: { - uid: maybeUid, - }, - select: { - title: true, - id: true, - uid: true, - description: true, - customInputs: true, - smsReminderNumber: true, - recurringEventId: true, - startTime: true, - endTime: true, - location: true, - status: true, - metadata: true, - cancellationReason: true, - responses: true, - rejectionReason: true, - user: { - select: { - id: true, - name: true, - email: true, - username: true, - timeZone: true, - }, - }, - attendees: { - select: { - name: true, - email: true, - timeZone: true, - }, - }, - eventTypeId: true, - eventType: { - select: { - eventName: true, - slug: true, - timeZone: true, - }, - }, - seatsReferences: { - select: { - referenceUid: true, - }, - }, - }, - }); - if (!bookingInfoRaw) { - return { - notFound: true, - } as const; - } - - const eventTypeRaw = !bookingInfoRaw.eventTypeId - ? getDefaultEvent(eventTypeSlug || "") - : await getEventTypesFromDB(bookingInfoRaw.eventTypeId); - if (!eventTypeRaw) { - return { - notFound: true, - } as const; - } - - if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) { - requiresLoginToUpdate = true; - } - - const bookingInfo = getBookingWithResponses(bookingInfoRaw); - // @NOTE: had to do this because Server side cant return [Object objects] - // probably fixable with json.stringify -> json.parse - bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date; - bookingInfo["endTime"] = (bookingInfo?.endTime as Date)?.toISOString() as unknown as Date; - - eventTypeRaw.users = !!eventTypeRaw.hosts?.length - ? eventTypeRaw.hosts.map((host) => host.user) - : eventTypeRaw.users; - - if (!eventTypeRaw.users.length) { - if (!eventTypeRaw.owner) - return { - notFound: true, - } as const; - eventTypeRaw.users.push({ - ...eventTypeRaw.owner, - }); - } - - const eventType = { - ...eventTypeRaw, - periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null, - periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null, - metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata), - recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent), - customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs), - }; - - const profile = { - name: eventType.team?.name || eventType.users[0]?.name || null, - email: eventType.team ? null : eventType.users[0].email || null, - theme: (!eventType.team?.name && eventType.users[0]?.theme) || null, - brandColor: eventType.team ? null : eventType.users[0].brandColor || null, - darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null, - slug: eventType.team?.slug || eventType.users[0]?.username || null, - }; - - if (bookingInfo !== null && eventType.seatsPerTimeSlot) { - await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id); - } - - const payment = await prisma.payment.findFirst({ - where: { - bookingId: bookingInfo.id, - }, - select: { - success: true, - refunded: true, - currency: true, - amount: true, - paymentOption: true, - }, - }); - - return { - props: { - themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username, - hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding, - profile, - eventType, - recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId), - trpcState: ssr.dehydrate(), - dynamicEventName: bookingInfo?.eventType?.eventName || "", - bookingInfo, - paymentStatus: payment, - ...(tz && { tz }), - userTimeFormat, - requiresLoginToUpdate, - }, - }; -} - -async function getRecurringBookings(recurringEventId: string | null) { - if (!recurringEventId) return null; - const recurringBookings = await prisma.booking.findMany({ - where: { - recurringEventId, - status: BookingStatus.ACCEPTED, - }, - select: { - startTime: true, - }, - }); - return recurringBookings.map((obj) => obj.startTime.toString()); -} diff --git a/apps/web/pages/booking/[uid]/embed.tsx b/apps/web/pages/booking/[uid]/embed.tsx index 5d6b405e57..3e004ab3e6 100644 --- a/apps/web/pages/booking/[uid]/embed.tsx +++ b/apps/web/pages/booking/[uid]/embed.tsx @@ -1,6 +1,7 @@ -import withEmbedSsr from "@lib/withEmbedSsr"; +"use client"; -import { getServerSideProps as _getServerSideProps } from "../[uid]"; +import { getServerSideProps as _getServerSideProps } from "@lib/booking/[uid]/getServerSideProps"; +import withEmbedSsr from "@lib/withEmbedSsr"; export { default } from "../[uid]";