diff --git a/apps/web/components/booking/AvailableTimes.tsx b/apps/web/components/booking/AvailableTimes.tsx index 7d55b70292..43db34e51b 100644 --- a/apps/web/components/booking/AvailableTimes.tsx +++ b/apps/web/components/booking/AvailableTimes.tsx @@ -65,7 +65,7 @@ const AvailableTimes: FC = ({ query: Record; }; const bookingUrl: BookingURL = { - pathname: "book", + pathname: router.pathname.endsWith("/embed") ? "../book" : "book", query: { ...router.query, date: dayjs(slot.time).format(), diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index 39c0cefbb5..1e1a2ab510 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -63,7 +63,7 @@ export default function CancelBooking(props: Props) { diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx index c125fc5277..7543c2ba1f 100644 --- a/apps/web/components/booking/pages/AvailabilityPage.tsx +++ b/apps/web/components/booking/pages/AvailabilityPage.tsx @@ -304,11 +304,11 @@ const timeFormatTotimeFormatString = (timeFormat?: number | null) => { return timeFormat === 24 ? "HH:mm" : "h:mma"; }; -const AvailabilityPage = ({ profile, eventType }: Props) => { +const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => { const { data: user } = trpc.useQuery(["viewer.me"]); const timeFormatFromProfile = timeFormatTotimeFormatString(user?.timeFormat); const router = useRouter(); - const isEmbed = useIsEmbed(); + const isEmbed = useIsEmbed(restProps.isEmbed); const query = dateQuerySchema.parse(router.query); const { rescheduleUid } = query; useTheme(profile.theme); diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index c2b1894322..0d5f5d059b 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -1,6 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { EventTypeCustomInputType, WorkflowActions } from "@prisma/client"; -import { SchedulingType } from "@prisma/client"; import { useMutation } from "@tanstack/react-query"; import { isValidPhoneNumber } from "libphonenumber-js"; import { useSession } from "next-auth/react"; @@ -84,9 +83,10 @@ const BookingPage = ({ recurringEventCount, hasHashedBookingLink, hashedLink, + ...restProps }: BookingPageProps) => { const { t, i18n } = useLocale(); - const isEmbed = useIsEmbed(); + const isEmbed = useIsEmbed(restProps.isEmbed); const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; const router = useRouter(); diff --git a/apps/web/lib/withEmbedSsr.tsx b/apps/web/lib/withEmbedSsr.tsx new file mode 100644 index 0000000000..f524068bf5 --- /dev/null +++ b/apps/web/lib/withEmbedSsr.tsx @@ -0,0 +1,21 @@ +import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next"; + +export type EmbedProps = { + isEmbed?: boolean; +}; + +export default function withEmbedSsr(getServerSideProps: GetServerSideProps) { + return async (context: GetServerSidePropsContext): Promise> => { + const ssrResponse = await getServerSideProps(context); + if (!("props" in ssrResponse)) { + return ssrResponse; + } + return { + ...ssrResponse, + props: { + ...ssrResponse.props, + isEmbed: true, + }, + }; + }; +} diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index fb0d5b68a1..dddd96ec1f 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -23,6 +23,13 @@ const middleware: NextMiddleware = async (req) => { } } + // Ensure that embed query param is there in when /embed is added. + // query param is the way in which client side code knows that it is in embed mode. + if (url.pathname.endsWith("/embed") && typeof url.searchParams.get("embed") !== "string") { + url.searchParams.set("embed", ""); + return NextResponse.redirect(url); + } + // Don't 404 old routing_forms links if (url.pathname.startsWith("/apps/routing_forms")) { url.pathname = url.pathname.replace("/apps/routing_forms", "/apps/routing-forms"); diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 0c6542cfaf..4108d8e01d 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -134,8 +134,8 @@ const nextConfig = { destination: "/api/user/avatar?teamname=:teamname", }, { - source: "/forms/:formId", - destination: "/apps/routing-forms/routing-link/:formId", + source: "/forms/:formQuery*", + destination: "/apps/routing-forms/routing-link/:formQuery*", }, { source: "/router", diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index b44efaa16b..ed5375f41b 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -30,6 +30,7 @@ import { BadgeCheckIcon, Icon } from "@calcom/ui/Icon"; import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally"; import { inferSSRProps } from "@lib/types/inferSSRProps"; +import { EmbedProps } from "@lib/withEmbedSsr"; import AvatarGroup from "@components/ui/AvatarGroup"; import { AvatarSSR } from "@components/ui/AvatarSSR"; @@ -38,7 +39,7 @@ import { ssrInit } from "@server/lib/ssr"; const EventTypeDescription = dynamic(() => import("@calcom/ui/v2/modules/event-types/EventTypeDescription")); const HeadSeo = dynamic(() => import("@components/seo/head-seo")); -export default function User(props: inferSSRProps) { +export default function User(props: inferSSRProps & EmbedProps) { const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props; const [user] = users; //To be used when we only have a single user, not dynamic group useTheme(user.theme); @@ -86,7 +87,7 @@ export default function User(props: inferSSRProps) { ); - const isEmbed = useIsEmbed(); + const isEmbed = useIsEmbed(props.isEmbed); const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem"); const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; @@ -287,6 +288,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => if (!users.length) { return { notFound: true, + } as { + notFound: true; }; } const isDynamicGroup = users.length > 1; diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index 07241b0c70..cf78274543 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -11,10 +11,11 @@ import prisma from "@calcom/prisma"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { inferSSRProps } from "@lib/types/inferSSRProps"; +import { EmbedProps } from "@lib/withEmbedSsr"; import AvailabilityPage from "@components/booking/pages/AvailabilityPage"; -export type AvailabilityPageProps = inferSSRProps; +export type AvailabilityPageProps = inferSSRProps & EmbedProps; export default function Type(props: AvailabilityPageProps) { const { t } = useLocale(); diff --git a/apps/web/pages/[user]/[type]/embed.tsx b/apps/web/pages/[user]/[type]/embed.tsx new file mode 100644 index 0000000000..4b701ce151 --- /dev/null +++ b/apps/web/pages/[user]/[type]/embed.tsx @@ -0,0 +1,21 @@ +import { GetStaticPropsContext } from "next"; + +import { getStaticProps as _getStaticProps } from "../[type]"; + +export { getStaticPaths } from "../[type]"; + +export { default } from "../[type]"; + +export const getStaticProps = async (context: GetStaticPropsContext) => { + const staticResponse = await _getStaticProps(context); + if (staticResponse.notFound) { + return staticResponse; + } + return { + ...staticResponse, + props: { + ...staticResponse.props, + isEmbed: true, + }, + }; +}; diff --git a/apps/web/pages/[user]/book.tsx b/apps/web/pages/[user]/book.tsx index 0aae61782f..36b353fb23 100644 --- a/apps/web/pages/[user]/book.tsx +++ b/apps/web/pages/[user]/book.tsx @@ -197,6 +197,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { isDynamicGroupBooking, hasHashedBookingLink: false, hashedLink: null, + isEmbed: typeof context.query.embed === "string", }, }; } diff --git a/apps/web/pages/[user]/embed.tsx b/apps/web/pages/[user]/embed.tsx new file mode 100644 index 0000000000..5c62c6117e --- /dev/null +++ b/apps/web/pages/[user]/embed.tsx @@ -0,0 +1,7 @@ +import withEmbedSsr from "@lib/withEmbedSsr"; + +import { getServerSideProps as _getServerSideProps } from "../[user]"; + +export { default } from "../[user]"; + +export const getServerSideProps = withEmbedSsr(_getServerSideProps); diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 56def4c1c8..cb1654466d 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -3,18 +3,13 @@ import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps type Props = Record & DocumentProps; function toRunBeforeReactOnClient() { - const calEmbedMode = location.search.includes("embed="); - try { - // eslint-disable-next-line @calcom/eslint/avoid-web-storage - window.sessionStorage.setItem("calEmbedMode", String(calEmbedMode)); - } catch (e) {} + const calEmbedMode = + location.search.includes("embed=") || + /* Iframe Name */ + window.name.includes("cal-embed"); window.isEmbed = () => { - try { - // eslint-disable-next-line @calcom/eslint/avoid-web-storage - return window.sessionStorage.getItem("calEmbedMode") === "true"; - } catch (e) {} - // If we can't use sessionStorage to retrieve embed mode, just use the variable. It would fail to detect embed if page in iframe reloads without embed query param in it. + // Once an embed mode always an embed mode return calEmbedMode; }; @@ -45,14 +40,14 @@ function toRunBeforeReactOnClient() { class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { + const isEmbed = ctx.asPath?.includes("/embed") || ctx.asPath?.includes("embedType="); const initialProps = await Document.getInitialProps(ctx); - return { ...initialProps }; + return { isEmbed, ...initialProps }; } render() { const { locale } = this.props.__NEXT_DATA__; const dir = locale === "ar" || locale === "he" ? "rtl" : "ltr"; - return ( @@ -82,22 +77,22 @@ class MyDocument extends Document { /> - +
- {/* In case of Embed we want background to be transparent so that it merges into the website seamlessly. Also, we keep the body hidden here and embed logic would take care of showing the body when it's ready */} - {/* We are doing it on browser and not on server because there are some pages which are not SSRd */} -