fix: Broken reschedule page for migrated user's booking through request-reschedule (#16068)

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
authored and GitHub committed 2024-08-29 14:35:32 -07:00
1 parent 17f537774c
commit 88f46a6b20
8 files changed
+62 -52

No files matched your search

+20 -8
View File
@@ -16,6 +16,16 @@ export default function Type() {
return null;
}
const querySchema = z.object({
uid: z.string(),
seatReferenceUid: z.string().optional(),
rescheduledBy: z.string().optional(),
allowRescheduleForCancelledBooking: z
.string()
.transform((value) => value === "true")
.optional(),
});
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getServerSession(context);
@@ -23,13 +33,12 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
uid: bookingUid,
seatReferenceUid,
rescheduledBy,
} = z
.object({
uid: z.string(),
seatReferenceUid: z.string().optional(),
rescheduledBy: z.string().optional(),
})
.parse(context.query);
/**
* This is for the case of request-reschedule where the booking is cancelled
*/
allowRescheduleForCancelledBooking,
} = querySchema.parse(context.query);
const coepFlag = context.query["flag.coep"];
const { uid, seatReferenceUid: maybeSeatReferenceUid } = await maybeGetBookingUidFromSeat(
prisma,
@@ -90,7 +99,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
// If booking is already CANCELLED or REJECTED, we can't reschedule this booking. Take the user to the booking page which would show it's correct status and other details.
// A booking that has been rescheduled to a new booking will also have a status of CANCELLED
if (booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED) {
if (
!allowRescheduleForCancelledBooking &&
(booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED)
) {
return {
redirect: {
destination: `/booking/${uid}`,
@@ -34,7 +34,7 @@ import type { EventBusyDate, IntervalLimit } from "@calcom/types/Calendar";
import { getMockPaymentService } from "./MockPaymentService";
import type { getMockRequestDataForBooking } from "./getMockRequestDataForBooking";
logger.settings.minLevel = 0;
logger.settings.minLevel = 1;
const log = logger.getSubLogger({ prefix: ["[bookingScenario]"] });
type InputWebhook = {
@@ -143,10 +143,9 @@ expect.extend({
if (!isEmailContentMatched) {
logger.silly("All Emails", JSON.stringify({ numEmails: emailsToLog.length, emailsToLog }));
return {
pass: false,
message: () => `Email content ${isNot ? "is" : "is not"} matching. ${JSON.stringify(emailsToLog)}`,
message: () => `Email content ${isNot ? "is" : "is not"} matching.`,
actual: actualEmailContent,
expected: expectedEmailContent,
};
@@ -818,8 +817,6 @@ export function expectBookingRequestRescheduledEmails({
loggedInUser,
booker,
booking,
bookNewTimePath,
organizer,
}: {
emails: Fixtures["emails"];
organizer: ReturnType<typeof getOrganizer>;
@@ -840,7 +837,7 @@ export function expectBookingRequestRescheduledEmails({
subHeading: "request_reschedule_subtitle",
links: [
{
href: `${bookingUrlOrigin}${bookNewTimePath}?rescheduleUid=${booking.uid}`,
href: `${bookingUrlOrigin}/reschedule/${booking.uid}?allowRescheduleForCancelledBooking=true`,
text: "Book a new time",
},
],
+10 -27
View File
@@ -1,10 +1,9 @@
import type { Booking } from "@prisma/client";
import { Prisma } from "@prisma/client";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getRescheduleLink } from "@calcom/lib/CalEventParser";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { getTranslation } from "@calcom/lib/server/i18n";
@@ -238,32 +237,16 @@ export class CalendarEventBuilder implements ICalendarEventBuilder {
}
}
public buildRescheduleLink(booking: Partial<Booking>, eventType?: CalendarEventBuilder["eventType"]) {
public buildRescheduleLink({
allowRescheduleForCancelledBooking = false,
}: {
allowRescheduleForCancelledBooking?: boolean;
} = {}) {
try {
if (!booking) {
throw new Error("Parameter booking is required to build reschedule link");
}
const isTeam = !!eventType && !!eventType.teamId;
const isDynamic = booking?.dynamicEventSlugRef && booking?.dynamicGroupSlugRef;
let slug = "";
if (isTeam && eventType?.team?.slug) {
slug = `team/${eventType.team?.slug}/${eventType.slug}`;
} else if (isDynamic) {
const dynamicSlug = isDynamic ? `${booking.dynamicGroupSlugRef}/${booking.dynamicEventSlugRef}` : "";
slug = dynamicSlug;
} else if (eventType?.slug) {
slug = `${this.users[0].username}/${eventType.slug}`;
}
const queryParams = new URLSearchParams();
queryParams.set("rescheduleUid", `${booking.uid}`);
slug = `${slug}`;
const rescheduleLink = `${
this.calendarEvent.bookerUrl ?? WEBAPP_URL
}/${slug}?${queryParams.toString()}`;
this.rescheduleLink = rescheduleLink;
this.rescheduleLink = getRescheduleLink({
calEvent: this.calendarEvent,
allowRescheduleForCancelledBooking,
});
} catch (error) {
if (error instanceof Error) {
throw new Error(`buildRescheduleLink.error: ${error.message}`);
@@ -37,7 +37,14 @@ export class CalendarEventDirector {
this.cancellationReason = reason;
}
public async buildForRescheduleEmail(): Promise<void> {
public async buildForRescheduleEmail({
allowRescheduleForCancelledBooking = false,
}: {
/**
* By default we don't want to allow reschedule for cancelled bookings.
*/
allowRescheduleForCancelledBooking?: boolean;
} = {}): Promise<void> {
if (this.existingBooking && this.existingBooking.eventTypeId && this.existingBooking.uid) {
await this.builder.buildEventObjectFromInnerClass(this.existingBooking.eventTypeId);
await this.builder.buildUsersFromInnerClass();
@@ -47,7 +54,7 @@ export class CalendarEventDirector {
this.builder.setCancellationReason(this.cancellationReason);
this.builder.setDescription(this.builder.eventType.description);
this.builder.setNotes(this.existingBooking.description);
this.builder.buildRescheduleLink(this.existingBooking, this.builder.eventType);
this.builder.buildRescheduleLink({ allowRescheduleForCancelledBooking });
log.debug(
"buildForRescheduleEmail",
safeStringify({ existingBooking: this.existingBooking, builder: this.builder })
@@ -65,7 +72,7 @@ export class CalendarEventDirector {
this.builder.setUId(this.existingBooking.uid);
this.builder.setCancellationReason(this.cancellationReason);
this.builder.setDescription(this.existingBooking.description);
await this.builder.buildRescheduleLink(this.existingBooking);
await this.builder.buildRescheduleLink();
} else {
throw new Error("buildWithoutEventTypeForRescheduleEmail.missing.params.required");
}
@@ -6,7 +6,7 @@ export function ManageLink(props: { calEvent: CalendarEvent; attendee: Person })
// Guests cannot
const t = props.attendee.language.translate;
const cancelLink = getCancelLink(props.calEvent, props.attendee);
const rescheduleLink = getRescheduleLink(props.calEvent, props.attendee);
const rescheduleLink = getRescheduleLink({ calEvent: props.calEvent, attendee: props.attendee });
const bookingLink = getBookingUrl(props.calEvent);
const isOriginalAttendee = props.attendee.email === props.calEvent.attendees[0]?.email;
+16 -6
View File
@@ -183,7 +183,7 @@ export const getPlatformManageLink = (calEvent: CalendarEvent, t: TFunction) =>
if (!calEvent.recurringEvent && calEvent.platformRescheduleUrl) {
res += `${calEvent.platformCancelUrl ? ` ${t("or_lowercase")} ` : ""}${t(
"reschedule"
)}: ${getRescheduleLink(calEvent)}`;
)}: ${getRescheduleLink({ calEvent })}`;
}
return res;
@@ -252,7 +252,15 @@ export const getPlatformRescheduleLink = (
return "";
};
export const getRescheduleLink = (calEvent: CalendarEvent, attendee?: Person): string => {
export const getRescheduleLink = ({
calEvent,
allowRescheduleForCancelledBooking = false,
attendee,
}: {
calEvent: CalendarEvent;
allowRescheduleForCancelledBooking?: boolean;
attendee?: Person;
}): string => {
const Uid = getUid(calEvent);
const seatUid = getSeatReferenceId(calEvent);
@@ -260,13 +268,15 @@ export const getRescheduleLink = (calEvent: CalendarEvent, attendee?: Person): s
return getPlatformRescheduleLink(calEvent, Uid, seatUid);
}
const rescheduleLink = new URL(`${calEvent.bookerUrl ?? WEBAPP_URL}/reschedule/${seatUid ? seatUid : Uid}`);
const url = new URL(`${calEvent.bookerUrl ?? WEBAPP_URL}/reschedule/${seatUid ? seatUid : Uid}`);
if (allowRescheduleForCancelledBooking) {
url.searchParams.append("allowRescheduleForCancelledBooking", "true");
}
if (attendee?.email) {
rescheduleLink.searchParams.append("rescheduledBy", attendee.email);
url.searchParams.append("rescheduledBy", attendee.email);
}
return rescheduleLink.toString();
return url.toString();
};
export const getRichDescription = (
@@ -205,7 +205,8 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
director.setExistingBooking(bookingToReschedule);
cancellationReason && director.setCancellationReason(cancellationReason);
if (Object.keys(event).length) {
await director.buildForRescheduleEmail();
// Request Reschedule flow first cancels the booking and then reschedule email is sent. So, we need to allow reschedule for cancelled booking
await director.buildForRescheduleEmail({ allowRescheduleForCancelledBooking: true });
} else {
await director.buildWithoutEventTypeForRescheduleEmail();
}