* refactor: apply biome formatting to apps/web (batch 1) Formats apps/web non-modules directories: - apps/web/app - apps/web/lib - apps/web/pages - apps/web/styles - apps/web/server - apps/web/test - Root-level .ts and .mjs files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/modules (batch 2) Formats smaller apps/web/modules directories: - onboarding, data-table, users, apps, auth - integration-attribute-sync, shell, availability - feature-flags, team, webhooks, getting-started - feature-opt-in, troubleshooter, schedules, notifications - signup-view.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/modules (batch 3) Formats medium apps/web/modules directories: - bookings - event-types - insights - embed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/modules/ee (batch 4) Formats apps/web/modules/ee directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web (batch 5) Formats remaining apps/web directories: - apps/web/modules/settings - apps/web/modules/booking-audit - apps/web/playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to apps/web/components (batch 6) Formats remaining apps/web/components files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to new apps/web files from main Formats newly added/modified files from main merge: - WorkflowStepContainer.tsx (resolved merge conflicts) - Newly moved files (blocklist, form-builder, schedules, etc.) - Other files modified in main Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: apply biome formatting to new apps/web files from main Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
584 lines
19 KiB
TypeScript
584 lines
19 KiB
TypeScript
"use client";
|
|
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { useRouter } from "next/navigation";
|
|
import { useRef, useState, useEffect } from "react";
|
|
import { shallow } from "zustand/shallow";
|
|
|
|
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
|
|
import { useHandleBookEvent } from "@calcom/atoms/hooks/bookings/useHandleBookEvent";
|
|
import dayjs from "@calcom/dayjs";
|
|
import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
|
import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
|
|
import { storeDecoyBooking } from "@calcom/features/bookings/lib/client/decoyBookingStore";
|
|
import { createBooking } from "@calcom/features/bookings/lib/create-booking";
|
|
import { createInstantBooking } from "@calcom/features/bookings/lib/create-instant-booking";
|
|
import { createRecurringBooking } from "@calcom/features/bookings/lib/create-recurring-booking";
|
|
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
|
import { getFullName } from "@calcom/features/form-builder/utils";
|
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { localStorage } from "@calcom/lib/webstorage";
|
|
import { BookingStatus } from "@calcom/prisma/enums";
|
|
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
import { trpc } from "@calcom/trpc/react";
|
|
import { showToast } from "@calcom/ui/components/toast";
|
|
|
|
import { useBookingSuccessRedirect } from "@calcom/features/bookings/lib/bookingSuccessRedirect";
|
|
import type { UseBookingFormReturnType } from "@calcom/features/bookings/Booker/hooks/useBookingForm";
|
|
|
|
export interface IUseBookings {
|
|
event: {
|
|
data?:
|
|
| (Pick<
|
|
BookerEvent,
|
|
| "id"
|
|
| "slug"
|
|
| "subsetOfHosts"
|
|
| "requiresConfirmation"
|
|
| "isDynamic"
|
|
| "metadata"
|
|
| "forwardParamsSuccessRedirect"
|
|
| "successRedirectUrl"
|
|
| "length"
|
|
| "recurringEvent"
|
|
| "schedulingType"
|
|
> & {
|
|
subsetOfUsers: Pick<
|
|
BookerEvent["subsetOfUsers"][number],
|
|
"name" | "username" | "avatarUrl" | "weekStart" | "profile" | "bookerUrl"
|
|
>[];
|
|
})
|
|
| null;
|
|
};
|
|
hashedLink?: string | null;
|
|
bookingForm: UseBookingFormReturnType["bookingForm"];
|
|
metadata: Record<string, string>;
|
|
teamMemberEmail?: string | null;
|
|
isBookingDryRun?: boolean;
|
|
}
|
|
|
|
const getBaseBookingEventPayload = (booking: {
|
|
title?: string;
|
|
startTime: string;
|
|
endTime: string;
|
|
eventTypeId?: number | null;
|
|
status?: BookingStatus;
|
|
paymentRequired: boolean;
|
|
isRecurring: boolean;
|
|
videoCallUrl?: string;
|
|
}) => {
|
|
return {
|
|
title: booking.title,
|
|
startTime: booking.startTime,
|
|
endTime: booking.endTime,
|
|
eventTypeId: booking.eventTypeId,
|
|
status: booking.status,
|
|
paymentRequired: booking.paymentRequired,
|
|
isRecurring: booking.isRecurring,
|
|
videoCallUrl: booking.videoCallUrl,
|
|
};
|
|
};
|
|
|
|
const getBookingSuccessfulEventPayload = (booking: {
|
|
title?: string;
|
|
startTime: string;
|
|
endTime: string;
|
|
eventTypeId?: number | null;
|
|
status?: BookingStatus;
|
|
paymentRequired: boolean;
|
|
uid?: string;
|
|
isRecurring: boolean;
|
|
videoCallUrl?: string;
|
|
}) => {
|
|
return {
|
|
uid: booking.uid,
|
|
...getBaseBookingEventPayload(booking),
|
|
};
|
|
};
|
|
|
|
const getRescheduleBookingSuccessfulEventPayload = getBookingSuccessfulEventPayload;
|
|
|
|
export const getDryRunBookingSuccessfulEventPayload = getBaseBookingEventPayload;
|
|
|
|
export const getDryRunRescheduleBookingSuccessfulEventPayload = getDryRunBookingSuccessfulEventPayload;
|
|
export interface IUseBookingLoadingStates {
|
|
creatingBooking: boolean;
|
|
creatingRecurringBooking: boolean;
|
|
creatingInstantBooking: boolean;
|
|
}
|
|
|
|
export interface IUseBookingErrors {
|
|
hasDataErrors: boolean;
|
|
dataErrors: unknown;
|
|
}
|
|
export type UseBookingsReturnType = ReturnType<typeof useBookings>;
|
|
|
|
const STORAGE_KEY = "instantBookingData";
|
|
const COOLDOWN_STORAGE_KEY = "instantBookingCooldownByEvent";
|
|
const COOLDOWN_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
|
|
|
|
type InstantBookingCooldownMap = Record<string, number>;
|
|
|
|
const readInstantCooldownMap = (): InstantBookingCooldownMap => {
|
|
try {
|
|
const raw = localStorage.getItem(COOLDOWN_STORAGE_KEY);
|
|
return raw ? (JSON.parse(raw) as InstantBookingCooldownMap) : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
const writeInstantCooldownMap = (map: InstantBookingCooldownMap) => {
|
|
try {
|
|
localStorage.setItem(COOLDOWN_STORAGE_KEY, JSON.stringify(map));
|
|
} catch {
|
|
// don't do anything
|
|
}
|
|
};
|
|
|
|
const getInstantCooldownRemainingMs = (eventTypeId?: number | null): number => {
|
|
if (!eventTypeId) return 0;
|
|
const map = readInstantCooldownMap();
|
|
const lastTs = map[String(eventTypeId)];
|
|
if (!lastTs) return 0;
|
|
const remaining = lastTs + COOLDOWN_WINDOW_MS - Date.now();
|
|
return remaining > 0 ? remaining : 0;
|
|
};
|
|
|
|
const setInstantCooldownNow = (eventTypeId?: number | null) => {
|
|
if (!eventTypeId) return;
|
|
const map = readInstantCooldownMap();
|
|
map[String(eventTypeId)] = Date.now();
|
|
writeInstantCooldownMap(map);
|
|
};
|
|
|
|
const storeInLocalStorage = ({
|
|
eventTypeId,
|
|
expiryTime,
|
|
bookingUid,
|
|
}: {
|
|
eventTypeId: number;
|
|
expiryTime: Date;
|
|
bookingUid: string;
|
|
}) => {
|
|
const value = JSON.stringify({ eventTypeId, expiryTime, bookingUid });
|
|
localStorage.setItem(STORAGE_KEY, value);
|
|
};
|
|
|
|
export const useBookings = ({ event, hashedLink, bookingForm, metadata, isBookingDryRun }: IUseBookings) => {
|
|
const router = useRouter();
|
|
const eventSlug = useBookerStoreContext((state) => state.eventSlug);
|
|
const eventTypeId = useBookerStoreContext((state) => state.eventId);
|
|
const isInstantMeeting = useBookerStoreContext((state) => state.isInstantMeeting);
|
|
|
|
const [rescheduleUid, setRescheduleUid] = useBookerStoreContext(
|
|
(state) => [state.rescheduleUid, state.setRescheduleUid],
|
|
shallow
|
|
);
|
|
const rescheduledBy = useBookerStoreContext((state) => state.rescheduledBy);
|
|
const [bookingData, setBookingData] = useBookerStoreContext(
|
|
(state) => [state.bookingData, state.setBookingData],
|
|
shallow
|
|
);
|
|
const timeslot = useBookerStoreContext((state) => state.selectedTimeslot);
|
|
const { t } = useLocale();
|
|
const bookingSuccessRedirect = useBookingSuccessRedirect();
|
|
const bookerFormErrorRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [instantMeetingTokenExpiryTime, setExpiryTime] = useState<Date | undefined>();
|
|
const [instantVideoMeetingUrl, setInstantVideoMeetingUrl] = useState<string | undefined>();
|
|
const duration = useBookerStoreContext((state) => state.selectedDuration);
|
|
|
|
const isRescheduling = !!rescheduleUid && !!bookingData;
|
|
|
|
const bookingUid = getQueryParam("bookingUid") ?? "";
|
|
|
|
useEffect(() => {
|
|
if (!isInstantMeeting) return;
|
|
|
|
const storedInfo = localStorage.getItem(STORAGE_KEY);
|
|
|
|
if (storedInfo) {
|
|
const parsedInfo = JSON.parse(storedInfo);
|
|
|
|
const parsedInstantBookingInfo =
|
|
parsedInfo.eventTypeId === eventTypeId &&
|
|
isInstantMeeting &&
|
|
new Date(parsedInfo.expiryTime) > new Date()
|
|
? parsedInfo
|
|
: null;
|
|
|
|
if (parsedInstantBookingInfo) {
|
|
setExpiryTime(parsedInstantBookingInfo.expiryTime);
|
|
updateQueryParam("bookingUid", parsedInstantBookingInfo.bookingUid);
|
|
}
|
|
}
|
|
}, [eventTypeId, isInstantMeeting]);
|
|
|
|
const instantConnectCooldownMs = getInstantCooldownRemainingMs(eventTypeId);
|
|
|
|
const _instantBooking = trpc.viewer.bookings.getInstantBookingLocation.useQuery(
|
|
{
|
|
bookingUid: bookingUid,
|
|
},
|
|
{
|
|
enabled: !!bookingUid && isInstantMeeting,
|
|
refetchInterval: 2000,
|
|
refetchIntervalInBackground: true,
|
|
}
|
|
);
|
|
useEffect(
|
|
function refactorMeWithoutEffect() {
|
|
const data = _instantBooking.data;
|
|
|
|
if (!data || !data.booking || !isInstantMeeting) return;
|
|
try {
|
|
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
|
|
data.booking?.metadata || {}
|
|
)?.videoCallUrl;
|
|
|
|
if (locationVideoCallUrl) {
|
|
setInstantVideoMeetingUrl(locationVideoCallUrl);
|
|
} else {
|
|
showToast(t("something_went_wrong_on_our_end"), "error");
|
|
}
|
|
} catch {
|
|
showToast(t("something_went_wrong_on_our_end"), "error");
|
|
}
|
|
},
|
|
[_instantBooking.data, isInstantMeeting]
|
|
);
|
|
|
|
const createBookingMutation = useMutation({
|
|
mutationFn: createBooking,
|
|
onSuccess: (booking) => {
|
|
if (booking.isDryRun) {
|
|
if (isRescheduling) {
|
|
sdkActionManager?.fire(
|
|
"dryRunRescheduleBookingSuccessfulV2",
|
|
getDryRunRescheduleBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: false,
|
|
})
|
|
);
|
|
} else {
|
|
sdkActionManager?.fire(
|
|
"dryRunBookingSuccessfulV2",
|
|
getDryRunBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: false,
|
|
})
|
|
);
|
|
}
|
|
|
|
router.push("/booking/dry-run-successful");
|
|
return;
|
|
}
|
|
|
|
if ("isShortCircuitedBooking" in booking && booking.isShortCircuitedBooking) {
|
|
if (!booking.uid) {
|
|
console.error("Decoy booking missing uid");
|
|
return;
|
|
}
|
|
|
|
const bookingData = {
|
|
uid: booking.uid,
|
|
title: booking.title ?? null,
|
|
startTime: booking.startTime,
|
|
endTime: booking.endTime,
|
|
booker: booking.attendees?.[0] ?? null,
|
|
host: booking.user ?? null,
|
|
location: booking.location ?? null,
|
|
};
|
|
|
|
storeDecoyBooking(bookingData);
|
|
router.push(`/booking-successful/${booking.uid}`);
|
|
return;
|
|
}
|
|
|
|
const { uid, paymentUid } = booking;
|
|
const fullName = getFullName(bookingForm.getValues("responses.name"));
|
|
|
|
const users = event.data?.subsetOfHosts?.length
|
|
? event.data?.subsetOfHosts.map((host) => host.user)
|
|
: event.data?.subsetOfUsers;
|
|
|
|
const validDuration = event.data?.isDynamic
|
|
? duration || event.data?.length
|
|
: duration && event.data?.metadata?.multipleDuration?.includes(duration)
|
|
? duration
|
|
: event.data?.length;
|
|
|
|
if (isRescheduling) {
|
|
sdkActionManager?.fire("rescheduleBookingSuccessful", {
|
|
booking: booking,
|
|
eventType: event.data,
|
|
date: booking?.startTime?.toString() || "",
|
|
duration: validDuration,
|
|
organizer: {
|
|
name: users?.[0]?.name || "Nameless",
|
|
email: booking?.userPrimaryEmail || booking.user?.email || "Email-less",
|
|
timeZone: booking.user?.timeZone || "Europe/London",
|
|
},
|
|
confirmed: !(booking.status === BookingStatus.PENDING && event.data?.requiresConfirmation),
|
|
});
|
|
sdkActionManager?.fire(
|
|
"rescheduleBookingSuccessfulV2",
|
|
getRescheduleBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: false,
|
|
})
|
|
);
|
|
} else {
|
|
sdkActionManager?.fire("bookingSuccessful", {
|
|
booking: booking,
|
|
eventType: event.data,
|
|
date: booking?.startTime?.toString() || "",
|
|
duration: validDuration,
|
|
organizer: {
|
|
name: users?.[0]?.name || "Nameless",
|
|
email: booking?.userPrimaryEmail || booking.user?.email || "Email-less",
|
|
timeZone: booking.user?.timeZone || "Europe/London",
|
|
},
|
|
confirmed: !(booking.status === BookingStatus.PENDING && event.data?.requiresConfirmation),
|
|
});
|
|
|
|
sdkActionManager?.fire(
|
|
"bookingSuccessfulV2",
|
|
getBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: false,
|
|
})
|
|
);
|
|
}
|
|
|
|
if (paymentUid) {
|
|
router.push(
|
|
createPaymentLink({
|
|
paymentUid,
|
|
date: timeslot,
|
|
name: fullName,
|
|
email: bookingForm.getValues("responses.email"),
|
|
absolute: false,
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!uid) {
|
|
console.error("No uid returned from createBookingMutation");
|
|
return;
|
|
}
|
|
|
|
const query = {
|
|
isSuccessBookingPage: true,
|
|
email: bookingForm.getValues("responses.email"),
|
|
eventTypeSlug: eventSlug,
|
|
seatReferenceUid: "seatReferenceUid" in booking ? (booking.seatReferenceUid as string) : null,
|
|
formerTime:
|
|
isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined,
|
|
rescheduledBy, // ensure further reschedules performed on the success page are recorded correctly
|
|
};
|
|
|
|
bookingSuccessRedirect({
|
|
successRedirectUrl: event?.data?.successRedirectUrl || "",
|
|
query,
|
|
booking: booking,
|
|
forwardParamsSuccessRedirect:
|
|
event?.data?.forwardParamsSuccessRedirect === undefined
|
|
? true
|
|
: event?.data?.forwardParamsSuccessRedirect,
|
|
});
|
|
},
|
|
onError: (err) => {
|
|
if (bookerFormErrorRef?.current) {
|
|
bookerFormErrorRef.current.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
|
|
const error = err as Error & {
|
|
data: { rescheduleUid: string; startTime: string; attendees: string[]; seatUid?: string };
|
|
traceId?: string;
|
|
};
|
|
|
|
if (error.message === ErrorCode.BookerLimitExceededReschedule && error.data?.rescheduleUid) {
|
|
setRescheduleUid(error.data?.seatUid ?? error.data?.rescheduleUid);
|
|
setBookingData({
|
|
uid: error.data?.rescheduleUid,
|
|
startTime: error.data?.startTime,
|
|
attendees: error.data?.attendees,
|
|
} as unknown as GetBookingType);
|
|
}
|
|
},
|
|
});
|
|
|
|
const createInstantBookingMutation = useMutation({
|
|
mutationFn: createInstantBooking,
|
|
onSuccess: (responseData) => {
|
|
if (eventTypeId) {
|
|
storeInLocalStorage({
|
|
eventTypeId,
|
|
expiryTime: responseData.expires,
|
|
bookingUid: responseData.bookingUid,
|
|
});
|
|
setInstantCooldownNow(eventTypeId);
|
|
}
|
|
|
|
updateQueryParam("bookingUid", responseData.bookingUid);
|
|
setExpiryTime(responseData.expires);
|
|
},
|
|
onError: (err) => {
|
|
console.error("Error creating instant booking", err);
|
|
if (bookerFormErrorRef?.current) {
|
|
bookerFormErrorRef.current.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
},
|
|
});
|
|
|
|
const createRecurringBookingMutation = useMutation({
|
|
mutationFn: createRecurringBooking,
|
|
onSuccess: async (bookings) => {
|
|
const booking = bookings[0] || {};
|
|
|
|
if (booking.isDryRun) {
|
|
if (isRescheduling) {
|
|
sdkActionManager?.fire("dryRunRescheduleBookingSuccessfulV2", {
|
|
...getDryRunRescheduleBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: true,
|
|
}),
|
|
allBookings: bookings.map((booking) => ({
|
|
startTime: booking.startTime,
|
|
endTime: booking.endTime,
|
|
})),
|
|
});
|
|
} else {
|
|
sdkActionManager?.fire("dryRunBookingSuccessfulV2", {
|
|
...getDryRunBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: true,
|
|
}),
|
|
allBookings: bookings.map((booking) => ({
|
|
startTime: booking.startTime,
|
|
endTime: booking.endTime,
|
|
})),
|
|
});
|
|
}
|
|
|
|
router.push("/booking/dry-run-successful");
|
|
return;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
if (isRescheduling) {
|
|
// NOTE: It is recommended to define the event payload in the argument itself to provide a better type safety.
|
|
sdkActionManager?.fire("rescheduleBookingSuccessfulV2", {
|
|
...getRescheduleBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: true,
|
|
}),
|
|
allBookings: bookings.map((booking) => ({
|
|
startTime: booking.startTime,
|
|
endTime: booking.endTime,
|
|
})),
|
|
});
|
|
} else {
|
|
sdkActionManager?.fire("bookingSuccessfulV2", {
|
|
...getBookingSuccessfulEventPayload({
|
|
...booking,
|
|
isRecurring: true,
|
|
}),
|
|
allBookings: bookings.map((booking) => ({
|
|
startTime: booking.startTime,
|
|
endTime: booking.endTime,
|
|
})),
|
|
});
|
|
}
|
|
|
|
bookingSuccessRedirect({
|
|
successRedirectUrl: event?.data?.successRedirectUrl || "",
|
|
query,
|
|
booking,
|
|
forwardParamsSuccessRedirect:
|
|
event?.data?.forwardParamsSuccessRedirect === undefined
|
|
? true
|
|
: event?.data?.forwardParamsSuccessRedirect,
|
|
});
|
|
},
|
|
onError: (err, _, ctx) => {
|
|
console.error("Error creating recurring booking", err);
|
|
// eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- It is only called when user takes an action in embed
|
|
bookerFormErrorRef && bookerFormErrorRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
},
|
|
});
|
|
|
|
const handleBookEvent = useHandleBookEvent({
|
|
event,
|
|
bookingForm,
|
|
hashedLink,
|
|
metadata,
|
|
handleInstantBooking: (variables: Parameters<typeof createInstantBookingMutation.mutate>[0]) => {
|
|
const remaining = getInstantCooldownRemainingMs(eventTypeId);
|
|
if (remaining > 0) {
|
|
showToast(
|
|
t("please_try_again_later_or_book_another_slot", { remaining: Math.ceil(remaining / 60000) }),
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
createInstantBookingMutation.mutate(variables);
|
|
},
|
|
handleRecBooking: createRecurringBookingMutation.mutate,
|
|
handleBooking: createBookingMutation.mutate,
|
|
isBookingDryRun,
|
|
});
|
|
|
|
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.isPending || createBookingMutation.isSuccess,
|
|
creatingRecurringBooking:
|
|
createRecurringBookingMutation.isPending || createRecurringBookingMutation.isSuccess,
|
|
creatingInstantBooking: createInstantBookingMutation.isPending,
|
|
};
|
|
|
|
return {
|
|
handleBookEvent,
|
|
expiryTime: instantMeetingTokenExpiryTime,
|
|
bookingForm,
|
|
bookerFormErrorRef,
|
|
errors,
|
|
loadingStates,
|
|
instantVideoMeetingUrl,
|
|
instantConnectCooldownMs,
|
|
};
|
|
};
|