diff --git a/packages/embeds/embed-core/src/sdk-action-manager.ts b/packages/embeds/embed-core/src/sdk-action-manager.ts index 246cd7f63f..a3804059f3 100644 --- a/packages/embeds/embed-core/src/sdk-action-manager.ts +++ b/packages/embeds/embed-core/src/sdk-action-manager.ts @@ -30,6 +30,11 @@ export type EventDataMap = { eventTypeId: number | null | undefined; status: string | undefined; paymentRequired: boolean; + isRecurring: boolean; + /** + * This is only used for recurring bookings + */ + allBookings?: { startTime: string; endTime: string }[]; }; /** @@ -56,6 +61,11 @@ export type EventDataMap = { eventTypeId: number | null | undefined; status: string | undefined; paymentRequired: boolean; + isRecurring: boolean; + /** + * This is only used for recurring bookings + */ + allBookings?: { startTime: string; endTime: string }[]; }; /** * @deprecated Use `rescheduleBookingSuccessfulV2` instead. We restrict the data heavily there, only sending what is absolutely needed and keeping it light as well. Plus, more importantly that can be documented well. diff --git a/packages/features/bookings/Booker/components/hooks/useBookings.ts b/packages/features/bookings/Booker/components/hooks/useBookings.ts index dce5cd6ffb..e988410b4c 100644 --- a/packages/features/bookings/Booker/components/hooks/useBookings.ts +++ b/packages/features/bookings/Booker/components/hooks/useBookings.ts @@ -50,6 +50,29 @@ export interface IUseBookings { teamMemberEmail?: string; } +const getBookingSuccessfulEventPayload = (booking: { + title?: string; + startTime: string; + endTime: string; + eventTypeId?: number | null; + status?: BookingStatus; + paymentRequired: boolean; + uid?: string; + isRecurring: boolean; +}) => { + return { + uid: booking.uid, + title: booking.title, + startTime: booking.startTime, + endTime: booking.endTime, + eventTypeId: booking.eventTypeId, + status: booking.status, + paymentRequired: booking.paymentRequired, + isRecurring: booking.isRecurring, + }; +}; + +const getRescheduleBookingSuccessfulEventPayload = getBookingSuccessfulEventPayload; export interface IUseBookingLoadingStates { creatingBooking: boolean; creatingRecurringBooking: boolean; @@ -113,8 +136,8 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb const createBookingMutation = useMutation({ mutationFn: createBooking, - onSuccess: (responseData) => { - const { uid, paymentUid } = responseData; + onSuccess: (booking) => { + const { uid, paymentUid } = booking; const fullName = getFullName(bookingForm.getValues("responses.name")); const users = !!event.data?.hosts?.length @@ -126,44 +149,48 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb : duration && event.data?.metadata?.multipleDuration?.includes(duration) ? duration : event.data?.length; - const eventPayload = { - uid: responseData.uid, - title: responseData.title, - startTime: responseData.startTime, - endTime: responseData.endTime, - eventTypeId: responseData.eventTypeId, - status: responseData.status, - paymentRequired: responseData.paymentRequired, - }; + if (isRescheduling) { sdkActionManager?.fire("rescheduleBookingSuccessful", { - booking: responseData, + booking: booking, eventType: event.data, - date: responseData?.startTime?.toString() || "", + date: booking?.startTime?.toString() || "", duration: validDuration, organizer: { name: users?.[0]?.name || "Nameless", - email: responseData?.userPrimaryEmail || responseData.user?.email || "Email-less", - timeZone: responseData.user?.timeZone || "Europe/London", + email: booking?.userPrimaryEmail || booking.user?.email || "Email-less", + timeZone: booking.user?.timeZone || "Europe/London", }, - confirmed: !(responseData.status === BookingStatus.PENDING && event.data?.requiresConfirmation), + confirmed: !(booking.status === BookingStatus.PENDING && event.data?.requiresConfirmation), }); - sdkActionManager?.fire("rescheduleBookingSuccessfulV2", eventPayload); + sdkActionManager?.fire( + "rescheduleBookingSuccessfulV2", + getRescheduleBookingSuccessfulEventPayload({ + ...booking, + isRecurring: false, + }) + ); } else { sdkActionManager?.fire("bookingSuccessful", { - booking: responseData, + booking: booking, eventType: event.data, - date: responseData?.startTime?.toString() || "", + date: booking?.startTime?.toString() || "", duration: validDuration, organizer: { name: users?.[0]?.name || "Nameless", - email: responseData?.userPrimaryEmail || responseData.user?.email || "Email-less", - timeZone: responseData.user?.timeZone || "Europe/London", + email: booking?.userPrimaryEmail || booking.user?.email || "Email-less", + timeZone: booking.user?.timeZone || "Europe/London", }, - confirmed: !(responseData.status === BookingStatus.PENDING && event.data?.requiresConfirmation), + confirmed: !(booking.status === BookingStatus.PENDING && event.data?.requiresConfirmation), }); - sdkActionManager?.fire("bookingSuccessfulV2", eventPayload); + sdkActionManager?.fire( + "bookingSuccessfulV2", + getBookingSuccessfulEventPayload({ + ...booking, + isRecurring: false, + }) + ); } if (paymentUid) { @@ -188,7 +215,7 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb isSuccessBookingPage: true, email: bookingForm.getValues("responses.email"), eventTypeSlug: eventSlug, - seatReferenceUid: "seatReferenceUid" in responseData ? responseData.seatReferenceUid : null, + seatReferenceUid: "seatReferenceUid" in booking ? booking.seatReferenceUid : null, formerTime: isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined, }; @@ -196,7 +223,7 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb bookingSuccessRedirect({ successRedirectUrl: event?.data?.successRedirectUrl || "", query, - booking: responseData, + booking: booking, forwardParamsSuccessRedirect: event?.data?.forwardParamsSuccessRedirect === undefined ? true @@ -223,8 +250,8 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb const createRecurringBookingMutation = useMutation({ mutationFn: createRecurringBooking, - onSuccess: async (responseData) => { - const booking = responseData[0] || {}; + onSuccess: async (bookings) => { + const booking = bookings[0] || {}; const { uid } = booking; if (!uid) { @@ -241,6 +268,31 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined, }; + if (isRescheduling) { + // NOTE: It is recommended to define the event payload in the argument itself to provide a better type safety. + sdkActionManager?.fire("rescheduleBookingSuccessfulV2", { + ...getRescheduleBookingSuccessfulEventPayload({ + ...booking, + isRecurring: true, + }), + allBookings: bookings.map((booking) => ({ + startTime: booking.startTime, + endTime: booking.endTime, + })), + }); + } else { + sdkActionManager?.fire("bookingSuccessfulV2", { + ...getBookingSuccessfulEventPayload({ + ...booking, + isRecurring: true, + }), + allBookings: bookings.map((booking) => ({ + startTime: booking.startTime, + endTime: booking.endTime, + })), + }); + } + bookingSuccessRedirect({ successRedirectUrl: event?.data?.successRedirectUrl || "", query, diff --git a/packages/features/bookings/lib/create-booking.ts b/packages/features/bookings/lib/create-booking.ts index cd0bc28491..a730499876 100644 --- a/packages/features/bookings/lib/create-booking.ts +++ b/packages/features/bookings/lib/create-booking.ts @@ -4,8 +4,9 @@ import type { BookingCreateBody, BookingResponse } from "../types"; export const createBooking = async (data: BookingCreateBody) => { const response = await post< - Omit, - BookingResponse & { + BookingCreateBody, + // fetch response can't have a Date type, it must be a string + Omit & { startTime: string; endTime: string; } diff --git a/packages/features/bookings/lib/create-instant-booking.ts b/packages/features/bookings/lib/create-instant-booking.ts index e8036f5ae4..672faecdb3 100644 --- a/packages/features/bookings/lib/create-instant-booking.ts +++ b/packages/features/bookings/lib/create-instant-booking.ts @@ -3,6 +3,13 @@ import { post } from "@calcom/lib/fetch-wrapper"; import type { BookingCreateBody, InstantBookingResponse } from "../types"; export const createInstantBooking = async (data: BookingCreateBody) => { - const response = await post("/api/book/instant-event", data); + const response = await post< + BookingCreateBody, + // fetch response can't have a Date type, it must be a string + Omit & { + startTime: string; + endTime: string; + } + >("/api/book/instant-event", data); return response; }; diff --git a/packages/features/bookings/lib/create-recurring-booking.ts b/packages/features/bookings/lib/create-recurring-booking.ts index 67765d2ed0..42c69ec6c7 100644 --- a/packages/features/bookings/lib/create-recurring-booking.ts +++ b/packages/features/bookings/lib/create-recurring-booking.ts @@ -3,9 +3,13 @@ import { post } from "@calcom/lib/fetch-wrapper"; import type { RecurringBookingCreateBody, BookingResponse } from "../types"; export const createRecurringBooking = async (data: RecurringBookingCreateBody[]) => { - const response = await post( - "/api/book/recurring-event", - data - ); + const response = await post< + RecurringBookingCreateBody[], + // fetch response can't have a Date type, it must be a string + (Omit & { + startTime: string; + endTime: string; + })[] + >("/api/book/recurring-event", data); return response; };