From cff3778e2dc975afd4b3650a205a949bc6eadf1f Mon Sep 17 00:00:00 2001 From: GurneeshBudhiraja Date: Tue, 23 Sep 2025 18:39:12 -0700 Subject: [PATCH 01/10] Fixes #24017: Allow the host to cancel the event when `Disable Rescheduling` is enabled --- .../components/booking/BookingListItem.tsx | 3 ++ .../components/booking/bookingActions.test.ts | 42 ++++++++++++++++++- apps/web/components/booking/bookingActions.ts | 15 +++++-- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index f673aa9f6440fb..4698925edb5e39 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -189,6 +189,8 @@ function BookingListItem(booking: BookingItemProps) { const isAttendee = !!userSeat; + const isHost = booking.loggedInUser.userId === booking.user?.id; + const paymentAppData = getPaymentAppData(booking.eventType); const location = booking.location as ReturnType; @@ -253,6 +255,7 @@ function BookingListItem(booking: BookingItemProps) { showPendingPayment: paymentAppData.enabled && booking.payment.length && !booking.paid, isAttendee, cardCharged, + isHost, attendeeList, getSeatReferenceUid, t, diff --git a/apps/web/components/booking/bookingActions.test.ts b/apps/web/components/booking/bookingActions.test.ts index a0ee856bc2f3ec..f0292bb9c57558 100644 --- a/apps/web/components/booking/bookingActions.test.ts +++ b/apps/web/components/booking/bookingActions.test.ts @@ -54,7 +54,7 @@ function createMockContext(overrides: Partial = {}): Booki locale: "en", bookingId: 1, noShow: false, - } as any, + }, ], user: { id: 1, @@ -503,6 +503,46 @@ describe("Booking Actions", () => { expect(isActionDisabled("cancel", futureContext)).toBe(false); }); + it("should allow hosts to cancel bookings even when cancellation is disabled", () => { + const hostContext = createMockContext({ + isHost: true, + isDisabledCancelling: true, + isBookingInPast: false, + }); + + expect(isActionDisabled("cancel", hostContext)).toBe(false); + }); + + it("should allow hosts to cancel past bookings", () => { + const hostContext = createMockContext({ + isHost: true, + isBookingInPast: true, + isDisabledCancelling: false, + }); + + expect(isActionDisabled("cancel", hostContext)).toBe(false); + }); + + it("should prevent non-hosts from cancelling when cancellation is disabled", () => { + const nonHostContext = createMockContext({ + isHost: false, + isDisabledCancelling: true, + isBookingInPast: false, + }); + + expect(isActionDisabled("cancel", nonHostContext)).toBe(true); + }); + + it("should prevent non-hosts from cancelling past bookings", () => { + const nonHostContext = createMockContext({ + isHost: false, + isDisabledCancelling: false, + isBookingInPast: true, + }); + + expect(isActionDisabled("cancel", nonHostContext)).toBe(true); + }); + it("should disable video actions for non-past bookings", () => { const context = createMockContext({ isBookingInPast: false }); diff --git a/apps/web/components/booking/bookingActions.ts b/apps/web/components/booking/bookingActions.ts index f98e99266a5d2a..49e5870187ab41 100644 --- a/apps/web/components/booking/bookingActions.ts +++ b/apps/web/components/booking/bookingActions.ts @@ -23,6 +23,7 @@ export interface BookingActionContext { showPendingPayment: boolean; isAttendee: boolean; cardCharged: boolean; + isHost?: boolean; attendeeList: Array<{ name: string; email: string; @@ -204,14 +205,22 @@ export function shouldShowRecurringCancelAction(context: BookingActionContext): } export function isActionDisabled(actionId: string, context: BookingActionContext): boolean { - const { booking, isBookingInPast, isDisabledRescheduling, isDisabledCancelling, isPending, isConfirmed } = - context; + const { + booking, + isBookingInPast, + isDisabledRescheduling, + isDisabledCancelling, + isPending: _isPending, + isConfirmed: _isConfirmed, + isHost, + } = context; switch (actionId) { case "reschedule": case "reschedule_request": return (isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling; case "cancel": + if (isHost) return false; return isDisabledCancelling || isBookingInPast; case "view_recordings": return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation); @@ -225,7 +234,7 @@ export function isActionDisabled(actionId: string, context: BookingActionContext } export function getActionLabel(actionId: string, context: BookingActionContext): string { - const { booking, isTabRecurring, isRecurring, attendeeList, cardCharged, t } = context; + const { booking: _booking, isTabRecurring, isRecurring, attendeeList, cardCharged, t } = context; switch (actionId) { case "reject": From f13782b87b5f3ed02f089838d1cae3108f97bcb5 Mon Sep 17 00:00:00 2001 From: GurneeshBudhiraja Date: Tue, 23 Sep 2025 19:06:29 -0700 Subject: [PATCH 02/10] Update: Add the test in `bookingActions.test.ts` to test that the hosts can cancel event when cancellation is disabled --- .../components/booking/bookingActions.test.ts | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/apps/web/components/booking/bookingActions.test.ts b/apps/web/components/booking/bookingActions.test.ts index f0292bb9c57558..56a2b7b2313569 100644 --- a/apps/web/components/booking/bookingActions.test.ts +++ b/apps/web/components/booking/bookingActions.test.ts @@ -504,43 +504,13 @@ describe("Booking Actions", () => { }); it("should allow hosts to cancel bookings even when cancellation is disabled", () => { - const hostContext = createMockContext({ - isHost: true, - isDisabledCancelling: true, - isBookingInPast: false, - }); - - expect(isActionDisabled("cancel", hostContext)).toBe(false); - }); - - it("should allow hosts to cancel past bookings", () => { - const hostContext = createMockContext({ + const context = createMockContext({ isHost: true, - isBookingInPast: true, - isDisabledCancelling: false, - }); - - expect(isActionDisabled("cancel", hostContext)).toBe(false); - }); - - it("should prevent non-hosts from cancelling when cancellation is disabled", () => { - const nonHostContext = createMockContext({ - isHost: false, isDisabledCancelling: true, isBookingInPast: false, }); - expect(isActionDisabled("cancel", nonHostContext)).toBe(true); - }); - - it("should prevent non-hosts from cancelling past bookings", () => { - const nonHostContext = createMockContext({ - isHost: false, - isDisabledCancelling: false, - isBookingInPast: true, - }); - - expect(isActionDisabled("cancel", nonHostContext)).toBe(true); + expect(isActionDisabled("cancel", context)).toBe(false); }); it("should disable video actions for non-past bookings", () => { From 786b7020cc138645f15043439b3ab7cfeae702a4 Mon Sep 17 00:00:00 2001 From: GurneeshBudhiraja Date: Tue, 23 Sep 2025 19:25:54 -0700 Subject: [PATCH 03/10] Fixes #24017: Updates the logic for verifying the `isHost` --- apps/web/components/booking/BookingListItem.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index 4698925edb5e39..7d256c637ba567 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -189,7 +189,11 @@ function BookingListItem(booking: BookingItemProps) { const isAttendee = !!userSeat; - const isHost = booking.loggedInUser.userId === booking.user?.id; + // Checks if the logged in user is the host of the booking + const isHost = + booking.user?.id != null && + booking.loggedInUser.userId != null && + booking.loggedInUser.userId === booking.user.id; const paymentAppData = getPaymentAppData(booking.eventType); From 35d6eb1db72dd78060404272fc272aad2c78c263 Mon Sep 17 00:00:00 2001 From: GurneeshBudhiraja Date: Tue, 23 Sep 2025 23:43:54 -0700 Subject: [PATCH 04/10] update `handleCancelBooking` utility to allow host cancellation when the event is disabled for cancellation --- .../bookings/lib/handleCancelBooking.ts | 11 +- .../test/handleCancelBooking.test.ts | 128 ++++++++++++++++++ 2 files changed, 134 insertions(+), 5 deletions(-) diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 24e86413baec21..988c249fb0c6e3 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -5,6 +5,7 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils"; import dayjs from "@calcom/dayjs"; import { sendCancelledEmailsAndSMS } from "@calcom/emails"; +import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation"; import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund"; @@ -17,7 +18,6 @@ import { } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; -import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; @@ -107,16 +107,17 @@ async function handler(input: CancelBookingInput) { throw new HttpError({ statusCode: 400, message: "User not found" }); } - if (bookingToDelete.eventType?.disableCancelling) { + const isCancellationUserHost = + bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy; + + // Only the host can cancel the booking even when the cancellation is disabled for the event + if (!isCancellationUserHost && bookingToDelete.eventType?.disableCancelling) { throw new HttpError({ statusCode: 400, message: "This event type does not allow cancellations", }); } - const isCancellationUserHost = - bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy; - if (!platformClientId && !cancellationReason?.trim() && isCancellationUserHost) { throw new HttpError({ statusCode: 400, diff --git a/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts b/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts index a99278e8defd10..ae4f807937c986 100644 --- a/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts +++ b/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts @@ -942,4 +942,132 @@ describe("Cancel Booking", () => { expect(result.success).toBe(true); }); + + test("Should allow host to cancel booking even when cancellation is disabled", async () => { + const handleCancelBooking = (await import("@calcom/features/bookings/lib/handleCancelBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + credentials: [getGoogleCalendarCredential()], + selectedCalendars: [TestData.selectedCalendars.google], + }); + + const uidOfBookingToBeCancelled = "host-cancel-disabled-test"; + const idOfBookingToBeCancelled = 5000; + const { dateString: plus1DateString } = getDate({ dateIncrement: 2 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: 30, + length: 30, + users: [{ id: 101 }], + disableCancelling: true, + }, + ], + bookings: [ + { + id: idOfBookingToBeCancelled, + uid: uidOfBookingToBeCancelled, + eventTypeId: 1, + userId: 101, + responses: { + name: booker.name, + email: booker.email, + }, + status: BookingStatus.ACCEPTED, + startTime: `${plus1DateString}T04:00:00.000Z`, + endTime: `${plus1DateString}T04:30:00.000Z`, + }, + ], + users: [organizer], + }) + ); + + const result = await handleCancelBooking({ + bookingData: { + id: idOfBookingToBeCancelled, + uid: uidOfBookingToBeCancelled, + cancelledBy: organizer.email, + cancellationReason: "Host cancellation", + }, + userId: organizer.id, + }); + + expect(result.success).toBe(true); + }); + + test("Should prevent non-host from canceling when cancellation is disabled", async () => { + const handleCancelBooking = (await import("@calcom/features/bookings/lib/handleCancelBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + credentials: [getGoogleCalendarCredential()], + selectedCalendars: [TestData.selectedCalendars.google], + }); + + const uidOfBookingToBeCancelled = "non-host-cancel-disabled-test"; + const idOfBookingToBeCancelled = 5001; + const { dateString: plus1DateString } = getDate({ dateIncrement: 2 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: 30, + length: 30, + users: [{ id: 101 }], + disableCancelling: true, + }, + ], + bookings: [ + { + id: idOfBookingToBeCancelled, + uid: uidOfBookingToBeCancelled, + eventTypeId: 1, + userId: 101, + responses: { + name: booker.name, + email: booker.email, + }, + status: BookingStatus.ACCEPTED, + startTime: `${plus1DateString}T04:00:00.000Z`, + endTime: `${plus1DateString}T04:30:00.000Z`, + }, + ], + users: [organizer], + }) + ); + + await expect( + handleCancelBooking({ + bookingData: { + id: idOfBookingToBeCancelled, + uid: uidOfBookingToBeCancelled, + cancelledBy: booker.email, + cancellationReason: "Attendee cancellation", + }, + userId: 999, + }) + ).rejects.toThrow("This event type does not allow cancellations"); + }); }); From 635059c7a64544cde352154db09a4ec2d0bce571 Mon Sep 17 00:00:00 2001 From: GurneeshBudhiraja Date: Thu, 27 Nov 2025 18:26:00 -0800 Subject: [PATCH 05/10] prevent the host from cancelling the past meetings --- apps/web/components/booking/actions/bookingActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/booking/actions/bookingActions.ts b/apps/web/components/booking/actions/bookingActions.ts index e742f8217da8dd..37a0c8f6d9728b 100644 --- a/apps/web/components/booking/actions/bookingActions.ts +++ b/apps/web/components/booking/actions/bookingActions.ts @@ -235,7 +235,7 @@ export function isActionDisabled(actionId: string, context: BookingActionContext case "reschedule_request": return (isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling; case "cancel": - if (isHost) return false; + if (isHost && !isBookingInPast) return false; return isDisabledCancelling || isBookingInPast; case "view_recordings": return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation); From 6b98b56e6bdc833abbc61cec0d37b336700a37bc Mon Sep 17 00:00:00 2001 From: GurneeshBudhiraja Date: Thu, 27 Nov 2025 19:01:35 -0800 Subject: [PATCH 06/10] Changes: add more checks for the `userId` to determine the user host and update the test --- .../bookings/lib/handleCancelBooking.ts | 40 ++++++++++++++----- .../test/handleCancelBooking.test.ts | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 1ec60539d67149..89741b5dfc8079 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -109,8 +109,26 @@ async function handler(input: CancelBookingInput) { throw new HttpError({ statusCode: 400, message: "User not found" }); } - const isCancellationUserHost = - bookingToDelete.userId == userId || bookingToDelete.user.email === cancelledBy; + let isCancellationUserHost = false; + if (userId) { + if (bookingToDelete.userId === userId) { + isCancellationUserHost = true; + } + else if (bookingToDelete.eventType?.hosts?.some((host) => host.user.id === userId)) { + isCancellationUserHost = true; + } + else if (bookingToDelete.eventType?.owner?.id === userId) { + isCancellationUserHost = true; + } + else if ( + await PrismaOrgMembershipRepository.isLoggedInUserOrgAdminOfBookingHost( + userId, + bookingToDelete.userId + ) + ) { + isCancellationUserHost = true; + } + } // Only the host can cancel the booking even when the cancellation is disabled for the event if (!isCancellationUserHost && bookingToDelete.eventType?.disableCancelling) { @@ -293,17 +311,17 @@ async function handler(input: CancelBookingInput) { destinationCalendar: bookingToDelete?.destinationCalendar ? [bookingToDelete?.destinationCalendar] : bookingToDelete?.user.destinationCalendar - ? [bookingToDelete?.user.destinationCalendar] - : [], + ? [bookingToDelete?.user.destinationCalendar] + : [], cancellationReason: cancellationReason, ...(teamMembers && teamId && { - team: { - name: bookingToDelete?.eventType?.team?.name || "Nameless", - members: teamMembers, - id: teamId, - }, - }), + team: { + name: bookingToDelete?.eventType?.team?.name || "Nameless", + members: teamMembers, + id: teamId, + }, + }), seatsPerTimeSlot: bookingToDelete.eventType?.seatsPerTimeSlot, seatsShowAttendees: bookingToDelete.eventType?.seatsShowAttendees, iCalUID: bookingToDelete.iCalUID, @@ -617,7 +635,7 @@ type BookingCancelServiceDependencies = { * Handles both individual booking cancellations and bulk cancellations for recurring events. */ export class BookingCancelService implements IBookingCancelService { - constructor(private readonly deps: BookingCancelServiceDependencies) {} + constructor(private readonly deps: BookingCancelServiceDependencies) { } async cancelBooking(input: { bookingData: CancelRegularBookingData; bookingMeta?: CancelBookingMeta }) { const cancelBookingInput: CancelBookingInput = { diff --git a/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts b/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts index dabcdc8b5df049..96e5861ee320a0 100644 --- a/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts +++ b/packages/features/bookings/lib/handleCancelBooking/test/handleCancelBooking.test.ts @@ -561,7 +561,6 @@ describe("Cancel Booking", () => { }) ); - // This should throw an error with current implementation await expect( handleCancelBooking({ bookingData: { @@ -569,6 +568,7 @@ describe("Cancel Booking", () => { uid: uidOfBookingToBeCancelled, cancelledBy: organizer.email, }, + userId: organizer.id, }) ).rejects.toThrow("Cancellation reason is required when you are the host"); }); From 18ac758d4146724f9e3aa1045813f4e976b52e7c Mon Sep 17 00:00:00 2001 From: Romit Date: Thu, 5 Feb 2026 05:16:51 +0530 Subject: [PATCH 07/10] update webhook delete confirmation message --- apps/web/public/static/locales/en/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 13af35248e98ca..e146e10b3b4908 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -254,7 +254,7 @@ "hey_there": "Hey there", "there": "there", "forgot_your_password_calcom": "Forgot your password? - {{appName}}", - "delete_webhook_confirmation_message": "Are you sure you want to delete this webhook? You will no longer receive {{appName}} meeting data at a specified URL, in real-time, when an event is scheduled or canceled.", + "delete_webhook_confirmation_message": "Are you sure you want to delete this webhook? You will no longer receive {{appName}} data at the specified URL for your configured event triggers.", "confirm_delete_webhook": "Delete webhook", "edit_webhook": "Edit webhook", "delete_webhook": "Delete webhook", From 0148bdb3d15b05848ab64a6826446812663ddc4f Mon Sep 17 00:00:00 2001 From: Romit Date: Thu, 5 Feb 2026 06:02:48 +0530 Subject: [PATCH 08/10] revert --- apps/web/public/static/locales/en/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index e146e10b3b4908..13af35248e98ca 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -254,7 +254,7 @@ "hey_there": "Hey there", "there": "there", "forgot_your_password_calcom": "Forgot your password? - {{appName}}", - "delete_webhook_confirmation_message": "Are you sure you want to delete this webhook? You will no longer receive {{appName}} data at the specified URL for your configured event triggers.", + "delete_webhook_confirmation_message": "Are you sure you want to delete this webhook? You will no longer receive {{appName}} meeting data at a specified URL, in real-time, when an event is scheduled or canceled.", "confirm_delete_webhook": "Delete webhook", "edit_webhook": "Edit webhook", "delete_webhook": "Delete webhook", From 9348f9c0b3df7187da82d6acad6be8dddd79c86a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:47:57 +0000 Subject: [PATCH 09/10] fix: prevent hosts from cancelling already cancelled/rejected bookings Address review feedback from cubic-dev-ai: - Add checks for isCancelled and isRejected in the isHost bypass - Add tests to verify hosts cannot cancel already cancelled or rejected bookings Co-authored-by: GurneeshBudhiraja Co-Authored-By: unknown <> --- .../booking/actions/bookingActions.test.ts | 22 +++++++++++++++++++ .../booking/actions/bookingActions.ts | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/web/components/booking/actions/bookingActions.test.ts b/apps/web/components/booking/actions/bookingActions.test.ts index 51c79dce189a25..24250ca97e6294 100644 --- a/apps/web/components/booking/actions/bookingActions.test.ts +++ b/apps/web/components/booking/actions/bookingActions.test.ts @@ -602,6 +602,28 @@ describe("Booking Actions", () => { expect(isActionDisabled("cancel", context)).toBe(false); }); + it("should not allow hosts to cancel already cancelled bookings", () => { + const context = createMockContext({ + isHost: true, + isDisabledCancelling: true, + isBookingInPast: false, + isCancelled: true, + }); + + expect(isActionDisabled("cancel", context)).toBe(true); + }); + + it("should not allow hosts to cancel rejected bookings", () => { + const context = createMockContext({ + isHost: true, + isDisabledCancelling: true, + isBookingInPast: false, + isRejected: true, + }); + + expect(isActionDisabled("cancel", context)).toBe(true); + }); + it("should disable video actions for non-past bookings", () => { const context = createMockContext({ isBookingInPast: false }); diff --git a/apps/web/components/booking/actions/bookingActions.ts b/apps/web/components/booking/actions/bookingActions.ts index a11f5015c5c9da..8f21304da4b5d8 100644 --- a/apps/web/components/booking/actions/bookingActions.ts +++ b/apps/web/components/booking/actions/bookingActions.ts @@ -264,7 +264,7 @@ export function isActionDisabled(actionId: string, context: BookingActionContext isWithinMinimumNotice ); case "cancel": - if (isHost && !isBookingInPast) return false; + if (isHost && !isBookingInPast && !isCancelled && !isRejected) return false; return isDisabledCancelling || isBookingInPast || isCancelled || isRejected; case "view_recordings": return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation); From 7fe57a91d755b23c394f9c286222f0c6f6f37eca Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:56:51 +0000 Subject: [PATCH 10/10] fix: add missing isHost to BookingActionsDropdown actionContext The isHost flag was computed but not included in the actionContext, causing the host bypass for disableCancelling to not work in the dropdown component. Co-authored-by: GurneeshBudhiraja Co-Authored-By: unknown <> --- apps/web/components/booking/actions/BookingActionsDropdown.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/components/booking/actions/BookingActionsDropdown.tsx b/apps/web/components/booking/actions/BookingActionsDropdown.tsx index 3bea40af9f5a00..2f876aa3093af7 100644 --- a/apps/web/components/booking/actions/BookingActionsDropdown.tsx +++ b/apps/web/components/booking/actions/BookingActionsDropdown.tsx @@ -254,6 +254,7 @@ export function BookingActionsDropdown({ showPendingPayment, isAttendee, cardCharged, + isHost, attendeeList, getSeatReferenceUid, t,