Files
calendar/packages/features/bookings/Booker/components/hooks/useBookerLayout.ts
T
sean-brydonGitHubrajiv@cal.com <sahalrajiv6900@gmail.com>Rajiv SahalRyukemeisterDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Benny Joo
4c01f171ae fix: multiple widgets for Booker atom (#22925)
* fix manage test error

* fix manage test error

* update useBookerContext

* wrap store provider around booker

* replace useStore with useBookerStoreContext

* replace useBookerStore with useBookerStoreContext

* add initializer function for booker store provider

* update props

* fixup: pass more props

* export StoreInitializeType type

* replace useBookerStore with useBookerStoreContext

* fixup: dont wrap BookerStoreProvider around booker directly

* wrap BookerStoreProvider around booker web wrapper and fix local storage import

* fix: wrap test components with BookerStoreProvider to fix failing unit tests

- Create reusable test utility in test-utils.tsx with comprehensive mock store
- Update Booker.test.tsx to use context-based testing approach
- Fix DatePicker tests in both bookings and calendars packages
- Simulate auto-advance behavior for month navigation tests
- All 14 previously failing tests now pass

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* add changesets

* fix: complete migration from useBookerStore to useBookerStoreContext

- Updated all remaining files to use useBookerStoreContext instead of useBookerStore
- Fixed BookingFields.test.tsx by wrapping test component with BookerStoreProvider
- Ensures all Booker components work within the new context-based store pattern
- Resolves failing unit tests in CI by completing the store migration

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* fix: add missing useBookerStoreContext import in BookerWebWrapper.tsx

- Fixes TypeScript errors in CI where useBookerStoreContext was used but not imported
- Completes the migration from useBookerStore to useBookerStoreContext pattern
- All tests pass locally after this fix

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* Revert "fix: add missing useBookerStoreContext import in BookerWebWrapper.tsx"

This reverts commit 12947ca63f46c42e5aee74c78176ef230e2bf2cf.

* Revert "fix: complete migration from useBookerStore to useBookerStoreContext"

This reverts commit 87d837f2d30e4f9259d2be786049d21856f4eef0.

* fixup: useBookerStoreContext and useInitializeBookerStoreContext for booker web wrapper

* fixup: failing E2E tests

* fixup: email embed breaking

* fixup: troubleshooter crashing

* replace useBookerstore with useBookerStoreContext

---------

Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
Co-authored-by: Ryukemeister <sahalrajiv6900@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
2025-08-14 17:57:59 +05:30

95 lines
3.9 KiB
TypeScript

import { useEffect, useRef } from "react";
import { shallow } from "zustand/shallow";
import { useEmbedType, useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
import type { BookerEvent } from "@calcom/features/bookings/types";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
import { defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
import { extraDaysConfig } from "../../config";
import type { BookerLayout } from "../../types";
import { validateLayout } from "../../utils/layout";
import { getQueryParam } from "../../utils/query-param";
export type UseBookerLayoutType = ReturnType<typeof useBookerLayout>;
export const useBookerLayout = (
profileBookerLayouts: BookerEvent["profile"]["bookerLayouts"] | undefined | null
) => {
const [_layout, setLayout] = useBookerStoreContext((state) => [state.layout, state.setLayout], shallow);
const isEmbed = useIsEmbed();
const isMobile = useMediaQuery("(max-width: 768px)");
const isTablet = useMediaQuery("(max-width: 1024px)");
const embedUiConfig = useEmbedUiConfig();
// In Embed we give preference to embed configuration for the layout.If that's not set, we use the App configuration for the event layout
// But if it's mobile view, there is only one layout supported which is 'mobile'
const layout = isEmbed ? (isMobile ? "mobile" : validateLayout(embedUiConfig.layout) || _layout) : _layout;
const extraDays = isTablet ? extraDaysConfig[layout].tablet : extraDaysConfig[layout].desktop;
const embedType = useEmbedType();
// Floating Button and Element Click both are modal and thus have dark background
const hasDarkBackground = isEmbed && embedType !== "inline";
const columnViewExtraDays = useRef<number>(
isTablet ? extraDaysConfig[layout].tablet : extraDaysConfig[layout].desktop
);
const bookerLayouts = profileBookerLayouts || defaultBookerLayoutSettings;
const defaultLayout = isEmbed
? validateLayout(embedUiConfig.layout) || bookerLayouts.defaultLayout
: bookerLayouts.defaultLayout;
useEffect(() => {
if (isMobile && layout !== "mobile") {
setLayout("mobile");
} else if (!isMobile && layout === "mobile") {
setLayout(defaultLayout);
}
}, [isMobile, setLayout, layout, defaultLayout]);
//setting layout from query param
useEffect(() => {
const layout = getQueryParam("layout") as BookerLayouts;
if (
!isMobile &&
!isEmbed &&
validateLayout(layout) &&
bookerLayouts?.enabledLayouts?.length &&
layout !== _layout
) {
const validLayout = bookerLayouts.enabledLayouts.find((userLayout) => userLayout === layout);
validLayout && setLayout(validLayout);
}
}, [bookerLayouts, setLayout, _layout, isEmbed, isMobile]);
// 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<BookerLayout, boolean> = {
// 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];
const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false;
return {
shouldShowFormInDialog,
hasDarkBackground,
extraDays,
columnViewExtraDays,
isMobile,
isEmbed,
isTablet,
layout,
defaultLayout,
hideEventTypeDetails,
bookerLayouts,
};
};