Skip to content

Commit 38cdf3b

Browse files
authored
fix: disable booking actions for cancelled/rejected/past bookings (calcom#26926)
* fix: disable booking actions for cancelled/rejected/past bookings * test: add tests for bookign actions disabled states
1 parent f7f4df6 commit 38cdf3b

2 files changed

Lines changed: 222 additions & 6 deletions

File tree

apps/web/components/booking/actions/bookingActions.test.ts

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,87 @@ describe("Booking Actions", () => {
374374
expect(rescheduleAction?.disabled).toBe(true);
375375
expect(rescheduleRequestAction?.disabled).toBe(true);
376376
});
377+
378+
it("should disable change_location for past bookings", () => {
379+
const context = createMockContext({ isBookingInPast: true });
380+
const actions = getEditEventActions(context);
381+
382+
const changeLocationAction = actions.find((a) => a.id === "change_location");
383+
expect(changeLocationAction?.disabled).toBe(true);
384+
});
385+
386+
it("should disable change_location for cancelled bookings", () => {
387+
const context = createMockContext({ isCancelled: true });
388+
const actions = getEditEventActions(context);
389+
390+
const changeLocationAction = actions.find((a) => a.id === "change_location");
391+
expect(changeLocationAction?.disabled).toBe(true);
392+
});
393+
394+
it("should disable add_members for rejected bookings", () => {
395+
const context = createMockContext({ isRejected: true });
396+
const actions = getEditEventActions(context);
397+
398+
const addMembersAction = actions.find((a) => a.id === "add_members");
399+
expect(addMembersAction?.disabled).toBe(true);
400+
});
401+
402+
it("should disable reroute for past bookings from routing form", () => {
403+
const context = createMockContext({
404+
isBookingFromRoutingForm: true,
405+
isBookingInPast: true,
406+
});
407+
const actions = getEditEventActions(context);
408+
409+
const rerouteAction = actions.find((a) => a.id === "reroute");
410+
expect(rerouteAction?.disabled).toBe(true);
411+
});
412+
413+
it("should disable reassign for cancelled round robin bookings", () => {
414+
const context = createMockContext({
415+
isCancelled: true,
416+
booking: {
417+
...createMockContext().booking,
418+
eventType: {
419+
...createMockContext().booking.eventType,
420+
schedulingType: SchedulingType.ROUND_ROBIN,
421+
hostGroups: [],
422+
},
423+
},
424+
});
425+
const actions = getEditEventActions(context);
426+
427+
const reassignAction = actions.find((a) => a.id === "reassign");
428+
expect(reassignAction?.disabled).toBe(true);
429+
});
430+
431+
it("should enable edit actions for upcoming active bookings", () => {
432+
const context = createMockContext({
433+
isBookingInPast: false,
434+
isCancelled: false,
435+
isRejected: false,
436+
isBookingFromRoutingForm: true,
437+
booking: {
438+
...createMockContext().booking,
439+
eventType: {
440+
...createMockContext().booking.eventType,
441+
schedulingType: SchedulingType.ROUND_ROBIN,
442+
hostGroups: [],
443+
},
444+
},
445+
});
446+
const actions = getEditEventActions(context);
447+
448+
const changeLocationAction = actions.find((a) => a.id === "change_location");
449+
const addMembersAction = actions.find((a) => a.id === "add_members");
450+
const rerouteAction = actions.find((a) => a.id === "reroute");
451+
const reassignAction = actions.find((a) => a.id === "reassign");
452+
453+
expect(changeLocationAction?.disabled).toBe(false);
454+
expect(addMembersAction?.disabled).toBe(false);
455+
expect(rerouteAction?.disabled).toBe(false);
456+
expect(reassignAction?.disabled).toBe(false);
457+
});
377458
});
378459

