8c39210a12
* chore: add new view for two step slot selection for embed * fix: make sure two step slot selection is false by default * chore: update embed playground to showcase two step slot selection * chore: add tests * chore: update embed snippet generator code to include two step slot selection * chore: update docs * fix: back button styling * chore: implement feedback from cubic * fix: add missing isEnableTwoStepSlotSelectionVisible properties to test mock store Co-Authored-By: unknown <> * chore: implement PR feedback * chore: update slot selection modal * chore: add prop to hide available times header * fix: scope two-step slot selection visibility to mobile only Restores the isMobile check in the timeslot visibility guard to ensure desktop embeds continue to show the booking UI when two-step slot selection is enabled. The feature should only hide the timeslot list on mobile devices. Addresses Cubic AI review feedback (confidence 9/10). Co-Authored-By: unknown <> * fixup: use translations for loading instead of actual word * fix: type check * fix: add window.matchMedia mock for jsdom test environment Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: add window.matchMedia mock to packages/testing/src/setupVitest.ts Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: unit tests failing * add a width style to the two-step slot selection embed container. * refactor: rename enableTwoStepSlotSelection to useSlotsViewOnSmallScreen - Rename external-facing embed config option from enableTwoStepSlotSelection to useSlotsViewOnSmallScreen - Update URL parameter parsing in embed-iframe.ts - Update type definitions in types.ts and index.d.ts - Update internal variable names to slotsViewOnSmallScreen for consistency - Update documentation, playground examples, and tests Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Add config to prefill all booking fields so that slot has confirm button along with it * chore: implement PR feedback * fix: move twoStepSlotSelection embed outside misc-embeds div to fix e2e test The e2e test was failing because the misc-embeds div content was intercepting pointer events on mobile viewport. This follows the same pattern used for skeletonDemo - the embed is now outside misc-embeds and the div is hidden when testing this namespace. Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: prevent pointer event interception in twoStepSlotSelection embed Hide heading and note elements and set pointer-events: none on container elements while keeping pointer-events: auto on the iframe container. This prevents the container from intercepting clicks on the iframe during mobile viewport e2e tests. Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: hide all non-essential content for twoStepSlotSelection e2e test Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: disable pointer-events on body for twoStepSlotSelection e2e test Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: remove pointer-events: auto on container to let clicks pass through to iframe Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: explicitly set pointer-events: none on container and inline-embed-container Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: also set pointer-events: none on html element to prevent interception Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: make .place div fill viewport for twoStepSlotSelection e2e test Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com> * fix: failing embed tests * fixup * fixup fixup * fix: failing tests * fix: update checks for verifying prefilled date --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
139 lines
4.5 KiB
TypeScript
139 lines
4.5 KiB
TypeScript
import dynamic from "next/dynamic";
|
|
import { useMemo } from "react";
|
|
import { shallow } from "zustand/shallow";
|
|
|
|
import { Timezone as PlatformTimezoneSelect } from "@calcom/atoms/timezone";
|
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
|
import type { Timezone } from "@calcom/features/bookings/Booker/types";
|
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
|
import { EventDetailBlocks } from "@calcom/features/bookings/types";
|
|
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
|
import { CURRENT_TIMEZONE } from "@calcom/lib/timezoneConstants";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { Icon } from "@calcom/ui/components/icon";
|
|
|
|
import { EventDetails } from "./event-meta/Details";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
|
|
const LoadingState = () => {
|
|
const { t } = useLocale();
|
|
return <span className="text-default text-sm">{t("loading")}</span>;
|
|
};
|
|
|
|
const WebTimezoneSelect = dynamic(
|
|
() =>
|
|
import("@calcom/features/components/timezone-select").then(
|
|
(mod) => mod.TimezoneSelect
|
|
),
|
|
{
|
|
ssr: false,
|
|
loading: () => <LoadingState />,
|
|
}
|
|
);
|
|
|
|
type SlotSelectionModalHeaderProps = {
|
|
onClick: () => void;
|
|
event?: Pick<
|
|
BookerEvent,
|
|
| "length"
|
|
| "metadata"
|
|
| "lockTimeZoneToggleOnBookingPage"
|
|
| "lockedTimeZone"
|
|
| "isDynamic"
|
|
| "currency"
|
|
| "price"
|
|
| "locations"
|
|
| "requiresConfirmation"
|
|
| "recurringEvent"
|
|
> | null;
|
|
isPlatform?: boolean;
|
|
timeZones?: Timezone[];
|
|
selectedDate: string | null;
|
|
};
|
|
|
|
export const SlotSelectionModalHeader = ({
|
|
onClick,
|
|
event,
|
|
isPlatform = false,
|
|
timeZones,
|
|
selectedDate,
|
|
}: SlotSelectionModalHeaderProps) => {
|
|
const { i18n } = useLocale();
|
|
const [setTimezone] = useTimePreferences((state) => [state.setTimezone]);
|
|
const [timezone, setBookerStoreTimezone] = useBookerStoreContext(
|
|
(state) => [state.timezone, state.setTimezone],
|
|
shallow
|
|
);
|
|
const [TimezoneSelect] = useMemo(
|
|
() => (isPlatform ? [PlatformTimezoneSelect] : [WebTimezoneSelect]),
|
|
[isPlatform]
|
|
);
|
|
|
|
const formattedDate = useMemo(() => {
|
|
if (!selectedDate) return { dayOfWeek: "", fullDate: "" };
|
|
const date = new Date(selectedDate);
|
|
const dayOfWeek = date.toLocaleDateString(i18n.language, {
|
|
weekday: "long",
|
|
});
|
|
const fullDate = date.toLocaleDateString(i18n.language, {
|
|
month: "long",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
});
|
|
return { dayOfWeek, fullDate };
|
|
}, [selectedDate, i18n.language]);
|
|
|
|
return (
|
|
<div className="two-step-slot-selection-modal-header bg-default border-subtle sticky top-0 z-10 flex flex-col mb-4 mt-0 border-b pb-4 -mx-4 px-8">
|
|
<div className="flex flex-col gap-2 pt-8">
|
|
<div className="flex flex-col">
|
|
<span className="text-emphasis text-lg font-semibold">
|
|
<Button
|
|
color="minimal"
|
|
StartIcon="arrow-left"
|
|
className="mb-2 ml-[-42px] w-[40px]"
|
|
onClick={onClick}
|
|
/>{" "}
|
|
{formattedDate.dayOfWeek}
|
|
</span>
|
|
<span className="text-default text-sm">{formattedDate.fullDate}</span>
|
|
</div>
|
|
|
|
{event && (
|
|
<EventDetails event={event} blocks={[EventDetailBlocks.DURATION]} />
|
|
)}
|
|
|
|
<div className="text-default flex items-center gap-2 text-sm mb-0">
|
|
<Icon name="globe" className="text-subtle h-4 w-4 shrink-0" />
|
|
{TimezoneSelect && (
|
|
<span className="min-w-32 -mt-[2px] flex h-6 max-w-full items-center justify-start">
|
|
<TimezoneSelect
|
|
timeZones={timeZones}
|
|
menuPosition="fixed"
|
|
classNames={{
|
|
control: () =>
|
|
"min-h-0! p-0 w-full border-0 bg-transparent focus-within:ring-0 shadow-none!",
|
|
menu: () => "w-64! max-w-[90vw] mb-1",
|
|
singleValue: () => "text-text py-1",
|
|
indicatorsContainer: () => "ml-auto",
|
|
container: () => "max-w-full",
|
|
}}
|
|
value={
|
|
event?.lockTimeZoneToggleOnBookingPage
|
|
? event.lockedTimeZone || CURRENT_TIMEZONE
|
|
: timezone || CURRENT_TIMEZONE
|
|
}
|
|
onChange={({ value }) => {
|
|
setTimezone(value);
|
|
setBookerStoreTimezone(value);
|
|
}}
|
|
isDisabled={event?.lockTimeZoneToggleOnBookingPage}
|
|
/>
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|