refactor: extract logic as bookingDetailsSheetStore (#25129)
* refactor: extract logic as bookingDetailsSheetStore * clean up * revert something * provide store from context
This commit is contained in:
@@ -29,54 +29,88 @@ import {
|
||||
import { BookingActionsDropdown } from "../../../components/booking/actions/BookingActionsDropdown";
|
||||
import { BookingActionsStoreProvider } from "../../../components/booking/actions/BookingActionsStoreProvider";
|
||||
import type { BookingListingStatus } from "../../../components/booking/types";
|
||||
import {
|
||||
useBookingDetailsSheetStore,
|
||||
useBookingDetailsSheetStoreApi,
|
||||
} from "../store/bookingDetailsSheetStore";
|
||||
import type { BookingOutput } from "../types";
|
||||
import { JoinMeetingButton } from "./JoinMeetingButton";
|
||||
|
||||
type BookingMetaData = z.infer<typeof bookingMetadataSchema>;
|
||||
|
||||
interface BookingDetailsSheetProps {
|
||||
booking: BookingOutput | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
userTimeZone?: string;
|
||||
userTimeFormat?: number;
|
||||
userId?: number;
|
||||
userEmail?: string;
|
||||
onPrevious?: () => void;
|
||||
hasPrevious?: boolean;
|
||||
onNext?: () => void;
|
||||
hasNext?: boolean;
|
||||
}
|
||||
|
||||
interface BookingDetailsSheetInnerProps extends Omit<BookingDetailsSheetProps, "booking"> {
|
||||
booking: BookingOutput;
|
||||
}
|
||||
export function BookingDetailsSheet({
|
||||
userTimeZone,
|
||||
userTimeFormat,
|
||||
userId,
|
||||
userEmail,
|
||||
}: BookingDetailsSheetProps) {
|
||||
const booking = useBookingDetailsSheetStore((state) => state.getSelectedBooking());
|
||||
|
||||
export function BookingDetailsSheet(props: BookingDetailsSheetProps) {
|
||||
if (!props.booking) return null;
|
||||
// Return null if no booking is selected (sheet is closed)
|
||||
if (!booking) return null;
|
||||
|
||||
return (
|
||||
<BookingActionsStoreProvider>
|
||||
<BookingDetailsSheetInner {...props} booking={props.booking} />
|
||||
<BookingDetailsSheetInner
|
||||
booking={booking}
|
||||
userTimeZone={userTimeZone}
|
||||
userTimeFormat={userTimeFormat}
|
||||
userId={userId}
|
||||
userEmail={userEmail}
|
||||
/>
|
||||
</BookingActionsStoreProvider>
|
||||
);
|
||||
}
|
||||
|
||||
interface BookingDetailsSheetInnerProps {
|
||||
booking: BookingOutput;
|
||||
userTimeZone?: string;
|
||||
userTimeFormat?: number;
|
||||
userId?: number;
|
||||
userEmail?: string;
|
||||
}
|
||||
|
||||
function BookingDetailsSheetInner({
|
||||
booking,
|
||||
isOpen,
|
||||
onClose,
|
||||
userTimeZone,
|
||||
userTimeFormat,
|
||||
userId,
|
||||
userEmail,
|
||||
onPrevious,
|
||||
hasPrevious = false,
|
||||
onNext,
|
||||
hasNext = false,
|
||||
}: BookingDetailsSheetInnerProps) {
|
||||
const { t } = useLocale();
|
||||
|
||||
// Get navigation state directly from the store
|
||||
const hasNext = useBookingDetailsSheetStore((state) => state.hasNext());
|
||||
const hasPrevious = useBookingDetailsSheetStore((state) => state.hasPrevious());
|
||||
const setSelectedBookingId = useBookingDetailsSheetStore((state) => state.setSelectedBookingId);
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedBookingId(null);
|
||||
};
|
||||
|
||||
const storeApi = useBookingDetailsSheetStoreApi();
|
||||
|
||||
const handleNext = () => {
|
||||
const nextId = storeApi.getState().getNextBookingId();
|
||||
if (nextId !== null) {
|
||||
setSelectedBookingId(nextId);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevious = () => {
|
||||
const prevId = storeApi.getState().getPreviousBookingId();
|
||||
if (prevId !== null) {
|
||||
setSelectedBookingId(prevId);
|
||||
}
|
||||
};
|
||||
|
||||
const startTime = dayjs(booking.startTime).tz(userTimeZone);
|
||||
const endTime = dayjs(booking.endTime).tz(userTimeZone);
|
||||
|
||||
@@ -115,7 +149,7 @@ function BookingDetailsSheetInner({
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Sheet open={isOpen} onOpenChange={onClose}>
|
||||
<Sheet open={true} onOpenChange={handleClose}>
|
||||
<SheetContent className="overflow-y-auto">
|
||||
<SheetHeader
|
||||
showCloseButton={false}
|
||||
@@ -128,7 +162,7 @@ function BookingDetailsSheetInner({
|
||||
disabled={!hasPrevious}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onPrevious?.();
|
||||
handlePrevious();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -138,7 +172,7 @@ function BookingDetailsSheetInner({
|
||||
disabled={!hasNext}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onNext?.();
|
||||
handleNext();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -190,7 +224,7 @@ function BookingDetailsSheetInner({
|
||||
|
||||
<SheetFooter className="bg-muted border-subtle -mx-4 -mb-4 border-t pt-0 sm:-mx-6 sm:-my-6">
|
||||
<div className="flex w-full flex-row items-center justify-between gap-2 px-4 pb-4 pt-4">
|
||||
<Button color="secondary" StartIcon="x" onClick={onClose}>
|
||||
<Button color="secondary" StartIcon="x" onClick={handleClose}>
|
||||
{t("close")}
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ type BookingsCalendarProps = {
|
||||
status: BookingListingStatus;
|
||||
table: ReactTable<RowData>;
|
||||
isPending?: boolean;
|
||||
onOpenDetails: (bookingId: number) => void;
|
||||
currentWeekStart: dayjs.Dayjs;
|
||||
setCurrentWeekStart: (
|
||||
value: dayjs.Dayjs | ((old: dayjs.Dayjs) => dayjs.Dayjs | null) | null
|
||||
@@ -32,7 +31,6 @@ const COLUMN_IDS_TO_HIDE = ["dateRange"];
|
||||
export function BookingsCalendar({
|
||||
table,
|
||||
isPending = false,
|
||||
onOpenDetails,
|
||||
currentWeekStart,
|
||||
setCurrentWeekStart,
|
||||
bookings,
|
||||
@@ -77,7 +75,6 @@ export function BookingsCalendar({
|
||||
currentWeekStart={currentWeekStart}
|
||||
onWeekStartChange={handleWeekStartChange}
|
||||
isPending={isPending}
|
||||
onOpenDetails={onOpenDetails}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useReactTable, getCoreRowModel, getSortedRowModel } from "@tanstack/react-table";
|
||||
import { createParser, useQueryState } from "nuqs";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -11,8 +11,7 @@ import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues";
|
||||
|
||||
import { buildFilterColumns, getFilterColumnVisibility } from "../columns/filterColumns";
|
||||
import { useBookingCursor } from "../hooks/useBookingCursor";
|
||||
import { useSelectedBookingId } from "../hooks/useSelectedBookingId";
|
||||
import { BookingDetailsSheetStoreProvider } from "../store/bookingDetailsSheetStore";
|
||||
import type { RowData, BookingListingStatus } from "../types";
|
||||
import { BookingDetailsSheet } from "./BookingDetailsSheet";
|
||||
import { BookingsCalendar } from "./BookingsCalendar";
|
||||
@@ -43,19 +42,11 @@ export function BookingsCalendarContainer({
|
||||
const { t } = useLocale();
|
||||
const user = useMeQuery().data;
|
||||
|
||||
const [selectedBookingId, setSelectedBookingId] = useSelectedBookingId();
|
||||
const [currentWeekStart, setCurrentWeekStart] = useQueryState(
|
||||
"weekStart",
|
||||
weekStartParser.withDefault(dayjs().startOf("week"))
|
||||
);
|
||||
|
||||
const onOpenDetails = useCallback(
|
||||
(bookingId: number) => {
|
||||
setSelectedBookingId(bookingId);
|
||||
},
|
||||
[setSelectedBookingId]
|
||||
);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return buildFilterColumns({ t, permissions, status });
|
||||
}, [t, permissions, status]);
|
||||
@@ -90,42 +81,23 @@ export function BookingsCalendarContainer({
|
||||
});
|
||||
}, [data, currentWeekStart]);
|
||||
|
||||
const selectedBooking = useMemo(() => {
|
||||
if (!selectedBookingId) return null;
|
||||
return bookings.find((booking) => booking.id === selectedBookingId) ?? null;
|
||||
}, [selectedBookingId, bookings]);
|
||||
|
||||
const bookingNavigation = useBookingCursor({
|
||||
bookings,
|
||||
selectedBookingId,
|
||||
setSelectedBookingId,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<BookingDetailsSheetStoreProvider bookings={bookings}>
|
||||
<BookingsCalendar
|
||||
status={status}
|
||||
table={table}
|
||||
isPending={isPending}
|
||||
onOpenDetails={onOpenDetails}
|
||||
currentWeekStart={currentWeekStart}
|
||||
setCurrentWeekStart={setCurrentWeekStart}
|
||||
bookings={bookings}
|
||||
/>
|
||||
|
||||
<BookingDetailsSheet
|
||||
booking={selectedBooking}
|
||||
isOpen={!!selectedBooking}
|
||||
onClose={() => setSelectedBookingId(null)}
|
||||
userTimeZone={user?.timeZone}
|
||||
userTimeFormat={user?.timeFormat === null ? undefined : user?.timeFormat}
|
||||
userId={user?.id}
|
||||
userEmail={user?.email}
|
||||
onPrevious={bookingNavigation.onPrevious}
|
||||
hasPrevious={bookingNavigation.hasPrevious}
|
||||
onNext={bookingNavigation.onNext}
|
||||
hasNext={bookingNavigation.hasNext}
|
||||
/>
|
||||
</>
|
||||
</BookingDetailsSheetStoreProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Button } from "@calcom/ui/components/button";
|
||||
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
|
||||
import { useBookingDetailsSheetStore } from "../store/bookingDetailsSheetStore";
|
||||
import type { BookingOutput } from "../types";
|
||||
|
||||
type BookingsCalendarViewProps = {
|
||||
@@ -19,7 +20,6 @@ type BookingsCalendarViewProps = {
|
||||
currentWeekStart: dayjs.Dayjs;
|
||||
onWeekStartChange: (weekStart: dayjs.Dayjs) => void;
|
||||
isPending?: boolean;
|
||||
onOpenDetails: (bookingId: number) => void;
|
||||
};
|
||||
|
||||
export function BookingsCalendarView({
|
||||
@@ -27,8 +27,8 @@ export function BookingsCalendarView({
|
||||
currentWeekStart,
|
||||
onWeekStartChange,
|
||||
isPending = false,
|
||||
onOpenDetails,
|
||||
}: BookingsCalendarViewProps) {
|
||||
const setSelectedBookingId = useBookingDetailsSheetStore((state) => state.setSelectedBookingId);
|
||||
const { t } = useLocale();
|
||||
const { timezone } = useTimePreferences();
|
||||
const { resolvedTheme, forcedTheme } = useGetTheme();
|
||||
@@ -152,7 +152,7 @@ export function BookingsCalendarView({
|
||||
onEventClick={(event) => {
|
||||
const bookingId = event.options?.bookingId;
|
||||
if (bookingId) {
|
||||
onOpenDetails(bookingId);
|
||||
setSelectedBookingId(bookingId);
|
||||
}
|
||||
}}
|
||||
hideHeader
|
||||
|
||||
@@ -10,6 +10,7 @@ import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||
|
||||
import { useBookingDetailsSheetStore } from "../store/bookingDetailsSheetStore";
|
||||
import type { RowData, BookingListingStatus } from "../types";
|
||||
|
||||
const descriptionByStatus: Record<BookingListingStatus, string> = {
|
||||
@@ -25,25 +26,19 @@ type BookingsListViewProps = {
|
||||
table: ReactTable<RowData>;
|
||||
isPending: boolean;
|
||||
totalRowCount?: number;
|
||||
onOpenDetails: (bookingId: number) => void;
|
||||
};
|
||||
|
||||
export function BookingsList({
|
||||
status,
|
||||
table,
|
||||
isPending,
|
||||
totalRowCount,
|
||||
onOpenDetails,
|
||||
}: BookingsListViewProps) {
|
||||
export function BookingsList({ status, table, isPending, totalRowCount }: BookingsListViewProps) {
|
||||
const { t } = useLocale();
|
||||
const setSelectedBookingId = useBookingDetailsSheetStore((state) => state.setSelectedBookingId);
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
(row: Row<RowData>) => {
|
||||
if (!isSeparatorRow(row.original)) {
|
||||
onOpenDetails(row.original.booking.id);
|
||||
setSelectedBookingId(row.original.booking.id);
|
||||
}
|
||||
},
|
||||
[onOpenDetails]
|
||||
[setSelectedBookingId]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,8 +16,7 @@ import { useFacetedUniqueValues } from "~/bookings/hooks/useFacetedUniqueValues"
|
||||
|
||||
import { buildFilterColumns, getFilterColumnVisibility } from "../columns/filterColumns";
|
||||
import { buildListDisplayColumns } from "../columns/listColumns";
|
||||
import { useBookingCursor } from "../hooks/useBookingCursor";
|
||||
import { useSelectedBookingId } from "../hooks/useSelectedBookingId";
|
||||
import { BookingDetailsSheetStoreProvider } from "../store/bookingDetailsSheetStore";
|
||||
import type { RowData, BookingListingStatus } from "../types";
|
||||
import { BookingDetailsSheet } from "./BookingDetailsSheet";
|
||||
import { BookingsList } from "./BookingsList";
|
||||
@@ -43,8 +42,6 @@ export function BookingsListContainer({
|
||||
const user = useMeQuery().data;
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const [selectedBookingId, setSelectedBookingId] = useSelectedBookingId();
|
||||
|
||||
// Filter out separator rows and extract bookings
|
||||
const bookings = useMemo(() => {
|
||||
return data
|
||||
@@ -52,17 +49,6 @@ export function BookingsListContainer({
|
||||
.map((row) => row.booking);
|
||||
}, [data]);
|
||||
|
||||
const selectedBooking = useMemo(() => {
|
||||
if (!selectedBookingId) return null;
|
||||
return bookings.find((booking) => booking.id === selectedBookingId) ?? null;
|
||||
}, [selectedBookingId, bookings]);
|
||||
|
||||
const bookingNavigation = useBookingCursor({
|
||||
bookings,
|
||||
selectedBookingId,
|
||||
setSelectedBookingId,
|
||||
});
|
||||
|
||||
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
|
||||
const [rejectionReason, setRejectionReason] = useState<string>("");
|
||||
const [pendingRejection, setPendingRejection] = useState<{
|
||||
@@ -117,13 +103,6 @@ export function BookingsListContainer({
|
||||
});
|
||||
}, [pendingRejection, rejectionReason, confirmMutation]);
|
||||
|
||||
const onOpenDetails = useCallback(
|
||||
(bookingId: number) => {
|
||||
setSelectedBookingId(bookingId);
|
||||
},
|
||||
[setSelectedBookingId]
|
||||
);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const filterCols = buildFilterColumns({ t, permissions, status });
|
||||
const listCols = buildListDisplayColumns({
|
||||
@@ -163,7 +142,7 @@ export function BookingsListContainer({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BookingDetailsSheetStoreProvider bookings={bookings}>
|
||||
<Dialog open={rejectionDialogIsOpen} onOpenChange={handleRejectionDialogChange}>
|
||||
<DialogContent title={t("rejection_reason_title")} description={t("rejection_reason_description")}>
|
||||
<div>
|
||||
@@ -192,27 +171,14 @@ export function BookingsListContainer({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<BookingsList
|
||||
status={status}
|
||||
table={table}
|
||||
isPending={isPending}
|
||||
totalRowCount={totalRowCount}
|
||||
onOpenDetails={onOpenDetails}
|
||||
/>
|
||||
<BookingsList status={status} table={table} isPending={isPending} totalRowCount={totalRowCount} />
|
||||
|
||||
<BookingDetailsSheet
|
||||
booking={selectedBooking}
|
||||
isOpen={!!selectedBooking}
|
||||
onClose={() => setSelectedBookingId(null)}
|
||||
userTimeZone={user?.timeZone}
|
||||
userTimeFormat={user?.timeFormat === null ? undefined : user?.timeFormat}
|
||||
userId={user?.id}
|
||||
userEmail={user?.email}
|
||||
onPrevious={bookingNavigation.onPrevious}
|
||||
hasPrevious={bookingNavigation.hasPrevious}
|
||||
onNext={bookingNavigation.onNext}
|
||||
hasNext={bookingNavigation.hasNext}
|
||||
/>
|
||||
</>
|
||||
</BookingDetailsSheetStoreProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { createStore, useStore } from "zustand";
|
||||
|
||||
import { useSelectedBookingId } from "../hooks/useSelectedBookingId";
|
||||
import type { BookingOutput } from "../types";
|
||||
|
||||
interface BookingDetailsSheetStore {
|
||||
// State
|
||||
selectedBookingId: number | null;
|
||||
bookings: BookingOutput[];
|
||||
|
||||
// Actions
|
||||
setSelectedBookingId: (id: number | null) => void;
|
||||
setBookings: (bookings: BookingOutput[]) => void;
|
||||
clearSelection: () => void;
|
||||
|
||||
// Computed getters (used via selectors)
|
||||
getSelectedBooking: () => BookingOutput | null;
|
||||
getNextBookingId: () => number | null;
|
||||
getPreviousBookingId: () => number | null;
|
||||
hasNext: () => boolean;
|
||||
hasPrevious: () => boolean;
|
||||
}
|
||||
|
||||
type BookingDetailsSheetStoreType = ReturnType<typeof createBookingDetailsSheetStore>;
|
||||
|
||||
const createBookingDetailsSheetStore = (initialBookings: BookingOutput[] = []) => {
|
||||
return createStore<BookingDetailsSheetStore>((set, get) => ({
|
||||
// Initial state
|
||||
selectedBookingId: null,
|
||||
bookings: initialBookings,
|
||||
|
||||
// Actions
|
||||
setSelectedBookingId: (id) => set({ selectedBookingId: id }),
|
||||
setBookings: (bookings) => set({ bookings }),
|
||||
clearSelection: () => set({ selectedBookingId: null }),
|
||||
|
||||
// Computed getters
|
||||
getSelectedBooking: () => {
|
||||
const state = get();
|
||||
if (!state.selectedBookingId) return null;
|
||||
return state.bookings.find((booking) => booking.id === state.selectedBookingId) ?? null;
|
||||
},
|
||||
|
||||
getNextBookingId: () => {
|
||||
const state = get();
|
||||
if (!state.selectedBookingId) return null;
|
||||
|
||||
const currentIndex = state.bookings.findIndex((booking) => booking.id === state.selectedBookingId);
|
||||
if (currentIndex === -1 || currentIndex >= state.bookings.length - 1) return null;
|
||||
|
||||
return state.bookings[currentIndex + 1].id;
|
||||
},
|
||||
|
||||
getPreviousBookingId: () => {
|
||||
const state = get();
|
||||
if (!state.selectedBookingId) return null;
|
||||
|
||||
const currentIndex = state.bookings.findIndex((booking) => booking.id === state.selectedBookingId);
|
||||
if (currentIndex <= 0) return null;
|
||||
|
||||
return state.bookings[currentIndex - 1].id;
|
||||
},
|
||||
|
||||
hasNext: () => {
|
||||
const state = get();
|
||||
if (!state.selectedBookingId) return false;
|
||||
|
||||
const currentIndex = state.bookings.findIndex((booking) => booking.id === state.selectedBookingId);
|
||||
return currentIndex >= 0 && currentIndex < state.bookings.length - 1;
|
||||
},
|
||||
|
||||
hasPrevious: () => {
|
||||
const state = get();
|
||||
if (!state.selectedBookingId) return false;
|
||||
|
||||
const currentIndex = state.bookings.findIndex((booking) => booking.id === state.selectedBookingId);
|
||||
return currentIndex > 0;
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const BookingDetailsSheetStoreContext = React.createContext<BookingDetailsSheetStoreType | null>(null);
|
||||
|
||||
export function BookingDetailsSheetStoreProvider({
|
||||
children,
|
||||
bookings,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
bookings: BookingOutput[];
|
||||
}) {
|
||||
const [store] = React.useState(() => createBookingDetailsSheetStore(bookings));
|
||||
const [selectedBookingIdFromUrl, setSelectedBookingIdToUrl] = useSelectedBookingId();
|
||||
|
||||
// Update bookings when they change
|
||||
React.useEffect(() => {
|
||||
store.getState().setBookings(bookings);
|
||||
}, [bookings, store]);
|
||||
|
||||
// Sync Store → URL
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = store.subscribe((state) => {
|
||||
const storeId = state.selectedBookingId;
|
||||
if (storeId !== selectedBookingIdFromUrl) {
|
||||
setSelectedBookingIdToUrl(storeId);
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [selectedBookingIdFromUrl, setSelectedBookingIdToUrl, store]);
|
||||
|
||||
// Sync URL → Store
|
||||
React.useEffect(() => {
|
||||
const currentStoreId = store.getState().selectedBookingId;
|
||||
if (currentStoreId !== selectedBookingIdFromUrl) {
|
||||
store.getState().setSelectedBookingId(selectedBookingIdFromUrl);
|
||||
}
|
||||
}, [selectedBookingIdFromUrl, store]);
|
||||
|
||||
return (
|
||||
<BookingDetailsSheetStoreContext.Provider value={store}>
|
||||
{children}
|
||||
</BookingDetailsSheetStoreContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useBookingDetailsSheetStore<T>(selector: (state: BookingDetailsSheetStore) => T): T {
|
||||
const store = React.useContext(BookingDetailsSheetStoreContext);
|
||||
if (!store) {
|
||||
throw new Error("useBookingDetailsSheetStore must be used within BookingDetailsSheetStoreProvider");
|
||||
}
|
||||
return useStore(store, selector);
|
||||
}
|
||||
|
||||
// For direct store access (needed for subscribe and getState)
|
||||
export function useBookingDetailsSheetStoreApi() {
|
||||
const store = React.useContext(BookingDetailsSheetStoreContext);
|
||||
if (!store) {
|
||||
throw new Error("useBookingDetailsSheetStoreApi must be used within BookingDetailsSheetStoreProvider");
|
||||
}
|
||||
return store;
|
||||
}
|
||||
Reference in New Issue
Block a user