feat: don't update query params for booker atom (#20000)
* feat: Add `omitUpdatingParams` prop to Booker store for query param control * remove console * Update booking.tsx * fix: type check failing * undo changes * feat: Expose `onBookerStateChange` callback to track `booker-store` updates * add `onBookerStateChange` docs * Update store.ts * added `allowUpdatingUrlParams` for backwards compatibility
This commit is contained in:
1 parent
f8aa2701b6
commit
b1ebefb854
5 files changed
+141
-15
No files matched your search
@@ -120,6 +120,8 @@ Below is a list of props that can be passed to the booker atom.
|
||||
| view | No | Specifies the layout of the booker atom into column, week, or month view |
|
||||
| metadata | No | Used to pass custom metadata values into the booker. Metadata should be an object eg: `{ bookingSource: "website", userRole: "admin" }` |
|
||||
| bannerUrl | No | Adds custom banner to the booker atom |
|
||||
| onBookerStateChange | No | Callback function that is triggered when the state of the booker atom changes. |
|
||||
| allowUpdatingUrlParams | No | Boolean indicating if the URL parameters should be updated, defaults to false. |
|
||||
|
||||
Along with the props, booker atom accepts custom styles via the **customClassNames** prop. Below is a list of props that fall under this **customClassNames** prop.
|
||||
<p></p>
|
||||
|
||||
@@ -36,6 +36,8 @@ type StoreInitializeType = {
|
||||
teamMemberEmail?: string | null;
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
isPlatform?: boolean;
|
||||
allowUpdatingUrlParams?: boolean;
|
||||
};
|
||||
|
||||
type SeatedEventData = {
|
||||
@@ -161,6 +163,8 @@ export type BookerStore = {
|
||||
teamMemberEmail?: string | null;
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
isPlatform?: boolean;
|
||||
allowUpdatingUrlParams?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -180,7 +184,9 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
if (["week_view", "column_view"].includes(layout) && !get().selectedDate) {
|
||||
set({ selectedDate: dayjs().format("YYYY-MM-DD") });
|
||||
}
|
||||
updateQueryParam("layout", layout);
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("layout", layout);
|
||||
}
|
||||
return set({ layout });
|
||||
},
|
||||
selectedDate: getQueryParam("date") || null,
|
||||
@@ -194,14 +200,14 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
const currentSelection = dayjs(get().selectedDate);
|
||||
const newSelection = dayjs(selectedDate);
|
||||
set({ selectedDate });
|
||||
if (!omitUpdatingParams) {
|
||||
if (!omitUpdatingParams && (!get().isPlatform || get().allowUpdatingUrlParams)) {
|
||||
updateQueryParam("date", selectedDate ?? "");
|
||||
}
|
||||
|
||||
// Setting month make sure small calendar in fullscreen layouts also updates.
|
||||
if (newSelection.month() !== currentSelection.month()) {
|
||||
set({ month: newSelection.format("YYYY-MM") });
|
||||
if (!omitUpdatingParams) {
|
||||
if (!omitUpdatingParams && (!get().isPlatform || get().allowUpdatingUrlParams)) {
|
||||
updateQueryParam("month", newSelection.format("YYYY-MM"));
|
||||
}
|
||||
}
|
||||
@@ -223,11 +229,15 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
|
||||
if (newSelection.month() !== currentSelection.month()) {
|
||||
set({ month: newSelection.format("YYYY-MM") });
|
||||
updateQueryParam("month", newSelection.format("YYYY-MM"));
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("month", newSelection.format("YYYY-MM"));
|
||||
}
|
||||
}
|
||||
|
||||
set({ selectedDate: newSelectionFormatted });
|
||||
updateQueryParam("date", newSelectionFormatted);
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("date", newSelectionFormatted);
|
||||
}
|
||||
},
|
||||
username: null,
|
||||
eventSlug: null,
|
||||
@@ -249,7 +259,9 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
return;
|
||||
}
|
||||
set({ month, selectedTimeslot: null });
|
||||
updateQueryParam("month", month ?? "");
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("month", month ?? "");
|
||||
}
|
||||
get().setSelectedDate(null);
|
||||
},
|
||||
dayCount: BOOKER_NUMBER_OF_DAYS_TO_LOAD > 0 ? BOOKER_NUMBER_OF_DAYS_TO_LOAD : null,
|
||||
@@ -265,10 +277,13 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
},
|
||||
setSeatedEventData: (seatedEventData: SeatedEventData) => {
|
||||
set({ seatedEventData });
|
||||
updateQueryParam("bookingUid", seatedEventData.bookingUid ?? "null");
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("bookingUid", seatedEventData.bookingUid ?? "null");
|
||||
}
|
||||
},
|
||||
// This is different from timeZone in timePreferencesStore, because timeZone in timePreferencesStore is the preferred timezone of the booker,
|
||||
// it is the timezone configured through query param. So, this is in a way the preference of the person who shared the link.
|
||||
// it is the timezone configured through query param. So, this is in a way the preference of the person who shared the link.
|
||||
timezone: getQueryParam("cal.tz") ?? null,
|
||||
setTimezone: (timezone: string | null) => {
|
||||
set({ timezone });
|
||||
@@ -291,6 +306,8 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isPlatform = false,
|
||||
allowUpdatingUrlParams = true,
|
||||
}: StoreInitializeType) => {
|
||||
const selectedDateInStore = get().selectedDate;
|
||||
|
||||
@@ -330,6 +347,8 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isPlatform,
|
||||
allowUpdatingUrlParams,
|
||||
});
|
||||
|
||||
if (durationConfig?.includes(Number(getQueryParam("duration")))) {
|
||||
@@ -353,7 +372,9 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
"minutes"
|
||||
);
|
||||
set({ selectedDuration: originalBookingLength });
|
||||
updateQueryParam("duration", originalBookingLength ?? "");
|
||||
if (!isPlatform || allowUpdatingUrlParams) {
|
||||
updateQueryParam("duration", originalBookingLength ?? "");
|
||||
}
|
||||
}
|
||||
if (month) set({ month });
|
||||
|
||||
@@ -367,9 +388,12 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
selectedTimeslot,
|
||||
isInstantMeeting,
|
||||
});
|
||||
updateQueryParam("month", month);
|
||||
updateQueryParam("date", selectedDate ?? "");
|
||||
updateQueryParam("slot", selectedTimeslot ?? "", false);
|
||||
|
||||
if (!isPlatform || allowUpdatingUrlParams) {
|
||||
updateQueryParam("month", month);
|
||||
updateQueryParam("date", selectedDate ?? "");
|
||||
updateQueryParam("slot", selectedTimeslot ?? "", false);
|
||||
}
|
||||
}
|
||||
//removeQueryParam("layout");
|
||||
},
|
||||
@@ -377,7 +401,9 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
selectedDuration: null,
|
||||
setSelectedDuration: (selectedDuration: number | null) => {
|
||||
set({ selectedDuration });
|
||||
updateQueryParam("duration", selectedDuration ?? "");
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("duration", selectedDuration ?? "");
|
||||
}
|
||||
},
|
||||
setBookingData: (bookingData: GetBookingType | null | undefined) => {
|
||||
set({ bookingData: bookingData ?? null });
|
||||
@@ -396,7 +422,9 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
},
|
||||
setSelectedTimeslot: (selectedTimeslot: string | null) => {
|
||||
set({ selectedTimeslot });
|
||||
updateQueryParam("slot", selectedTimeslot ?? "", false);
|
||||
if (!get().isPlatform || get().allowUpdatingUrlParams) {
|
||||
updateQueryParam("slot", selectedTimeslot ?? "", false);
|
||||
}
|
||||
},
|
||||
formValues: {},
|
||||
setFormValues: (formValues: Record<string, any>) => {
|
||||
@@ -406,6 +434,8 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
setOrg: (org: string | null | undefined) => {
|
||||
set({ org });
|
||||
},
|
||||
isPlatform: false,
|
||||
allowUpdatingUrlParams: true,
|
||||
}));
|
||||
|
||||
export const useInitializeBookerStore = ({
|
||||
@@ -426,6 +456,8 @@ export const useInitializeBookerStore = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isPlatform = false,
|
||||
allowUpdatingUrlParams = true,
|
||||
}: StoreInitializeType) => {
|
||||
const initializeStore = useBookerStore((state) => state.initialize);
|
||||
useEffect(() => {
|
||||
@@ -447,6 +479,8 @@ export const useInitializeBookerStore = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isPlatform,
|
||||
allowUpdatingUrlParams,
|
||||
});
|
||||
}, [
|
||||
initializeStore,
|
||||
@@ -467,5 +501,7 @@ export const useInitializeBookerStore = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isPlatform,
|
||||
allowUpdatingUrlParams,
|
||||
]);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useMemo, useEffect, useCallback, useState } from "react";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import debounce from "lodash/debounce";
|
||||
import { useMemo, useEffect, useCallback, useState, useRef } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -40,6 +42,7 @@ import { AtomsWrapper } from "../src/components/atoms-wrapper";
|
||||
import type {
|
||||
BookerPlatformWrapperAtomPropsForIndividual,
|
||||
BookerPlatformWrapperAtomPropsForTeam,
|
||||
BookerStoreValues,
|
||||
} from "./types";
|
||||
|
||||
export const BookerPlatformWrapper = (
|
||||
@@ -53,6 +56,8 @@ export const BookerPlatformWrapper = (
|
||||
crmAppSlug,
|
||||
crmOwnerRecordType,
|
||||
preventEventTypeRedirect,
|
||||
onBookerStateChange,
|
||||
allowUpdatingUrlParams = false,
|
||||
} = props;
|
||||
const layout = BookerLayouts[view];
|
||||
|
||||
@@ -70,6 +75,48 @@ export const BookerPlatformWrapper = (
|
||||
const [isOverlayCalendarEnabled, setIsOverlayCalendarEnabled] = useState(
|
||||
Boolean(localStorage?.getItem?.("overlayCalendarSwitchDefault"))
|
||||
);
|
||||
const prevStateRef = useRef<BookerStoreValues | null>(null);
|
||||
const getStateValues = useCallback(
|
||||
(state: ReturnType<typeof useBookerStore.getState>): BookerStoreValues => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(state).filter(([_, value]) => typeof value !== "function")
|
||||
) as BookerStoreValues;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const debouncedStateChange = useMemo(() => {
|
||||
return debounce(
|
||||
(currentStateValues: BookerStoreValues, callback: (values: BookerStoreValues) => void) => {
|
||||
const prevState = prevStateRef.current;
|
||||
const stateChanged = !prevState || JSON.stringify(prevState) !== JSON.stringify(currentStateValues);
|
||||
|
||||
if (stateChanged) {
|
||||
callback(currentStateValues);
|
||||
prevStateRef.current = currentStateValues;
|
||||
}
|
||||
},
|
||||
50
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onBookerStateChange) return;
|
||||
|
||||
const unsubscribe = useBookerStore.subscribe((state) => {
|
||||
const currentStateValues = getStateValues(state);
|
||||
debouncedStateChange(currentStateValues, onBookerStateChange);
|
||||
});
|
||||
|
||||
// Initial call with current state
|
||||
const initialState = getStateValues(useBookerStore.getState());
|
||||
onBookerStateChange(initialState);
|
||||
prevStateRef.current = initialState;
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
debouncedStateChange.cancel();
|
||||
};
|
||||
}, [onBookerStateChange, getStateValues, debouncedStateChange]);
|
||||
|
||||
useGetBookingForReschedule({
|
||||
uid: props.rescheduleUid ?? props.bookingUid ?? "",
|
||||
@@ -166,6 +213,8 @@ export const BookerPlatformWrapper = (
|
||||
org: props.entity?.orgSlug,
|
||||
username,
|
||||
bookingData,
|
||||
isPlatform: true,
|
||||
allowUpdatingUrlParams,
|
||||
});
|
||||
const [dayCount] = useBookerStore((state) => [state.dayCount, state.setDayCount], shallow);
|
||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
||||
@@ -397,6 +446,13 @@ export const BookerPlatformWrapper = (
|
||||
},
|
||||
[setIsOverlayCalendarEnabled]
|
||||
);
|
||||
const selectedDateProp = useMemo(
|
||||
() => dayjs(props.selectedDate).format("YYYY-MM-DD"),
|
||||
[props.selectedDate]
|
||||
);
|
||||
useEffect(() => {
|
||||
setSelectedDate(selectedDateProp, true);
|
||||
}, [selectedDateProp]);
|
||||
|
||||
useEffect(() => {
|
||||
// reset booker whenever it's unmounted
|
||||
@@ -408,7 +464,6 @@ export const BookerPlatformWrapper = (
|
||||
setSelectedDuration(null);
|
||||
setOrg(null);
|
||||
setSelectedMonth(null);
|
||||
setSelectedDuration(null);
|
||||
if (props.rescheduleUid) {
|
||||
// clean booking data from cache
|
||||
queryClient.removeQueries({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
||||
import type { BookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { BookingResponse } from "@calcom/platform-libraries";
|
||||
import type {
|
||||
ApiSuccessResponse,
|
||||
@@ -10,6 +11,30 @@ import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { UseCreateBookingInput } from "../hooks/bookings/useCreateBooking";
|
||||
|
||||
// Type that includes only the data values from BookerStore (excluding functions)
|
||||
export type BookerStoreValues = Omit<
|
||||
BookerStore,
|
||||
| "setState"
|
||||
| "setLayout"
|
||||
| "setSelectedDate"
|
||||
| "setSelectedDatesAndTimes"
|
||||
| "addToSelectedDate"
|
||||
| "setVerifiedEmail"
|
||||
| "setMonth"
|
||||
| "setDayCount"
|
||||
| "setSeatedEventData"
|
||||
| "setTimezone"
|
||||
| "initialize"
|
||||
| "setSelectedDuration"
|
||||
| "setBookingData"
|
||||
| "setRecurringEventCount"
|
||||
| "setOccurenceCount"
|
||||
| "setTentativeSelectedTimeslots"
|
||||
| "setSelectedTimeslot"
|
||||
| "setFormValues"
|
||||
| "setOrg"
|
||||
>;
|
||||
|
||||
export type BookerPlatformWrapperAtomProps = Omit<
|
||||
BookerProps,
|
||||
"username" | "entity" | "isTeamEvent" | "teamId"
|
||||
@@ -39,6 +64,7 @@ export type BookerPlatformWrapperAtomProps = Omit<
|
||||
onReserveSlotError?: (data: ApiErrorResponse) => void;
|
||||
onDeleteSlotSuccess?: (data: ApiSuccessResponseWithoutData) => void;
|
||||
onDeleteSlotError?: (data: ApiErrorResponse) => void;
|
||||
onBookerStateChange?: (state: BookerStoreValues) => void;
|
||||
locationUrl?: string;
|
||||
view?: VIEW_TYPE;
|
||||
metadata?: Record<string, string>;
|
||||
@@ -46,6 +72,7 @@ export type BookerPlatformWrapperAtomProps = Omit<
|
||||
onDryRunSuccess?: () => void;
|
||||
hostsLimit?: number;
|
||||
preventEventTypeRedirect?: boolean;
|
||||
allowUpdatingUrlParams?: boolean;
|
||||
};
|
||||
|
||||
type VIEW_TYPE = keyof typeof BookerLayouts;
|
||||
|
||||
@@ -101,6 +101,10 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
router.push(`/${data.data.uid}`);
|
||||
}
|
||||
}}
|
||||
onBookerStateChange={(bookerState) => {
|
||||
console.log("Booker state updated:", bookerState);
|
||||
// You can perform any actions based on the updated state here
|
||||
}}
|
||||
metadata={{ CustomKey: "CustomValue" }}
|
||||
duration={eventTypeDuration}
|
||||
customClassNames={{
|
||||
@@ -120,6 +124,8 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
? { isTeamEvent: true, teamId: teams?.[0]?.id || 0 }
|
||||
: { username: props.calUsername })}
|
||||
hostsLimit={3}
|
||||
selectedDate={new Date("2025-03-25")}
|
||||
allowUpdatingUrlParams={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user