import type { EventType } from "@prisma/client"; import { useRouter } from "next/navigation"; import type { PaymentPageProps } from "@calcom/ee/payments/pages/payment"; import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import type { BookingResponse } from "@calcom/features/bookings/types"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { navigateInTopWindow } from "@calcom/lib/navigateInTopWindow"; function getNewSearchParams(args: { query: Record; searchParams?: URLSearchParams; }) { const { query, searchParams } = args; const newSearchParams = new URLSearchParams(searchParams); Object.entries(query).forEach(([key, value]) => { if (value === null || value === undefined) { return; } newSearchParams.append(key, String(value)); }); return newSearchParams; } type SuccessRedirectBookingType = Pick< BookingResponse | PaymentPageProps["booking"], "uid" | "title" | "description" | "startTime" | "endTime" | "location" >; export const getBookingRedirectExtraParams = (booking: SuccessRedirectBookingType) => { type BookingResponseKey = keyof SuccessRedirectBookingType; const redirectQueryParamKeys: BookingResponseKey[] = [ "title", "description", "startTime", "endTime", "location", ]; return (Object.keys(booking) as BookingResponseKey[]) .filter((key) => redirectQueryParamKeys.includes(key)) .reduce((obj, key) => ({ ...obj, [key]: booking[key] }), {}); }; export const useBookingSuccessRedirect = () => { const router = useRouter(); const searchParams = useCompatSearchParams(); const isEmbed = useIsEmbed(); const bookingSuccessRedirect = ({ successRedirectUrl, query, booking, forwardParamsSuccessRedirect, }: { successRedirectUrl: EventType["successRedirectUrl"]; forwardParamsSuccessRedirect: EventType["forwardParamsSuccessRedirect"]; query: Record; booking: SuccessRedirectBookingType; }) => { // Ensures that the param is added both to external redirect url and booking success page URL query = { ...query, "cal.rerouting": searchParams.get("cal.rerouting"), }; if (successRedirectUrl) { const url = new URL(successRedirectUrl); // Using parent ensures, Embed iframe would redirect outside of the iframe. if (!forwardParamsSuccessRedirect) { navigateInTopWindow(url.toString()); return; } const bookingExtraParams = getBookingRedirectExtraParams(booking); const newSearchParams = getNewSearchParams({ query: { ...query, ...bookingExtraParams, }, searchParams: searchParams ?? undefined, }); newSearchParams.forEach((value, key) => { url.searchParams.append(key, value); }); navigateInTopWindow(url.toString()); return; } // TODO: Abstract it out and reuse at other places where we navigate within the embed. Though this is needed only in case of hard navigation happening but we aren't sure where hard navigation happens and where a soft navigation // This is specially true after App Router it seems const headersRelatedSearchParams = searchParams ? { "flag.coep": searchParams.get("flag.coep") ?? "false", } : undefined; // We don't want to forward all search params, as they could possibly break the booking page. const newSearchParams = getNewSearchParams({ query, searchParams: new URLSearchParams(headersRelatedSearchParams), }); return router.push(`/booking/${booking.uid}${isEmbed ? "/embed" : ""}?${newSearchParams.toString()}`); }; return bookingSuccessRedirect; };