diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx
index 4baccf7a35..eb7cc34535 100644
--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -118,7 +118,7 @@ function BookingListItem(booking: BookingItemProps) {
label: isTabRecurring && isRecurring ? t("cancel_all_remaining") : t("cancel"),
/* When cancelling we need to let the UI and the API know if the intention is to
cancel all remaining bookings or just that booking instance. */
- href: `/success?uid=${booking.uid}&cancel=true${
+ href: `/booking/${booking.uid}?cancel=true${
isTabRecurring && isRecurring ? "&allRemainingBookings=true" : ""
}`,
icon: Icon.FiX,
@@ -195,11 +195,9 @@ function BookingListItem(booking: BookingItemProps) {
const onClickTableData = () => {
router.push({
- pathname: "/success",
+ pathname: `/booking/${booking.uid}`,
query: {
- uid: booking.uid,
allRemainingBookings: isTabRecurring,
- listingStatus: booking.listingStatus,
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
},
});
diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx
index 5404f51b55..d15a239b77 100644
--- a/apps/web/components/booking/pages/BookingPage.tsx
+++ b/apps/web/components/booking/pages/BookingPage.tsx
@@ -136,9 +136,8 @@ const BookingPage = ({
}
return router.push({
- pathname: "/success",
+ pathname: `/booking/${uid}`,
query: {
- uid,
isSuccessBookingPage: true,
email: bookingForm.getValues("email"),
eventTypeSlug: eventType.slug,
@@ -152,9 +151,8 @@ const BookingPage = ({
const { uid } = responseData[0] || {};
return router.push({
- pathname: "/success",
+ pathname: `/booking/${uid}`,
query: {
- uid,
allRemainingBookings: true,
email: bookingForm.getValues("email"),
eventTypeSlug: eventType.slug,
diff --git a/apps/web/components/dialog/RescheduleDialog.tsx b/apps/web/components/dialog/RescheduleDialog.tsx
index 3f6b20d59a..a81df1531c 100644
--- a/apps/web/components/dialog/RescheduleDialog.tsx
+++ b/apps/web/components/dialog/RescheduleDialog.tsx
@@ -47,7 +47,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
-
{t("reschedule_modal_description")}
+
{t("reschedule_modal_description")}
{t("reason_for_reschedule_request")}
(Optional)
diff --git a/apps/web/lib/hooks/usePublicPage.ts b/apps/web/lib/hooks/usePublicPage.ts
index 90c88c90c1..d10ae13a25 100644
--- a/apps/web/lib/hooks/usePublicPage.ts
+++ b/apps/web/lib/hooks/usePublicPage.ts
@@ -2,7 +2,7 @@ import { useRouter } from "next/router";
export default function usePublicPage() {
const router = useRouter();
- const isPublicPage = ["/[user]", "/success", "/cancel", "/reschedule"].find((route) =>
+ const isPublicPage = ["/[user]", "/booking", "/cancel", "/reschedule"].find((route) =>
router.pathname.startsWith(route)
);
return isPublicPage;
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 302798ffef..902d6289e4 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -149,6 +149,21 @@ const nextConfig = {
source: "/router",
destination: "/apps/routing-forms/router",
},
+ {
+ source: "/success/:path*",
+ has: [
+ {
+ type: "query",
+ key: "uid",
+ value: "(?.*)",
+ },
+ ],
+ destination: "/booking/:uid/:path*",
+ },
+ {
+ source: "/cancel/:path*",
+ destination: "/booking/:path*",
+ },
/* TODO: have these files being served from another deployment or CDN {
source: "/embed/embed.js",
destination: process.env.NEXT_PUBLIC_EMBED_LIB_URL?,
diff --git a/apps/web/pages/404.tsx b/apps/web/pages/404.tsx
index 3dd1d8428f..952a9c5ffc 100644
--- a/apps/web/pages/404.tsx
+++ b/apps/web/pages/404.tsx
@@ -38,7 +38,7 @@ export default function Custom404() {
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
}, [username]);
- const isSuccessPage = router.asPath.startsWith("/success");
+ const isSuccessPage = router.asPath.startsWith("/booking");
const isSubpage = router.asPath.includes("/", 2) || isSuccessPage;
const isSignup = router.asPath.startsWith("/signup");
const isCalcom = process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com";
diff --git a/apps/web/pages/success.tsx b/apps/web/pages/booking/[uid].tsx
similarity index 99%
rename from apps/web/pages/success.tsx
rename to apps/web/pages/booking/[uid].tsx
index d02aa7d3db..b2f1b57211 100644
--- a/apps/web/pages/success.tsx
+++ b/apps/web/pages/booking/[uid].tsx
@@ -147,6 +147,7 @@ const querySchema = z.object({
uid: z.string(),
allRemainingBookings: stringToBoolean,
cancel: stringToBoolean,
+ changes: stringToBoolean,
reschedule: stringToBoolean,
isSuccessBookingPage: z.string().optional(),
});
@@ -159,9 +160,10 @@ export default function Success(props: SuccessProps) {
allRemainingBookings,
isSuccessBookingPage,
cancel: isCancellationMode,
+ changes,
} = querySchema.parse(router.query);
- if (isCancellationMode && typeof window !== "undefined") {
+ if ((isCancellationMode || changes) && typeof window !== "undefined") {
window.scrollTo(0, document.body.scrollHeight);
}
const location: ReturnType = Array.isArray(props.bookingInfo.location)
@@ -221,7 +223,7 @@ export default function Success(props: SuccessProps) {
useEffect(() => {
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
- telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/success"));
+ telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/booking"));
}
}, [telemetry]);
diff --git a/apps/web/pages/cancel/[uid].tsx b/apps/web/pages/cancel/[uid].tsx
deleted file mode 100644
index 3162d06336..0000000000
--- a/apps/web/pages/cancel/[uid].tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { GetServerSidePropsContext } from "next";
-import z from "zod";
-
-const querySchema = z.object({
- uid: z.string(),
- allRemainingBookings: z
- .string()
- .optional()
- .transform((val) => (val ? JSON.parse(val) : false)),
-});
-
-export default function Type() {
- return <>>;
-}
-
-export const getServerSideProps = async (context: GetServerSidePropsContext) => {
- const { allRemainingBookings, uid } = querySchema.parse(context.query);
- return {
- redirect: {
- permanent: false,
- destination: `/success?uid=${uid}&allRemainingBookings=${allRemainingBookings}&cancel=true`,
- },
- };
-};
diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts
index fbfab86433..cd247bc3f3 100644
--- a/apps/web/playwright/booking-pages.e2e.ts
+++ b/apps/web/playwright/booking-pages.e2e.ts
@@ -93,7 +93,7 @@ test.describe("pro user", () => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
await page.waitForNavigation({
url(url) {
- return url.pathname === "/success";
+ return url.pathname.startsWith("/booking");
},
});
});
@@ -108,10 +108,9 @@ test.describe("pro user", () => {
await page.locator('[data-testid="cancel"]').first().click();
await page.waitForNavigation({
url: (url) => {
- return url.pathname.startsWith("/success");
+ return url.pathname.startsWith("/booking");
},
});
- // --- fill form
await page.locator('[data-testid="cancel"]').click();
const cancelledHeadline = await page.locator('[data-testid="cancelled-headline"]').innerText();
diff --git a/apps/web/playwright/dynamic-booking-pages.e2e.ts b/apps/web/playwright/dynamic-booking-pages.e2e.ts
index bf4e7e0f58..148f755edc 100644
--- a/apps/web/playwright/dynamic-booking-pages.e2e.ts
+++ b/apps/web/playwright/dynamic-booking-pages.e2e.ts
@@ -37,7 +37,7 @@ test("dynamic booking", async ({ page, users }) => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
await page.waitForNavigation({
url(url) {
- return url.pathname === "/success";
+ return url.pathname.startsWith("/booking");
},
});
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
@@ -48,10 +48,9 @@ test("dynamic booking", async ({ page, users }) => {
await page.locator('[data-testid="cancel"]').first().click();
await page.waitForNavigation({
url: (url) => {
- return url.pathname.startsWith("/success");
+ return url.pathname.startsWith("/booking");
},
});
- // --- fill form
await page.locator('[data-testid="cancel"]').click();
const cancelledHeadline = await page.locator('[data-testid="cancelled-headline"]').innerText();
diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts
index b55b45c0c0..ae898ff789 100644
--- a/apps/web/playwright/lib/testUtils.ts
+++ b/apps/web/playwright/lib/testUtils.ts
@@ -112,7 +112,7 @@ async function bookEventOnThisPage(page: Page) {
// Make sure we're navigated to the success page
await page.waitForNavigation({
url(url) {
- return url.pathname.endsWith("/success");
+ return url.pathname.startsWith("/booking");
},
});
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
diff --git a/apps/web/playwright/reschedule.e2e.ts b/apps/web/playwright/reschedule.e2e.ts
index 49edcefceb..7ad35676df 100644
--- a/apps/web/playwright/reschedule.e2e.ts
+++ b/apps/web/playwright/reschedule.e2e.ts
@@ -150,7 +150,7 @@ test.describe("Reschedule Tests", async () => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
- await expect(page).toHaveURL(/.*success/);
+ await expect(page).toHaveURL(/.*booking/);
await payment.delete();
});
@@ -168,7 +168,7 @@ test.describe("Reschedule Tests", async () => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
- await expect(page).toHaveURL(/.*success/);
+ await expect(page).toHaveURL(/.*booking/);
const newBooking = await prisma.booking.findFirst({ where: { fromReschedule: booking?.uid } });
expect(newBooking).not.toBeNull();
@@ -189,7 +189,7 @@ test.describe("Reschedule Tests", async () => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
- await expect(page).toHaveURL(/.*success/);
+ await expect(page).toHaveURL(/.*booking/);
const newBooking = await prisma.booking.findFirst({ where: { fromReschedule: booking?.uid } });
expect(newBooking).not.toBeNull();
diff --git a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts
index a30131d4f4..f8366c6d4e 100644
--- a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts
+++ b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts
@@ -1,7 +1,7 @@
import { createEvent, DateArray, Person } from "ics";
import dayjs from "@calcom/dayjs";
-import { getCancelLink } from "@calcom/lib/CalEventParser";
+import { getManageLink } from "@calcom/lib/CalEventParser";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { renderEmail } from "..";
@@ -89,7 +89,7 @@ ${this.t("request_reschedule_subtitle", {
})},
${this.getWhen()}
${this.t("need_to_reschedule_or_cancel")}
-${getCancelLink(this.calEvent)}
+${getManageLink(this.calEvent)}
`.replace(/(<([^>]+)>)/gi, "");
}
}
diff --git a/packages/features/ee/payments/components/Payment.tsx b/packages/features/ee/payments/components/Payment.tsx
index 8d15682d45..39cd3bcd49 100644
--- a/packages/features/ee/payments/components/Payment.tsx
+++ b/packages/features/ee/payments/components/Payment.tsx
@@ -81,9 +81,7 @@ export default function PaymentComponent(props: Props) {
error: new Error(`Payment failed: ${payload.error.message}`),
});
} else {
- const params: { [k: string]: any } = {
- uid: props.bookingUid,
- };
+ const params: { [k: string]: any } = {};
if (props.location) {
if (props.location.includes("integration")) {
@@ -94,7 +92,7 @@ export default function PaymentComponent(props: Props) {
}
const query = stringify(params);
- const successUrl = `/success?${query}`;
+ const successUrl = `/booking/${props.bookingUid}?${query}`;
await router.push(successUrl);
}
diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts
index 358a1a8698..fe867db412 100644
--- a/packages/lib/CalEventParser.ts
+++ b/packages/lib/CalEventParser.ts
@@ -123,21 +123,20 @@ export const getProviderName = (calEvent: CalendarEvent): string => {
return "";
};
-export const getManageLink = (calEvent: CalendarEvent) => {
- return `
-${calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
-${getCancelLink(calEvent)}
- `;
-};
-
export const getUid = (calEvent: CalendarEvent): string => {
return calEvent.uid ?? translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL));
};
+export const getManageLink = (calEvent: CalendarEvent) => {
+ return `
+${calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
+${WEBAPP_URL + "/booking/" + getUid(calEvent) + "?changes=true"}
+ `;
+};
+
export const getCancelLink = (calEvent: CalendarEvent): string => {
return (
- WEBAPP_URL +
- `/success?uid=${getUid(calEvent)}&cancel=true&allRemainingBookings=${!!calEvent.recurringEvent}`
+ WEBAPP_URL + `/booking/${getUid(calEvent)}?cancel=true&allRemainingBookings=${!!calEvent.recurringEvent}`
);
};