diff --git a/packages/lib/bookingSuccessRedirect.ts b/packages/lib/bookingSuccessRedirect.ts index 76b6972d7f..8c630127ec 100644 --- a/packages/lib/bookingSuccessRedirect.ts +++ b/packages/lib/bookingSuccessRedirect.ts @@ -1,6 +1,7 @@ import type { EventType } from "@prisma/client"; import { useRouter } from "next/navigation"; +import dayjs from "@calcom/dayjs"; import type { PaymentPageProps } from "@calcom/ee/payments/pages/payment"; import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import type { BookingResponse } from "@calcom/features/bookings/types"; @@ -24,11 +25,25 @@ function getNewSearchParams(args: { type SuccessRedirectBookingType = Pick< BookingResponse | PaymentPageProps["booking"], - "uid" | "title" | "description" | "startTime" | "endTime" | "location" | "attendees" | "user" + "uid" | "title" | "description" | "startTime" | "endTime" | "location" | "attendees" | "user" | "responses" >; +type BookingResponseKey = keyof SuccessRedirectBookingType; + +type ResultType = { + [key in BookingResponseKey]?: SuccessRedirectBookingType[key]; +} & { + hostName?: string[]; + attendeeName?: string | null; + hostStartTime?: string | null; + attendeeStartTime?: string | null; + guestEmails?: string[] | null; + phone?: string | null; + attendeeFirstName?: string | null; + attendeeLastName?: string | null; +}; + export const getBookingRedirectExtraParams = (booking: SuccessRedirectBookingType) => { - type BookingResponseKey = keyof SuccessRedirectBookingType; const redirectQueryParamKeys: BookingResponseKey[] = [ "title", "description", @@ -37,42 +52,80 @@ export const getBookingRedirectExtraParams = (booking: SuccessRedirectBookingTyp "location", "attendees", "user", + "responses", ]; - type ResultType = { - [key in BookingResponseKey]?: SuccessRedirectBookingType[key]; - } & { - hostName?: string[]; - attendeeName?: string | null; - }; + function getSafe(obj: unknown, path: (string | number)[]): T | undefined { + return path.reduce( + (acc, key) => (typeof acc === "object" && acc !== null ? (acc as any)[key] : undefined), + obj + ) as T | undefined; + } + + // Helper function to extract response details (e.g., phone, attendee's first and last name) + function extractResponseDetails(booking: SuccessRedirectBookingType, obj: ResultType): ResultType { + const result: ResultType = { ...obj }; + const phone = getSafe(booking.responses, ["phone"]); + const firstName = getSafe(booking.responses, ["name", "firstName"]); + const lastName = getSafe(booking.responses, ["name", "lastName"]); + const name = getSafe(booking.responses, ["name"]); + + if (phone) result.phone = phone; + if (firstName) result.attendeeFirstName = firstName; + if (lastName) result.attendeeLastName = lastName; + else if (name && typeof name === "string") result.attendeeName = name; // Fallback if `name` is a string instead of an object + + return result; + } + + // Helper function to extract user details (e.g., host name and time zone) + function extractUserDetails(booking: SuccessRedirectBookingType, obj: ResultType): ResultType { + if (booking.user?.name) { + const hostStartTime = dayjs(booking.startTime).tz(booking.user.timeZone).format(); + return { + ...obj, + hostName: [...(obj.hostName || []), booking.user.name], + hostStartTime, + }; + } + return obj; + } + + // Helper function to extract attendee and guest details + function extractAttendeesAndGuests(booking: SuccessRedirectBookingType, obj: ResultType): ResultType { + if (!Array.isArray(booking.attendees) || booking.attendees.length === 0) return obj; + + const attendeeName = booking.attendees[0]?.name || null; + const attendeeTimeZone = booking.attendees[0]?.timeZone || "UTC"; + const attendeeStartTime = dayjs(booking.startTime).tz(attendeeTimeZone).format(); + + const { hostNames, guestEmails } = booking.attendees.slice(1).reduce( + (acc, attendee) => { + if (attendee.name) { + acc.hostNames.push(attendee.name); + } else if (attendee.email) { + acc.guestEmails.push(attendee.email); + } + return acc; + }, + { hostNames: [], guestEmails: [] } as { hostNames: string[]; guestEmails: string[] } + ); + + return { + ...obj, + attendeeName, + attendeeStartTime, + hostName: [...(obj.hostName || []), ...hostNames], + guestEmails: guestEmails.length > 0 ? guestEmails : undefined, + }; + } const result = (Object.keys(booking) as BookingResponseKey[]) .filter((key) => redirectQueryParamKeys.includes(key)) .reduce((obj, key) => { - if (key === "user" && booking.user?.name) { - return { ...obj, hostName: [...(obj.hostName || []), booking.user.name] }; - } - - if (key === "attendees" && Array.isArray(booking.attendees)) { - const attendeeName = booking.attendees[0]?.name || null; - const { hostNames, guestEmails } = booking.attendees.slice(1).reduce( - (acc, attendee) => { - if (attendee.name) { - acc.hostNames.push(attendee.name); - } else if (attendee.email) { - acc.guestEmails.push(attendee.email); - } - return acc; - }, - { hostNames: [], guestEmails: [] } as { hostNames: string[]; guestEmails: string[] } - ); - return { - ...obj, - attendeeName, - hostName: [...(obj.hostName || []), ...hostNames], - guestEmail: guestEmails.length > 0 ? guestEmails : undefined, - }; - } + if (key === "responses") return extractResponseDetails(booking, obj); + if (key === "user") return extractUserDetails(booking, obj); + if (key === "attendees") return extractAttendeesAndGuests(booking, obj); return { ...obj, [key]: booking[key] }; }, {}); @@ -91,6 +144,8 @@ export const getBookingRedirectExtraParams = (booking: SuccessRedirectBookingTyp ), hostName: result.hostName?.join(", "), attendeeName: result.attendeeName || undefined, + hostStartTime: result.hostStartTime || undefined, + attendeeStartTime: result.attendeeStartTime || undefined, }; return queryCompatibleParams; diff --git a/packages/prisma/selects/payment.ts b/packages/prisma/selects/payment.ts index 1b7104406a..fdaadc5c4c 100644 --- a/packages/prisma/selects/payment.ts +++ b/packages/prisma/selects/payment.ts @@ -18,15 +18,18 @@ export const paymentDataSelect = Prisma.validator()({ title: true, startTime: true, endTime: true, + responses: true, user: { select: { name: true, + timeZone: true, }, }, attendees: { select: { email: true, name: true, + timeZone: true, }, }, eventTypeId: true,