import { LazyMotion, domAnimation, m, AnimatePresence } from "framer-motion"; import dynamic from "next/dynamic"; import { useEffect, useRef, useMemo } from "react"; import StickyBox from "react-sticky-box"; import { shallow } from "zustand/shallow"; import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager"; import { useEmbedUiConfig } from "@calcom/embed-core/embed-iframe"; import classNames from "@calcom/lib/classNames"; import useMediaQuery from "@calcom/lib/hooks/useMediaQuery"; import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils"; import { AvailableTimeSlots } from "./components/AvailableTimeSlots"; import { BookEventForm } from "./components/BookEventForm"; import { BookFormAsModal } from "./components/BookEventForm/BookFormAsModal"; import { EventMeta } from "./components/EventMeta"; import { Header } from "./components/Header"; import { LargeCalendar } from "./components/LargeCalendar"; import { BookerSection } from "./components/Section"; import { Away, NotFound } from "./components/Unavailable"; import { extraDaysConfig, fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config"; import { useBookerStore, useInitializeBookerStore } from "./store"; import type { BookerLayout, BookerProps } from "./types"; import { useEvent } from "./utils/event"; import { validateLayout } from "./utils/layout"; import { getQueryParam } from "./utils/query-param"; import { useBrandColors } from "./utils/use-brand-colors"; const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy")); const DatePicker = dynamic(() => import("./components/DatePicker").then((mod) => mod.DatePicker), { ssr: false, }); const BookerComponent = ({ username, eventSlug, month, rescheduleBooking, hideBranding = false, isTeamEvent, }: BookerProps) => { const isMobile = useMediaQuery("(max-width: 768px)"); const isTablet = useMediaQuery("(max-width: 1024px)"); const timeslotsRef = useRef(null); const StickyOnDesktop = isMobile ? "div" : StickyBox; const rescheduleUid = typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("rescheduleUid") : null; const event = useEvent(); const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow); if (typeof window !== "undefined") { window.CalEmbed.setLayout = setLayout; } const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow); const selectedDate = useBookerStore((state) => state.selectedDate); const [selectedTimeslot, setSelectedTimeslot] = useBookerStore( (state) => [state.selectedTimeslot, state.setSelectedTimeslot], shallow ); const extraDays = isTablet ? extraDaysConfig[layout].tablet : extraDaysConfig[layout].desktop; const bookerLayouts = event.data?.profile?.bookerLayouts || defaultBookerLayoutSettings; const animationScope = useBookerResizeAnimation(layout, bookerState); const isEmbed = typeof window !== "undefined" && window?.isEmbed?.(); // We only want the initial url value, that's why we memo it. The embed seems to change the url, which sometimes drops // the layout query param. const layoutFromQueryParam = useMemo(() => validateLayout(getQueryParam("layout") as BookerLayouts), []); const defaultLayout = isEmbed ? layoutFromQueryParam || BookerLayouts.MONTH_VIEW : bookerLayouts.defaultLayout; useBrandColors({ brandColor: event.data?.profile.brandColor, darkBrandColor: event.data?.profile.darkBrandColor, theme: event.data?.profile.theme, }); useInitializeBookerStore({ username, eventSlug, month, eventId: event?.data?.id, rescheduleUid, rescheduleBooking, layout: defaultLayout, isTeamEvent, }); useEffect(() => { if (isMobile && layout !== "mobile") { setLayout("mobile"); } else if (!isMobile && layout === "mobile") { setLayout(defaultLayout); } }, [isMobile, setLayout, layout, defaultLayout]); useEffect(() => { if (event.isLoading) return setBookerState("loading"); if (!selectedDate) return setBookerState("selecting_date"); if (!selectedTimeslot) return setBookerState("selecting_time"); return setBookerState("booking"); }, [event, selectedDate, selectedTimeslot, setBookerState]); useEffect(() => { if (layout === "mobile") { timeslotsRef.current?.scrollIntoView({ behavior: "smooth" }); } }, [layout]); const embedUiConfig = useEmbedUiConfig(); const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false; if (event.isSuccess && !event.data) { return ; } // In Embed, a Dialog doesn't look good, we disable it intentionally for the layouts that support showing Form without Dialog(i.e. no-dialog Form) const shouldShowFormInDialogMap: Record = { // mobile supports showing the Form without Dialog mobile: !isEmbed, // We don't show Dialog in month_view currently. Can be easily toggled though as it supports no-dialog Form month_view: false, // week_view doesn't support no-dialog Form // When it's supported, disable it for embed week_view: true, // column_view doesn't support no-dialog Form // When it's supported, disable it for embed column_view: true, }; const shouldShowFormInDialog = shouldShowFormInDialogMap[layout]; return ( <> {event.data ? : null}
{layout !== BookerLayouts.MONTH_VIEW && !(layout === "mobile" && bookerState === "booking") && (
)}
setSelectedTimeslot(null)} />
{!hideBranding ? : null}
setSelectedTimeslot(null)} /> ); }; export const Booker = (props: BookerProps) => { if (props.isAway) return ; return ( ); };