379460
describe("getAfterEventActions", () => {
@@ -523,6 +604,134 @@ describe("Booking Actions", () => {
523604

524605
expect(isActionDisabled("charge_card", context)).toBe(true);
525606
});
607+
608+
describe("change_location action", () => {
609+
it("should be disabled for past bookings", () => {
610+
const context = createMockContext({ isBookingInPast: true });
611+
expect(isActionDisabled("change_location", context)).toBe(true);
612+
});
613+
614+
it("should be disabled for cancelled bookings", () => {
615+
const context = createMockContext({ isCancelled: true });
616+
expect(isActionDisabled("change_location", context)).toBe(true);
617+
});
618+
619+
it("should be disabled for rejected bookings", () => {
620+
const context = createMockContext({ isRejected: true });
621+
expect(isActionDisabled("change_location", context)).toBe(true);
622+
});
623+
624+
it("should be enabled for upcoming active bookings", () => {
625+
const context = createMockContext({
626+
isBookingInPast: false,
627+
isCancelled: false,
628+
isRejected: false,
629+
});
630+
expect(isActionDisabled("change_location", context)).toBe(false);
631+
});
632+
});
633+
634+
describe("add_members action", () => {
635+
it("should be disabled for past bookings", () => {
636+
const context = createMockContext({ isBookingInPast: true });
637+
expect(isActionDisabled("add_members", context)).toBe(true);
638+
});
639+
640+
it("should be disabled for cancelled bookings", () => {
641+
const context = createMockContext({ isCancelled: true });
642+
expect(isActionDisabled("add_members", context)).toBe(true);
643+
});
644+
645+
it("should be disabled for rejected bookings", () => {
646+
const context = createMockContext({ isRejected: true });
647+
expect(isActionDisabled("add_members", context)).toBe(true);
648+
});
649+
650+
it("should be enabled for upcoming active bookings", () => {
651+
const context = createMockContext({
652+
isBookingInPast: false,
653+
isCancelled: false,
654+
isRejected: false,
655+
});
656+
expect(isActionDisabled("add_members", context)).toBe(false);
657+
});
658+
});
659+
660+
describe("reroute action", () => {
661+
it("should be disabled for past bookings", () => {
662+
const context = createMockContext({ isBookingInPast: true });
663+
expect(isActionDisabled("reroute", context)).toBe(true);
664+
});
665+
666+
it("should be disabled for cancelled bookings", () => {
667+
const context = createMockContext({ isCancelled: true });
668+
expect(isActionDisabled("reroute", context)).toBe(true);
669+
});
670+
671+
it("should be disabled for rejected bookings", () => {
672+
const context = createMockContext({ isRejected: true });
673+
expect(isActionDisabled("reroute", context)).toBe(true);
674+
});
675+
676+
it("should be enabled for upcoming active bookings", () => {
677+
const context = createMockContext({
678+
isBookingInPast: false,
679+
isCancelled: false,
680+
isRejected: false,
681+
});
682+
expect(isActionDisabled("reroute", context)).toBe(false);
683+
});
684+
});
685+
686+
describe("reassign action", () => {
687+
it("should be disabled for past bookings", () => {
688+
const context = createMockContext({ isBookingInPast: true });
689+
expect(isActionDisabled("reassign", context)).toBe(true);
690+
});
691+
692+
it("should be disabled for cancelled bookings", () => {
693+
const context = createMockContext({ isCancelled: true });
694+
expect(isActionDisabled("reassign", context)).toBe(true);
695+
});
696+
697+
it("should be disabled for rejected bookings", () => {
698+
const context = createMockContext({ isRejected: true });
699+
expect(isActionDisabled("reassign", context)).toBe(true);
700+
});
701+
702+
it("should be enabled for upcoming active bookings", () => {
703+
const context = createMockContext({
704+
isBookingInPast: false,
705+
isCancelled: false,
706+
isRejected: false,
707+
});
708+
expect(isActionDisabled("reassign", context)).toBe(false);
709+
});
710+
});
711+
712+
describe("cancel action with cancelled/rejected states", () => {
713+
it("should be disabled for already cancelled bookings", () => {
714+
const context = createMockContext({ isCancelled: true });
715+
expect(isActionDisabled("cancel", context)).toBe(true);
716+
});
717+
718+
it("should be disabled for rejected bookings", () => {
719+
const context = createMockContext({ isRejected: true });
720+
expect(isActionDisabled("cancel", context)).toBe(true);
721+
});
722+
});
723+
724+
describe("reschedule_request action with cancelled/rejected states", () => {
725+
it("should be disabled for cancelled bookings", () => {
726+
const context = createMockContext({ isCancelled: true });
727+
expect(isActionDisabled("reschedule_request", context)).toBe(true);
728+
});
729+
730+
it("should be disabled for rejected bookings", () => {
731+
const context = createMockContext({ isRejected: true });
732+
expect(isActionDisabled("reschedule_request", context)).toBe(true);
733+
});
734+
});
526735
});
527736

528737
describe("getActionLabel", () => {

apps/web/components/booking/actions/bookingActions.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,29 +148,29 @@ export function getEditEventActions(context: BookingActionContext): ActionType[]
148148
id: "reroute",
149149
label: t("reroute"),
150150
icon: "waypoints",
151-
disabled: false,
151+
disabled: isActionDisabled("reroute", context),
152152
}
153153
: null,
154154
{
155155
id: "change_location",
156156
label: t("edit_location"),
157157
icon: "map-pin",
158-
disabled: false,
158+
disabled: isActionDisabled("change_location", context),
159159
},
160160
booking.eventType?.disableGuests
161161
? null
162162
: {
163163
id: "add_members",
164164
label: t("additional_guests"),
165165
icon: "user-plus",
166-
disabled: false,
166+
disabled: isActionDisabled("add_members", context),
167167
},
168168
isReassignable
169169
? {
170170
id: "reassign",
171171
label: t("reassign"),
172172
icon: "users",
173-
disabled: false,
173+
disabled: isActionDisabled("reassign", context),
174174
}
175175
: null,
176176
];
@@ -237,7 +237,7 @@ export function shouldShowIndividualReportButton(context: BookingActionContext):
237237
}
238238

239239
export function isActionDisabled(actionId: string, context: BookingActionContext): boolean {
240-
const { booking, isBookingInPast, isDisabledRescheduling, isDisabledCancelling, isAttendee } = context;
240+
const { booking, isBookingInPast, isDisabledRescheduling, isDisabledCancelling, isAttendee, isCancelled, isRejected } = context;
241241

242242
switch (actionId) {
243243
case "reschedule":
@@ -256,18 +256,25 @@ export function isActionDisabled(actionId: string, context: BookingActionContext
256256
booking.eventType.minimumRescheduleNotice ?? null
257257
);
258258
return (
259+
isCancelled ||
260+
isRejected ||
259261
(isBookingInPast && !booking.eventType.allowReschedulingPastBookings) ||
260262
isDisabledRescheduling ||
261263
isWithinMinimumNotice
262264
);
263265
case "cancel":
264-
return isDisabledCancelling || isBookingInPast;
266+
return isDisabledCancelling || isBookingInPast || isCancelled || isRejected;
265267
case "view_recordings":
266268
return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation);
267269
case "meeting_session_details":
268270
return !(isBookingInPast && booking.status === BookingStatus.ACCEPTED && context.isCalVideoLocation);
269271
case "charge_card":
270272
return context.cardCharged;
273+
case "reroute":
274+
case "reassign":
275+
case "change_location":
276+
case "add_members":
277+
return isBookingInPast || isCancelled || isRejected;
271278
default:
272279
return false;
273280
}

0 commit comments

Comments
 (0)