Files
calendar/packages/platform/atoms/hooks/useSlots.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

115 lines
3.5 KiB
TypeScript

import { useEffect } from "react";
import { shallow } from "zustand/shallow";
import dayjs from "@calcom/dayjs";
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
import type { BookerEvent } from "@calcom/features/bookings/types";
import { MINUTES_TO_BOOK } from "@calcom/lib/constants";
import type {
ApiErrorResponse,
ApiSuccessResponse,
ApiSuccessResponseWithoutData,
} from "@calcom/platform-types";
import { useDeleteSelectedSlot } from "./useDeleteSelectedSlot";
import { useReserveSlot } from "./useReserveSlot";
type UseSlotsCallbacks = {
onReserveSlotSuccess?: (data: ApiSuccessResponse<string>) => void;
onReserveSlotError?: (err: ApiErrorResponse) => void;
onDeleteSlotSuccess?: (data: ApiSuccessResponseWithoutData) => void;
onDeleteSlotError?: (err: ApiErrorResponse) => void;
handleSlotReservation?: (timeslot: string) => void;
};
export type UseSlotsReturnType = ReturnType<typeof useSlots>;
export const useSlots = (
event: { data?: Pick<BookerEvent, "id" | "length"> | null },
{
onReserveSlotSuccess,
onReserveSlotError,
onDeleteSlotSuccess,
onDeleteSlotError,
isBookingDryRun,
handleSlotReservation,
}: UseSlotsCallbacks & { isBookingDryRun?: boolean } = {}
) => {
const selectedDuration = useBookerStoreContext((state) => state.selectedDuration);
const [selectedTimeslot, setSelectedTimeslot] = useBookerStoreContext(
(state) => [state.selectedTimeslot, state.setSelectedTimeslot],
shallow
);
const [slotReservationId, setSlotReservationId] = useSlotReservationId();
const reserveSlotMutation = useReserveSlot({
onSuccess: (res) => {
setSlotReservationId(res.data);
onReserveSlotSuccess?.(res);
},
onError: onReserveSlotError,
});
const seatedEventData = useBookerStoreContext((state) => state.seatedEventData);
const removeSelectedSlot = useDeleteSelectedSlot({
onSuccess: onDeleteSlotSuccess,
onError: onDeleteSlotError,
});
const handleRemoveSlot = () => {
if (event?.data) {
removeSelectedSlot.mutate({ uid: slotReservationId ?? undefined });
}
};
const handleReserveSlot = () => {
if (event?.data?.id && selectedTimeslot && (selectedDuration || event?.data?.length)) {
if (handleSlotReservation) {
handleSlotReservation(selectedTimeslot);
return;
}
reserveSlotMutation.mutate({
slotUtcStartDate: dayjs(selectedTimeslot).utc().format(),
eventTypeId: event.data.id,
slotUtcEndDate: dayjs(selectedTimeslot)
.utc()
.add(selectedDuration || event.data.length, "minutes")
.format(),
_isDryRun: isBookingDryRun,
bookingUid: seatedEventData.bookingUid || undefined,
});
}
};
const timeslot = useBookerStoreContext((state) => state.selectedTimeslot);
useEffect(() => {
handleReserveSlot();
const interval = setInterval(() => {
handleReserveSlot();
}, parseInt(MINUTES_TO_BOOK) * 60 * 1000 - 2000);
return () => {
handleRemoveSlot();
clearInterval(interval);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [event?.data?.id, timeslot]);
return {
selectedTimeslot,
setSelectedTimeslot,
setSlotReservationId,
slotReservationId,
handleReserveSlot,
handleRemoveSlot,
// TODO: implement slot no longer available feature
allSelectedTimeslots: [],
};
};