From 0af62da16bdc53c45c5c1896a80fbb82243d45db Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Fri, 15 Aug 2025 10:51:08 +0530 Subject: [PATCH] feat: add dry run events for SDK (dryRunBookingSuccessfulV2 and dryRunRescheduleBookingSuccessfulV2) (#23072) * feat: add dry run events for SDK (dryRunBookingSuccessfulV2 and dryRunRescheduleBookingSuccessfulV2) - Add new event types dryRunBookingSuccessfulV2 and dryRunRescheduleBookingSuccessfulV2 to EventDataMap - Create payload functions that exclude uid field for dry run events - Fire dry run events when isDryRun is true in both regular and recurring booking flows - Add unit tests to verify dry run event payloads are correct and exclude uid field - Ensures dry run mode no longer skips event firing but uses appropriate dry run variants Co-Authored-By: hariom@cal.com * refactor: make dry run events more DRY by reusing types and payload logic - Create BaseBookingEventPayload type to eliminate duplication between regular and dry run events - Use intersection types to compose final event types in sdk-action-manager.ts - Create getBaseBookingEventPayload function for common payload generation logic - Export dry run payload functions and import them in tests instead of redefining - Fix missing videoCallUrl field in rescheduleBookingSuccessfulV2 type Co-Authored-By: hariom@cal.com * Remove test file --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> --- .../embed-core/src/sdk-action-manager.ts | 44 +++++----- .../Booker/components/hooks/useBookings.ts | 83 +++++++++++++++++-- 2 files changed, 94 insertions(+), 33 deletions(-) diff --git a/packages/embeds/embed-core/src/sdk-action-manager.ts b/packages/embeds/embed-core/src/sdk-action-manager.ts index 151e58861d..62921c18f8 100644 --- a/packages/embeds/embed-core/src/sdk-action-manager.ts +++ b/packages/embeds/embed-core/src/sdk-action-manager.ts @@ -9,6 +9,21 @@ function _fireEvent(fullName: string, detail: CustomEventDetail) { window.dispatchEvent(event); } +type BaseBookingEventPayload = { + title: string | undefined; + startTime: string | undefined; + endTime: string | undefined; + eventTypeId: number | null | undefined; + status: string | undefined; + paymentRequired: boolean; + isRecurring: boolean; + /** + * This is only used for recurring bookings + */ + allBookings?: { startTime: string; endTime: string }[]; + videoCallUrl?: string; +}; + export type EventDataMap = { eventTypeSelected: { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -24,20 +39,8 @@ export type EventDataMap = { linkReady: Record; __connectInitiated: Record; __connectCompleted: Record; - bookingSuccessfulV2: { + bookingSuccessfulV2: BaseBookingEventPayload & { uid: string | undefined; - title: string | undefined; - startTime: string | undefined; - endTime: string | undefined; - eventTypeId: number | null | undefined; - status: string | undefined; - paymentRequired: boolean; - isRecurring: boolean; - /** - * This is only used for recurring bookings - */ - allBookings?: { startTime: string; endTime: string }[]; - videoCallUrl?: string; }; /** @@ -56,20 +59,11 @@ export type EventDataMap = { }; confirmed: boolean; }; - rescheduleBookingSuccessfulV2: { + rescheduleBookingSuccessfulV2: BaseBookingEventPayload & { uid: string | undefined; - title: string | undefined; - startTime: string | undefined; - endTime: string | undefined; - eventTypeId: number | null | undefined; - status: string | undefined; - paymentRequired: boolean; - isRecurring: boolean; - /** - * This is only used for recurring bookings - */ - allBookings?: { startTime: string; endTime: string }[]; }; + dryRunBookingSuccessfulV2: BaseBookingEventPayload; + dryRunRescheduleBookingSuccessfulV2: BaseBookingEventPayload; /** * @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 6ca4358374..a612d0f728 100644 --- a/packages/features/bookings/Booker/components/hooks/useBookings.ts +++ b/packages/features/bookings/Booker/components/hooks/useBookings.ts @@ -57,6 +57,28 @@ export interface IUseBookings { isBookingDryRun?: boolean; } +const getBaseBookingEventPayload = (booking: { + title?: string; + startTime: string; + endTime: string; + eventTypeId?: number | null; + status?: BookingStatus; + paymentRequired: boolean; + isRecurring: boolean; + videoCallUrl?: string; +}) => { + return { + title: booking.title, + startTime: booking.startTime, + endTime: booking.endTime, + eventTypeId: booking.eventTypeId, + status: booking.status, + paymentRequired: booking.paymentRequired, + isRecurring: booking.isRecurring, + videoCallUrl: booking.videoCallUrl, + }; +}; + const getBookingSuccessfulEventPayload = (booking: { title?: string; startTime: string; @@ -70,18 +92,15 @@ const getBookingSuccessfulEventPayload = (booking: { }) => { 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, - videoCallUrl: booking.videoCallUrl, + ...getBaseBookingEventPayload(booking), }; }; const getRescheduleBookingSuccessfulEventPayload = getBookingSuccessfulEventPayload; + +export const getDryRunBookingSuccessfulEventPayload = getBaseBookingEventPayload; + +export const getDryRunRescheduleBookingSuccessfulEventPayload = getDryRunBookingSuccessfulEventPayload; export interface IUseBookingLoadingStates { creatingBooking: boolean; creatingRecurringBooking: boolean; @@ -196,6 +215,30 @@ export const useBookings = ({ mutationFn: createBooking, onSuccess: (booking) => { if (booking.isDryRun) { + const validDuration = event.data?.isDynamic + ? duration || event.data?.length + : duration && event.data?.metadata?.multipleDuration?.includes(duration) + ? duration + : event.data?.length; + + if (isRescheduling) { + sdkActionManager?.fire( + "dryRunRescheduleBookingSuccessfulV2", + getDryRunRescheduleBookingSuccessfulEventPayload({ + ...booking, + isRecurring: false, + }) + ); + } else { + sdkActionManager?.fire( + "dryRunBookingSuccessfulV2", + getDryRunBookingSuccessfulEventPayload({ + ...booking, + isRecurring: false, + }) + ); + } + router.push("/booking/dry-run-successful"); return; } @@ -343,6 +386,30 @@ export const useBookings = ({ const booking = bookings[0] || {}; if (booking.isDryRun) { + if (isRescheduling) { + sdkActionManager?.fire("dryRunRescheduleBookingSuccessfulV2", { + ...getDryRunRescheduleBookingSuccessfulEventPayload({ + ...booking, + isRecurring: true, + }), + allBookings: bookings.map((booking) => ({ + startTime: booking.startTime, + endTime: booking.endTime, + })), + }); + } else { + sdkActionManager?.fire("dryRunBookingSuccessfulV2", { + ...getDryRunBookingSuccessfulEventPayload({ + ...booking, + isRecurring: true, + }), + allBookings: bookings.map((booking) => ({ + startTime: booking.startTime, + endTime: booking.endTime, + })), + }); + } + router.push("/booking/dry-run-successful"); return; }