refactor: useBookings, useOverlayCalendar
This commit is contained in:
parent
c9ca50fe85
commit
a893c22ea9
|
@ -21,11 +21,16 @@ import { EventMeta } from "./components/EventMeta";
|
|||
import { Header } from "./components/Header";
|
||||
import { InstantBooking } from "./components/InstantBooking";
|
||||
import { LargeCalendar } from "./components/LargeCalendar";
|
||||
import { OverlayCalendarContinueModal } from "./components/OverlayCalendar/OverlayCalendarContinueModal";
|
||||
import { OverlayCalendarSettingsModal } from "./components/OverlayCalendar/OverlayCalendarSettingsModal";
|
||||
import { OverlayCalendarSwitch } from "./components/OverlayCalendar/OverlayCalendarSwitch";
|
||||
import { RedirectToInstantMeetingModal } from "./components/RedirectToInstantMeetingModal";
|
||||
import { BookerSection } from "./components/Section";
|
||||
import { Away, NotFound } from "./components/Unavailable";
|
||||
import { useBookerLayout } from "./components/hooks/useBookerLayout";
|
||||
import { useBookingForm } from "./components/hooks/useBookingForm";
|
||||
import { useBookings } from "./components/hooks/useBookings";
|
||||
import { useOverlayCalendar } from "./components/hooks/useOverlayCalendar";
|
||||
import { useSlots } from "./components/hooks/useSlots";
|
||||
import { useVerifyEmail } from "./components/hooks/useVerifyEmail";
|
||||
import { fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config";
|
||||
|
@ -161,19 +166,21 @@ const BookerComponent = ({
|
|||
});
|
||||
|
||||
const {
|
||||
handleBookEvent,
|
||||
bookerFormErrorRef,
|
||||
key,
|
||||
errors,
|
||||
loadingStates,
|
||||
hasInstantMeetingTokenExpired,
|
||||
formEmail,
|
||||
formName,
|
||||
bookingForm,
|
||||
beforeVerifyEmail,
|
||||
errors: formErrors,
|
||||
} = useBookingForm({
|
||||
event,
|
||||
});
|
||||
|
||||
const { handleBookEvent, hasInstantMeetingTokenExpired, errors, loadingStates } = useBookings({
|
||||
event,
|
||||
hashedLink,
|
||||
bookingForm,
|
||||
});
|
||||
|
||||
const {
|
||||
|
@ -189,6 +196,17 @@ const BookerComponent = ({
|
|||
onVerifyEmail: beforeVerifyEmail,
|
||||
});
|
||||
|
||||
const {
|
||||
isOverlayCalendarEnabled,
|
||||
connectedCalendars,
|
||||
loadingConnectedCalendar,
|
||||
handleCloseContinueModal,
|
||||
handleCloseSettingsModal,
|
||||
isOpenOverlayContinueModal,
|
||||
isOpenOverlaySettingsModal,
|
||||
handleToggleConnectedCalendar,
|
||||
} = useOverlayCalendar();
|
||||
|
||||
useEffect(() => {
|
||||
if (event.isLoading) return setBookerState("loading");
|
||||
if (!selectedDate) return setBookerState("selecting_date");
|
||||
|
@ -219,7 +237,7 @@ const BookerComponent = ({
|
|||
}}
|
||||
onSubmit={renderConfirmNotVerifyEmailButtonCond ? handleBookEvent : handleVerifyEmail}
|
||||
errorRef={bookerFormErrorRef}
|
||||
errors={errors}
|
||||
errors={{ ...formErrors, ...errors }}
|
||||
loadingStates={loadingStates}
|
||||
renderConfirmNotVerifyEmailButtonCond={renderConfirmNotVerifyEmailButtonCond}
|
||||
bookingForm={bookingForm as unknown as UseFormReturn<FieldValues, any>}
|
||||
|
@ -308,6 +326,26 @@ const BookerComponent = ({
|
|||
extraDays={layout === BookerLayouts.COLUMN_VIEW ? columnViewExtraDays.current : extraDays}
|
||||
isMobile={isMobile}
|
||||
nextSlots={nextSlots}
|
||||
renderOverlay={() =>
|
||||
isEmbed ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
<OverlayCalendarSwitch enabled={isOverlayCalendarEnabled} />
|
||||
<OverlayCalendarContinueModal
|
||||
open={isOpenOverlayContinueModal}
|
||||
onClose={handleCloseContinueModal}
|
||||
/>
|
||||
<OverlayCalendarSettingsModal
|
||||
connectedCalendars={connectedCalendars}
|
||||
open={isOpenOverlaySettingsModal}
|
||||
onClose={handleCloseSettingsModal}
|
||||
isLoading={loadingConnectedCalendar}
|
||||
onToggleConnectedCalendar={handleToggleConnectedCalendar}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</BookerSection>
|
||||
)}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import type {
|
||||
IUseBookingFormErrors,
|
||||
IUseBookingFormLoadingStates,
|
||||
} from "bookings/Booker/components/hooks/useBookingForm";
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { useState } from "react";
|
||||
import type { FieldError } from "react-hook-form";
|
||||
|
@ -14,6 +10,8 @@ import { Calendar } from "@calcom/ui/components/icon";
|
|||
|
||||
import { useBookerStore } from "../../store";
|
||||
import type { useEventReturnType } from "../../utils/event";
|
||||
import type { useBookingFormReturnType } from "../hooks/useBookingForm";
|
||||
import type { IUseBookingErrors, IUseBookingLoadingStates } from "../hooks/useBookings";
|
||||
import { BookingFields } from "./BookingFields";
|
||||
import { FormSkeleton } from "./Skeleton";
|
||||
|
||||
|
@ -21,8 +19,8 @@ type BookEventFormProps = {
|
|||
onCancel?: () => void;
|
||||
onSubmit: () => void;
|
||||
errorRef: React.RefObject<HTMLDivElement>;
|
||||
errors: IUseBookingFormErrors;
|
||||
loadingStates: IUseBookingFormLoadingStates;
|
||||
errors: useBookingFormReturnType["errors"] & IUseBookingErrors;
|
||||
loadingStates: IUseBookingLoadingStates;
|
||||
children?: React.ReactNode;
|
||||
bookingForm: UseFormReturn<FieldValues, any>;
|
||||
renderConfirmNotVerifyEmailButtonCond: boolean;
|
||||
|
@ -93,7 +91,7 @@ export const BookEventForm = ({
|
|||
rescheduleUid={rescheduleUid || undefined}
|
||||
bookingData={bookingData}
|
||||
/>
|
||||
{errors.hasFormErrors && (
|
||||
{(errors.hasFormErrors || errors.hasDataErrors) && (
|
||||
<div data-testid="booking-fail">
|
||||
<Alert
|
||||
ref={errorRef}
|
||||
|
|
|
@ -12,7 +12,6 @@ import { Calendar, Columns, Grid } from "@calcom/ui/components/icon";
|
|||
import { TimeFormatToggle } from "../../components/TimeFormatToggle";
|
||||
import { useBookerStore } from "../store";
|
||||
import type { BookerLayout } from "../types";
|
||||
import { OverlayCalendarContainer } from "./OverlayCalendar/OverlayCalendarContainer";
|
||||
|
||||
export function Header({
|
||||
extraDays,
|
||||
|
@ -21,6 +20,7 @@ export function Header({
|
|||
nextSlots,
|
||||
eventSlug,
|
||||
isMyLink,
|
||||
renderOverlay,
|
||||
}: {
|
||||
extraDays: number;
|
||||
isMobile: boolean;
|
||||
|
@ -28,6 +28,7 @@ export function Header({
|
|||
nextSlots: number;
|
||||
eventSlug: string;
|
||||
isMyLink: boolean;
|
||||
renderOverlay?: () => JSX.Element | null;
|
||||
}) {
|
||||
const { t, i18n } = useLocale();
|
||||
|
||||
|
@ -60,7 +61,6 @@ export function Header({
|
|||
<LayoutToggle onLayoutToggle={onLayoutToggle} layout={layout} enabledLayouts={enabledLayouts} />
|
||||
);
|
||||
};
|
||||
|
||||
// In month view we only show the layout toggle.
|
||||
if (isMonthView) {
|
||||
return (
|
||||
|
@ -75,7 +75,7 @@ export function Header({
|
|||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<OverlayCalendarContainer />
|
||||
renderOverlay?.()
|
||||
)}
|
||||
<LayoutToggleWithData />
|
||||
</div>
|
||||
|
@ -136,7 +136,7 @@ export function Header({
|
|||
</ButtonGroup>
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
<OverlayCalendarContainer />
|
||||
{renderOverlay?.()}
|
||||
<TimeFormatToggle />
|
||||
<div className="fixed top-4 ltr:right-4 rtl:left-4">
|
||||
<LayoutToggleWithData />
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { useOverlayCalendarReturnType } from "bookings/Booker/components/hooks/useOverlayCalendar";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
Alert,
|
||||
Dialog,
|
||||
|
@ -21,11 +21,13 @@ import {
|
|||
import { Calendar } from "@calcom/ui/components/icon";
|
||||
|
||||
import { useLocalSet } from "../hooks/useLocalSet";
|
||||
import { useOverlayCalendarStore } from "./store";
|
||||
|
||||
interface IOverlayCalendarContinueModalProps {
|
||||
open?: boolean;
|
||||
onClose?: (state: boolean) => void;
|
||||
isLoading: boolean;
|
||||
connectedCalendars: useOverlayCalendarReturnType["connectedCalendars"];
|
||||
onToggleConnectedCalendar: (externalCalendarId: string, credentialId: number) => void;
|
||||
}
|
||||
|
||||
const SkeletonLoader = () => {
|
||||
|
@ -41,15 +43,14 @@ const SkeletonLoader = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export function OverlayCalendarSettingsModal(props: IOverlayCalendarContinueModalProps) {
|
||||
const utils = trpc.useContext();
|
||||
const [initalised, setInitalised] = useState(false);
|
||||
const searchParams = useSearchParams();
|
||||
const setOverlayBusyDates = useOverlayCalendarStore((state) => state.setOverlayBusyDates);
|
||||
const { data, isLoading } = trpc.viewer.connectedCalendars.useQuery(undefined, {
|
||||
enabled: !!props.open || Boolean(searchParams?.get("overlayCalendar")),
|
||||
});
|
||||
const { toggleValue, hasItem, set } = useLocalSet<{
|
||||
export function OverlayCalendarSettingsModal({
|
||||
connectedCalendars,
|
||||
isLoading,
|
||||
open,
|
||||
onClose,
|
||||
onToggledConnectedCalendar,
|
||||
}: IOverlayCalendarContinueModalProps) {
|
||||
const { hasItem } = useLocalSet<{
|
||||
credentialId: number;
|
||||
externalId: string;
|
||||
}>("toggledConnectedCalendars", []);
|
||||
|
@ -57,24 +58,9 @@ export function OverlayCalendarSettingsModal(props: IOverlayCalendarContinueModa
|
|||
const router = useRouter();
|
||||
const { t } = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.connectedCalendars && set.size === 0 && !initalised) {
|
||||
data?.connectedCalendars.forEach((item) => {
|
||||
item.calendars?.forEach((cal) => {
|
||||
const id = { credentialId: item.credentialId, externalId: cal.externalId };
|
||||
if (cal.primary) {
|
||||
toggleValue(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
setInitalised(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, hasItem, set, initalised]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={props.open} onOpenChange={props.onClose}>
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent
|
||||
enableOverflow
|
||||
type="creation"
|
||||
|
@ -86,7 +72,7 @@ export function OverlayCalendarSettingsModal(props: IOverlayCalendarContinueModa
|
|||
<SkeletonLoader />
|
||||
) : (
|
||||
<>
|
||||
{data?.connectedCalendars.length === 0 ? (
|
||||
{connectedCalendars.length === 0 ? (
|
||||
<EmptyScreen
|
||||
Icon={Calendar}
|
||||
headline={t("no_calendar_installed")}
|
||||
|
@ -96,7 +82,7 @@ export function OverlayCalendarSettingsModal(props: IOverlayCalendarContinueModa
|
|||
/>
|
||||
) : (
|
||||
<>
|
||||
{data?.connectedCalendars.map((item) => (
|
||||
{connectedCalendars.map((item) => (
|
||||
<Fragment key={item.credentialId}>
|
||||
{item.error && !item.calendars && (
|
||||
<Alert severity="error" title={item.error.message} />
|
||||
|
@ -139,12 +125,7 @@ export function OverlayCalendarSettingsModal(props: IOverlayCalendarContinueModa
|
|||
externalId: cal.externalId,
|
||||
})}
|
||||
onCheckedChange={() => {
|
||||
toggleValue({
|
||||
credentialId: item.credentialId,
|
||||
externalId: cal.externalId,
|
||||
});
|
||||
setOverlayBusyDates([]);
|
||||
utils.viewer.availability.calendarOverlay.reset();
|
||||
onToggledConnectedCalendar(cal.externalId, item.credentialId);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={id}>{cal.name}</label>
|
||||
|
|
|
@ -1,29 +1,21 @@
|
|||
import { useSession } from "next-auth/react";
|
||||
import { useSearchParams, useRouter, usePathname } from "next/navigation";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, Switch } from "@calcom/ui";
|
||||
import { Settings } from "@calcom/ui/components/icon";
|
||||
|
||||
import { useBookerStore } from "../../store";
|
||||
import { OverlayCalendarContinueModal } from "../OverlayCalendar/OverlayCalendarContinueModal";
|
||||
import { OverlayCalendarSettingsModal } from "../OverlayCalendar/OverlayCalendarSettingsModal";
|
||||
import { useLocalSet } from "../hooks/useLocalSet";
|
||||
import { useOverlayCalendarStore } from "./store";
|
||||
|
||||
interface OverlayCalendarSwitchProps {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
function OverlayCalendarSwitch({ enabled }: OverlayCalendarSwitchProps) {
|
||||
export function OverlayCalendarSwitch({ enabled }: OverlayCalendarSwitchProps) {
|
||||
const { t } = useLocale();
|
||||
const setContinueWithProvider = useOverlayCalendarStore((state) => state.setContinueWithProviderModal);
|
||||
const setCalendarSettingsOverlay = useOverlayCalendarStore(
|
||||
|
@ -106,90 +98,3 @@ function OverlayCalendarSwitch({ enabled }: OverlayCalendarSwitchProps) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function OverlayCalendarContainer() {
|
||||
const isEmbed = useIsEmbed();
|
||||
const searchParams = useSearchParams();
|
||||
const [continueWithProvider, setContinueWithProvider] = useOverlayCalendarStore(
|
||||
(state) => [state.continueWithProviderModal, state.setContinueWithProviderModal],
|
||||
shallow
|
||||
);
|
||||
const [calendarSettingsOverlay, setCalendarSettingsOverlay] = useOverlayCalendarStore(
|
||||
(state) => [state.calendarSettingsOverlayModal, state.setCalendarSettingsOverlayModal],
|
||||
shallow
|
||||
);
|
||||
|
||||
const { data: session } = useSession();
|
||||
const setOverlayBusyDates = useOverlayCalendarStore((state) => state.setOverlayBusyDates);
|
||||
const switchEnabled =
|
||||
searchParams?.get("overlayCalendar") === "true" ||
|
||||
localStorage.getItem("overlayCalendarSwitchDefault") === "true";
|
||||
|
||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
||||
const { timezone } = useTimePreferences();
|
||||
|
||||
// Move this to a hook
|
||||
const { set, clearSet } = useLocalSet<{
|
||||
credentialId: number;
|
||||
externalId: string;
|
||||
}>("toggledConnectedCalendars", []);
|
||||
|
||||
const { data: overlayBusyDates } = trpc.viewer.availability.calendarOverlay.useQuery(
|
||||
{
|
||||
loggedInUsersTz: timezone || "Europe/London",
|
||||
dateFrom: selectedDate,
|
||||
dateTo: selectedDate,
|
||||
calendarsToLoad: Array.from(set).map((item) => ({
|
||||
credentialId: item.credentialId,
|
||||
externalId: item.externalId,
|
||||
})),
|
||||
},
|
||||
{
|
||||
enabled: !!session && set.size > 0 && switchEnabled,
|
||||
onError: () => {
|
||||
clearSet();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (overlayBusyDates) {
|
||||
const nowDate = dayjs();
|
||||
const usersTimezoneDate = nowDate.tz(timezone);
|
||||
|
||||
const offset = (usersTimezoneDate.utcOffset() - nowDate.utcOffset()) / 60;
|
||||
|
||||
const offsettedArray = overlayBusyDates.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
start: dayjs(item.start).add(offset, "hours").toDate(),
|
||||
end: dayjs(item.end).add(offset, "hours").toDate(),
|
||||
};
|
||||
});
|
||||
setOverlayBusyDates(offsettedArray);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [overlayBusyDates]);
|
||||
|
||||
if (isEmbed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverlayCalendarSwitch enabled={switchEnabled} />
|
||||
<OverlayCalendarContinueModal
|
||||
open={continueWithProvider}
|
||||
onClose={(val) => {
|
||||
setContinueWithProvider(val);
|
||||
}}
|
||||
/>
|
||||
<OverlayCalendarSettingsModal
|
||||
open={calendarSettingsOverlay}
|
||||
onClose={(val) => {
|
||||
setCalendarSettingsOverlay(val);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,71 +1,27 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRef, useState } from "react";
|
||||
import type { FieldError } from "react-hook-form";
|
||||
import { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
||||
import {
|
||||
createBooking,
|
||||
createRecurringBooking,
|
||||
mapBookingToMutationInput,
|
||||
mapRecurringBookingToMutationInput,
|
||||
createInstantBooking,
|
||||
useTimePreferences,
|
||||
} from "@calcom/features/bookings/lib";
|
||||
import getBookingResponsesSchema from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import { getFullName } from "@calcom/features/form-builder/utils";
|
||||
import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { showToast } from "@calcom/ui";
|
||||
|
||||
import { useInitialFormValues } from "./useInitialFormValues";
|
||||
|
||||
export interface IUseBookingForm {
|
||||
event: useEventReturnType;
|
||||
hashedLink?: string | null;
|
||||
}
|
||||
|
||||
export interface IUseBookingFormErrors {
|
||||
hasFormErrors: boolean;
|
||||
formErrors: FieldError | undefined;
|
||||
dataErrors: unknown;
|
||||
}
|
||||
export type useBookingFormReturnType = ReturnType<typeof useBookingForm>;
|
||||
|
||||
export interface IUseBookingFormLoadingStates {
|
||||
creatingBooking: boolean;
|
||||
creatingRecurringBooking: boolean;
|
||||
creatingInstantBooking: boolean;
|
||||
}
|
||||
export const useBookingForm = ({ event, hashedLink }: IUseBookingForm) => {
|
||||
const router = useRouter();
|
||||
const eventSlug = useBookerStore((state) => state.eventSlug);
|
||||
const setFormValues = useBookerStore((state) => state.setFormValues);
|
||||
export const useBookingForm = ({ event }: IUseBookingForm) => {
|
||||
const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
|
||||
const bookingData = useBookerStore((state) => state.bookingData);
|
||||
const timeslot = useBookerStore((state) => state.selectedTimeslot);
|
||||
const seatedEventData = useBookerStore((state) => state.seatedEventData);
|
||||
const { t, i18n } = useLocale();
|
||||
const bookingSuccessRedirect = useBookingSuccessRedirect();
|
||||
const { t } = useLocale();
|
||||
const bookerFormErrorRef = useRef<HTMLDivElement>(null);
|
||||
const [expiryTime, setExpiryTime] = useState<Date | undefined>();
|
||||
const recurringEventCount = useBookerStore((state) => state.recurringEventCount);
|
||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
||||
const duration = useBookerStore((state) => state.selectedDuration);
|
||||
const { timezone } = useTimePreferences();
|
||||
const username = useBookerStore((state) => state.username);
|
||||
const routerQuery = useRouterQuery();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const bookingFormSchema = z
|
||||
.object({
|
||||
|
@ -108,178 +64,6 @@ export const useBookingForm = ({ event, hashedLink }: IUseBookingForm) => {
|
|||
});
|
||||
const email = bookingForm.watch("responses.email");
|
||||
const name = bookingForm.watch("responses.name");
|
||||
const bookingId = parseInt(getQueryParam("bookingId") || "0");
|
||||
const hasInstantMeetingTokenExpired = expiryTime && new Date(expiryTime) < new Date();
|
||||
const _instantBooking = trpc.viewer.bookings.getInstantBookingLocation.useQuery(
|
||||
{
|
||||
bookingId: bookingId,
|
||||
},
|
||||
{
|
||||
enabled: !!bookingId && !hasInstantMeetingTokenExpired,
|
||||
refetchInterval: 2000,
|
||||
onSuccess: (data) => {
|
||||
try {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
|
||||
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
|
||||
data.booking?.metadata || {}
|
||||
)?.videoCallUrl;
|
||||
|
||||
if (locationVideoCallUrl) {
|
||||
router.push(locationVideoCallUrl);
|
||||
} else {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const createBookingMutation = useMutation(createBooking, {
|
||||
onSuccess: (responseData) => {
|
||||
const { uid, paymentUid } = responseData;
|
||||
const fullName = getFullName(bookingForm.getValues("responses.name"));
|
||||
if (paymentUid) {
|
||||
router.push(
|
||||
createPaymentLink({
|
||||
paymentUid,
|
||||
date: timeslot,
|
||||
name: fullName,
|
||||
email: bookingForm.getValues("responses.email"),
|
||||
absolute: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!uid) {
|
||||
console.error("No uid returned from createBookingMutation");
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {
|
||||
isSuccessBookingPage: true,
|
||||
email: bookingForm.getValues("responses.email"),
|
||||
eventTypeSlug: eventSlug,
|
||||
seatReferenceUid: "seatReferenceUid" in responseData ? responseData.seatReferenceUid : null,
|
||||
formerTime:
|
||||
isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined,
|
||||
};
|
||||
|
||||
bookingSuccessRedirect({
|
||||
successRedirectUrl: event?.data?.successRedirectUrl || "",
|
||||
query,
|
||||
booking: responseData,
|
||||
});
|
||||
},
|
||||
onError: (err, _, ctx) => {
|
||||
// TODO:
|
||||
// const vercelId = ctx?.meta?.headers?.get("x-vercel-id");
|
||||
// if (vercelId) {
|
||||
// setResponseVercelIdHeader(vercelId);
|
||||
// }
|
||||
bookerFormErrorRef && bookerFormErrorRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
},
|
||||
});
|
||||
|
||||
const createInstantBookingMutation = useMutation(createInstantBooking, {
|
||||
onSuccess: (responseData) => {
|
||||
updateQueryParam("bookingId", responseData.bookingId);
|
||||
setExpiryTime(responseData.expires);
|
||||
},
|
||||
onError: (err, _, ctx) => {
|
||||
console.error("Error creating instant booking", err);
|
||||
|
||||
bookerFormErrorRef && bookerFormErrorRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
},
|
||||
});
|
||||
|
||||
const createRecurringBookingMutation = useMutation(createRecurringBooking, {
|
||||
onSuccess: async (responseData) => {
|
||||
const booking = responseData[0] || {};
|
||||
const { uid } = booking;
|
||||
|
||||
if (!uid) {
|
||||
console.error("No uid returned from createRecurringBookingMutation");
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {
|
||||
isSuccessBookingPage: true,
|
||||
allRemainingBookings: true,
|
||||
email: bookingForm.getValues("responses.email"),
|
||||
eventTypeSlug: eventSlug,
|
||||
formerTime:
|
||||
isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined,
|
||||
};
|
||||
|
||||
bookingSuccessRedirect({
|
||||
successRedirectUrl: event?.data?.successRedirectUrl || "",
|
||||
query,
|
||||
booking,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleBookEvent = () => {
|
||||
const values = bookingForm.getValues();
|
||||
if (timeslot) {
|
||||
// Clears form values stored in store, so old values won't stick around.
|
||||
setFormValues({});
|
||||
bookingForm.clearErrors();
|
||||
|
||||
// It shouldn't be possible that this method is fired without having event data,
|
||||
// but since in theory (looking at the types) it is possible, we still handle that case.
|
||||
if (!event?.data) {
|
||||
bookingForm.setError("globalError", { message: t("error_booking_event") });
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensures that duration is an allowed value, if not it defaults to the
|
||||
// default event duration.
|
||||
const validDuration = event.data.isDynamic
|
||||
? duration || event.data.length
|
||||
: duration && event.data.metadata?.multipleDuration?.includes(duration)
|
||||
? duration
|
||||
: event.data.length;
|
||||
|
||||
const bookingInput = {
|
||||
values,
|
||||
duration: validDuration,
|
||||
event: event.data,
|
||||
date: timeslot,
|
||||
timeZone: timezone,
|
||||
language: i18n.language,
|
||||
rescheduleUid: rescheduleUid || undefined,
|
||||
bookingUid: (bookingData && bookingData.uid) || seatedEventData?.bookingUid || undefined,
|
||||
username: username || "",
|
||||
metadata: Object.keys(routerQuery)
|
||||
.filter((key) => key.startsWith("metadata"))
|
||||
.reduce(
|
||||
(metadata, key) => ({
|
||||
...metadata,
|
||||
[key.substring("metadata[".length, key.length - 1)]: searchParams?.get(key),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
hashedLink,
|
||||
};
|
||||
|
||||
if (isInstantMeeting) {
|
||||
createInstantBookingMutation.mutate(mapBookingToMutationInput(bookingInput));
|
||||
} else if (event.data?.recurringEvent?.freq && recurringEventCount && !rescheduleUid) {
|
||||
createRecurringBookingMutation.mutate(
|
||||
mapRecurringBookingToMutationInput(bookingInput, recurringEventCount)
|
||||
);
|
||||
} else {
|
||||
createBookingMutation.mutate(mapBookingToMutationInput(bookingInput));
|
||||
}
|
||||
// Clears form values stored in store, so old values won't stick around.
|
||||
setFormValues({});
|
||||
bookingForm.clearErrors();
|
||||
}
|
||||
};
|
||||
|
||||
const beforeVerifyEmail = () => {
|
||||
bookingForm.clearErrors();
|
||||
|
@ -293,39 +77,18 @@ export const useBookingForm = ({ event, hashedLink }: IUseBookingForm) => {
|
|||
};
|
||||
|
||||
const errors = {
|
||||
hasFormErrors: Boolean(
|
||||
createBookingMutation.isError ||
|
||||
createRecurringBookingMutation.isError ||
|
||||
createInstantBookingMutation.isError ||
|
||||
bookingForm.formState.errors["globalError"]
|
||||
),
|
||||
hasFormErrors: Boolean(bookingForm.formState.errors["globalError"]),
|
||||
formErrors: bookingForm.formState.errors["globalError"],
|
||||
dataErrors:
|
||||
createBookingMutation.error ||
|
||||
createRecurringBookingMutation.error ||
|
||||
createInstantBookingMutation.error,
|
||||
};
|
||||
|
||||
// A redirect is triggered on mutation success, so keep the loading state while it is happening.
|
||||
const loadingStates = {
|
||||
creatingBooking: createBookingMutation.isLoading || createBookingMutation.isSuccess,
|
||||
creatingRecurringBooking:
|
||||
createRecurringBookingMutation.isLoading || createRecurringBookingMutation.isSuccess,
|
||||
creatingInstantBooking: createInstantBookingMutation.isLoading,
|
||||
};
|
||||
|
||||
return {
|
||||
handleBookEvent,
|
||||
expiryTime,
|
||||
bookingForm,
|
||||
bookerFormErrorRef,
|
||||
initialValues,
|
||||
key,
|
||||
errors,
|
||||
loadingStates,
|
||||
hasInstantMeetingTokenExpired: Boolean(hasInstantMeetingTokenExpired),
|
||||
formEmail: email,
|
||||
formName: name,
|
||||
beforeVerifyEmail,
|
||||
formErrors: errors,
|
||||
errors,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,278 @@
|
|||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { useEventReturnType } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
||||
import {
|
||||
createBooking,
|
||||
createRecurringBooking,
|
||||
mapBookingToMutationInput,
|
||||
mapRecurringBookingToMutationInput,
|
||||
createInstantBooking,
|
||||
useTimePreferences,
|
||||
} from "@calcom/features/bookings/lib";
|
||||
import { getFullName } from "@calcom/features/form-builder/utils";
|
||||
import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { showToast } from "@calcom/ui";
|
||||
|
||||
import type { useBookingFormReturnType } from "./useBookingForms";
|
||||
import { useInitialFormValues } from "./useInitialFormValues";
|
||||
|
||||
export interface IUseBookings {
|
||||
event: useEventReturnType;
|
||||
hashedLink?: string | null;
|
||||
bookingForm: useBookingFormReturnType["bookingForm"];
|
||||
}
|
||||
|
||||
export interface IUseBookingLoadingStates {
|
||||
creatingBooking: boolean;
|
||||
creatingRecurringBooking: boolean;
|
||||
creatingInstantBooking: boolean;
|
||||
}
|
||||
|
||||
export interface IUseBookingErrors {
|
||||
hasDataErrors: boolean;
|
||||
dataErrors: unknown;
|
||||
}
|
||||
|
||||
export const useBookings = ({ event, hashedLink, bookingForm }: IUseBookings) => {
|
||||
const router = useRouter();
|
||||
const eventSlug = useBookerStore((state) => state.eventSlug);
|
||||
const setFormValues = useBookerStore((state) => state.setFormValues);
|
||||
const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
|
||||
const bookingData = useBookerStore((state) => state.bookingData);
|
||||
const timeslot = useBookerStore((state) => state.selectedTimeslot);
|
||||
const seatedEventData = useBookerStore((state) => state.seatedEventData);
|
||||
const { t, i18n } = useLocale();
|
||||
const bookingSuccessRedirect = useBookingSuccessRedirect();
|
||||
const bookerFormErrorRef = useRef<HTMLDivElement>(null);
|
||||
const [expiryTime, setExpiryTime] = useState<Date | undefined>();
|
||||
const recurringEventCount = useBookerStore((state) => state.recurringEventCount);
|
||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
||||
const duration = useBookerStore((state) => state.selectedDuration);
|
||||
const { timezone } = useTimePreferences();
|
||||
const username = useBookerStore((state) => state.username);
|
||||
const routerQuery = useRouterQuery();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const isRescheduling = !!rescheduleUid && !!bookingData;
|
||||
|
||||
const { initialValues, key } = useInitialFormValues({
|
||||
eventType: event.data,
|
||||
rescheduleUid,
|
||||
isRescheduling,
|
||||
});
|
||||
|
||||
const bookingId = parseInt(getQueryParam("bookingId") || "0");
|
||||
const hasInstantMeetingTokenExpired = expiryTime && new Date(expiryTime) < new Date();
|
||||
const _instantBooking = trpc.viewer.bookings.getInstantBookingLocation.useQuery(
|
||||
{
|
||||
bookingId: bookingId,
|
||||
},
|
||||
{
|
||||
enabled: !!bookingId && !hasInstantMeetingTokenExpired,
|
||||
refetchInterval: 2000,
|
||||
onSuccess: (data) => {
|
||||
try {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
|
||||
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
|
||||
data.booking?.metadata || {}
|
||||
)?.videoCallUrl;
|
||||
|
||||
if (locationVideoCallUrl) {
|
||||
router.push(locationVideoCallUrl);
|
||||
} else {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const createBookingMutation = useMutation(createBooking, {
|
||||
onSuccess: (responseData) => {
|
||||
const { uid, paymentUid } = responseData;
|
||||
const fullName = getFullName(bookingForm.getValues("responses.name"));
|
||||
if (paymentUid) {
|
||||
router.push(
|
||||
createPaymentLink({
|
||||
paymentUid,
|
||||
date: timeslot,
|
||||
name: fullName,
|
||||
email: bookingForm.getValues("responses.email"),
|
||||
absolute: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!uid) {
|
||||
console.error("No uid returned from createBookingMutation");
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {
|
||||
isSuccessBookingPage: true,
|
||||
email: bookingForm.getValues("responses.email"),
|
||||
eventTypeSlug: eventSlug,
|
||||
seatReferenceUid: "seatReferenceUid" in responseData ? responseData.seatReferenceUid : null,
|
||||
formerTime:
|
||||
isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined,
|
||||
};
|
||||
|
||||
bookingSuccessRedirect({
|
||||
successRedirectUrl: event?.data?.successRedirectUrl || "",
|
||||
query,
|
||||
booking: responseData,
|
||||
});
|
||||
},
|
||||
onError: (err, _, ctx) => {
|
||||
// TODO:
|
||||
// const vercelId = ctx?.meta?.headers?.get("x-vercel-id");
|
||||
// if (vercelId) {
|
||||
// setResponseVercelIdHeader(vercelId);
|
||||
// }
|
||||
bookerFormErrorRef && bookerFormErrorRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
},
|
||||
});
|
||||
|
||||
const createInstantBookingMutation = useMutation(createInstantBooking, {
|
||||
onSuccess: (responseData) => {
|
||||
updateQueryParam("bookingId", responseData.bookingId);
|
||||
setExpiryTime(responseData.expires);
|
||||
},
|
||||
onError: (err, _, ctx) => {
|
||||
console.error("Error creating instant booking", err);
|
||||
|
||||
bookerFormErrorRef && bookerFormErrorRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
},
|
||||
});
|
||||
|
||||
const createRecurringBookingMutation = useMutation(createRecurringBooking, {
|
||||
onSuccess: async (responseData) => {
|
||||
const booking = responseData[0] || {};
|
||||
const { uid } = booking;
|
||||
|
||||
if (!uid) {
|
||||
console.error("No uid returned from createRecurringBookingMutation");
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {
|
||||
isSuccessBookingPage: true,
|
||||
allRemainingBookings: true,
|
||||
email: bookingForm.getValues("responses.email"),
|
||||
eventTypeSlug: eventSlug,
|
||||
formerTime:
|
||||
isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined,
|
||||
};
|
||||
|
||||
bookingSuccessRedirect({
|
||||
successRedirectUrl: event?.data?.successRedirectUrl || "",
|
||||
query,
|
||||
booking,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleBookEvent = () => {
|
||||
const values = bookingForm.getValues();
|
||||
if (timeslot) {
|
||||
// Clears form values stored in store, so old values won't stick around.
|
||||
setFormValues({});
|
||||
bookingForm.clearErrors();
|
||||
|
||||
// It shouldn't be possible that this method is fired without having event data,
|
||||
// but since in theory (looking at the types) it is possible, we still handle that case.
|
||||
if (!event?.data) {
|
||||
bookingForm.setError("globalError", { message: t("error_booking_event") });
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensures that duration is an allowed value, if not it defaults to the
|
||||
// default event duration.
|
||||
const validDuration = event.data.isDynamic
|
||||
? duration || event.data.length
|
||||
: duration && event.data.metadata?.multipleDuration?.includes(duration)
|
||||
? duration
|
||||
: event.data.length;
|
||||
|
||||
const bookingInput = {
|
||||
values,
|
||||
duration: validDuration,
|
||||
event: event.data,
|
||||
date: timeslot,
|
||||
timeZone: timezone,
|
||||
language: i18n.language,
|
||||
rescheduleUid: rescheduleUid || undefined,
|
||||
bookingUid: (bookingData && bookingData.uid) || seatedEventData?.bookingUid || undefined,
|
||||
username: username || "",
|
||||
metadata: Object.keys(routerQuery)
|
||||
.filter((key) => key.startsWith("metadata"))
|
||||
.reduce(
|
||||
(metadata, key) => ({
|
||||
...metadata,
|
||||
[key.substring("metadata[".length, key.length - 1)]: searchParams?.get(key),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
hashedLink,
|
||||
};
|
||||
|
||||
if (isInstantMeeting) {
|
||||
createInstantBookingMutation.mutate(mapBookingToMutationInput(bookingInput));
|
||||
} else if (event.data?.recurringEvent?.freq && recurringEventCount && !rescheduleUid) {
|
||||
createRecurringBookingMutation.mutate(
|
||||
mapRecurringBookingToMutationInput(bookingInput, recurringEventCount)
|
||||
);
|
||||
} else {
|
||||
createBookingMutation.mutate(mapBookingToMutationInput(bookingInput));
|
||||
}
|
||||
// Clears form values stored in store, so old values won't stick around.
|
||||
setFormValues({});
|
||||
bookingForm.clearErrors();
|
||||
}
|
||||
};
|
||||
|
||||
const errors = {
|
||||
hasDataErrors: Boolean(
|
||||
createBookingMutation.isError ||
|
||||
createRecurringBookingMutation.isError ||
|
||||
createInstantBookingMutation.isError
|
||||
),
|
||||
dataErrors:
|
||||
createBookingMutation.error ||
|
||||
createRecurringBookingMutation.error ||
|
||||
createInstantBookingMutation.error,
|
||||
};
|
||||
|
||||
// A redirect is triggered on mutation success, so keep the loading state while it is happening.
|
||||
const loadingStates = {
|
||||
creatingBooking: createBookingMutation.isLoading || createBookingMutation.isSuccess,
|
||||
creatingRecurringBooking:
|
||||
createRecurringBookingMutation.isLoading || createRecurringBookingMutation.isSuccess,
|
||||
creatingInstantBooking: createInstantBookingMutation.isLoading,
|
||||
};
|
||||
|
||||
return {
|
||||
handleBookEvent,
|
||||
expiryTime,
|
||||
bookingForm,
|
||||
bookerFormErrorRef,
|
||||
initialValues,
|
||||
key,
|
||||
errors,
|
||||
loadingStates,
|
||||
hasInstantMeetingTokenExpired: Boolean(hasInstantMeetingTokenExpired),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,114 @@
|
|||
import { useSession } from "next-auth/react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import { useBookerStore } from "../../store";
|
||||
import { useOverlayCalendarStore } from "../OverlayCalendar/store";
|
||||
import { useLocalSet } from "./useLocalSet";
|
||||
|
||||
export type useOverlayCalendarReturnType = ReturnType<typeof useOverlayCalendar>;
|
||||
|
||||
export const useOverlayCalendar = () => {
|
||||
const utils = trpc.useContext();
|
||||
const { set, toggleValue, hasItem, clearSet } = useLocalSet<{
|
||||
credentialId: number;
|
||||
externalId: string;
|
||||
}>("toggledConnectedCalendars", []);
|
||||
const { data: session } = useSession();
|
||||
const searchParams = useSearchParams();
|
||||
const [initalised, setInitalised] = useState(false);
|
||||
const [continueWithProvider, setContinueWithProvider] = useOverlayCalendarStore(
|
||||
(state) => [state.continueWithProviderModal, state.setContinueWithProviderModal],
|
||||
shallow
|
||||
);
|
||||
const [calendarSettingsOverlay, setCalendarSettingsOverlay] = useOverlayCalendarStore(
|
||||
(state) => [state.calendarSettingsOverlayModal, state.setCalendarSettingsOverlayModal],
|
||||
shallow
|
||||
);
|
||||
const setOverlayBusyDates = useOverlayCalendarStore((state) => state.setOverlayBusyDates);
|
||||
const switchEnabled =
|
||||
searchParams?.get("overlayCalendar") === "true" ||
|
||||
localStorage.getItem("overlayCalendarSwitchDefault") === "true";
|
||||
|
||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
||||
const { timezone } = useTimePreferences();
|
||||
const { data: overlayBusyDates } = trpc.viewer.availability.calendarOverlay.useQuery(
|
||||
{
|
||||
loggedInUsersTz: timezone || "Europe/London",
|
||||
dateFrom: selectedDate,
|
||||
dateTo: selectedDate,
|
||||
calendarsToLoad: Array.from(set).map((item) => ({
|
||||
credentialId: item.credentialId,
|
||||
externalId: item.externalId,
|
||||
})),
|
||||
},
|
||||
{
|
||||
enabled: !!session && set.size > 0 && switchEnabled,
|
||||
onError: () => {
|
||||
clearSet();
|
||||
},
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
if (overlayBusyDates) {
|
||||
const nowDate = dayjs();
|
||||
const usersTimezoneDate = nowDate.tz(timezone);
|
||||
|
||||
const offset = (usersTimezoneDate.utcOffset() - nowDate.utcOffset()) / 60;
|
||||
|
||||
const offsettedArray = overlayBusyDates.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
start: dayjs(item.start).add(offset, "hours").toDate(),
|
||||
end: dayjs(item.end).add(offset, "hours").toDate(),
|
||||
};
|
||||
});
|
||||
setOverlayBusyDates(offsettedArray);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [overlayBusyDates]);
|
||||
|
||||
const { data, isLoading } = trpc.viewer.connectedCalendars.useQuery(undefined, {
|
||||
enabled: !!calendarSettingsOverlay || Boolean(searchParams?.get("overlayCalendar")),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.connectedCalendars && set.size === 0 && !initalised) {
|
||||
data?.connectedCalendars.forEach((item) => {
|
||||
item.calendars?.forEach((cal) => {
|
||||
const id = { credentialId: item.credentialId, externalId: cal.externalId };
|
||||
if (cal.primary) {
|
||||
toggleValue(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
setInitalised(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, hasItem, set, initalised]);
|
||||
|
||||
const handleToggleConnectedCalendar = (externalCalendarId: string, credentialId: number) => {
|
||||
toggleValue({
|
||||
credentialId: credentialId,
|
||||
externalId: externalCalendarId,
|
||||
});
|
||||
setOverlayBusyDates([]);
|
||||
utils.viewer.availability.calendarOverlay.reset();
|
||||
};
|
||||
|
||||
return {
|
||||
isOverlayCalendarEnabled: switchEnabled,
|
||||
connectedCalendars: data?.connectedCalendars || [],
|
||||
loadingConnectedCalendar: isLoading,
|
||||
isOpenOverlayContinueModal: continueWithProvider,
|
||||
isOpenOverlaySettingsModal: calendarSettingsOverlay,
|
||||
handleCloseContinueModal: (val: boolean) => setContinueWithProvider(val),
|
||||
handleCloseSettingsModal: (val: boolean) => setCalendarSettingsOverlay(val),
|
||||
handleToggleConnectedCalendar,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user