fix: skip confirm step followup (#19076)
* Fix: Resolved double-click issue on timeSlot in Booker Atom * fix: show toast on booking error * fix: type error * Prevent the automatic change of bookerState when all fields are filled. * fix: when a user clicks on a new timeslot, any previously displayed "Confirm" button should be hidden * Update useSkipConfirmStep.ts * fix: remove 2 states for slots * Update useSlotsForDate.ts * fix types * use common types * fix: show VerifyCodeDialog and RedirectToInstantMeetingModal when skiping confirm step * fix for instant bookings --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
co-authored by
Udit Takkar
parent
07accf700e
commit
829bb63eb9
@@ -161,7 +161,12 @@ const BookerComponent = ({
|
||||
}
|
||||
};
|
||||
|
||||
const skipConfirmStep = useSkipConfirmStep(bookingForm, event?.data?.bookingFields);
|
||||
const skipConfirmStep = useSkipConfirmStep(
|
||||
bookingForm,
|
||||
bookerState,
|
||||
isInstantMeeting,
|
||||
event?.data?.bookingFields
|
||||
);
|
||||
|
||||
// Cloudflare Turnstile Captcha
|
||||
const shouldRenderCaptcha = !!(
|
||||
@@ -175,7 +180,8 @@ const BookerComponent = ({
|
||||
useEffect(() => {
|
||||
if (event.isPending) return setBookerState("loading");
|
||||
if (!selectedDate) return setBookerState("selecting_date");
|
||||
if (!selectedTimeslot || skipConfirmStep) return setBookerState("selecting_time");
|
||||
if (!selectedTimeslot) return setBookerState("selecting_time");
|
||||
if (selectedTimeslot && skipConfirmStep && !isInstantMeeting) return setBookerState("selecting_time");
|
||||
return setBookerState("booking");
|
||||
}, [event, selectedDate, selectedTimeslot, setBookerState, skipConfirmStep]);
|
||||
|
||||
@@ -212,22 +218,6 @@ const BookerComponent = ({
|
||||
isVerificationCodeSending={isVerificationCodeSending}
|
||||
isPlatform={isPlatform}>
|
||||
<>
|
||||
{verifyCode && formEmail ? (
|
||||
<VerifyCodeDialog
|
||||
isOpenDialog={isEmailVerificationModalVisible}
|
||||
setIsOpenDialog={setEmailVerificationModalVisible}
|
||||
email={formEmail}
|
||||
isUserSessionRequiredToVerify={false}
|
||||
verifyCodeWithSessionNotRequired={verifyCode.verifyCodeWithSessionNotRequired}
|
||||
verifyCodeWithSessionRequired={verifyCode.verifyCodeWithSessionRequired}
|
||||
error={verifyCode.error}
|
||||
resetErrors={verifyCode.resetErrors}
|
||||
isPending={verifyCode.isPending}
|
||||
setIsPending={verifyCode.setIsPending}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{!isPlatform && (
|
||||
<RedirectToInstantMeetingModal
|
||||
expiryTime={expiryTime}
|
||||
@@ -253,26 +243,17 @@ const BookerComponent = ({
|
||||
event,
|
||||
expiryTime,
|
||||
extraOptions,
|
||||
formEmail,
|
||||
formErrors,
|
||||
handleBookEvent,
|
||||
handleVerifyEmail,
|
||||
isEmailVerificationModalVisible,
|
||||
key,
|
||||
loadingStates,
|
||||
onGoBackInstantMeeting,
|
||||
renderConfirmNotVerifyEmailButtonCond,
|
||||
rescheduleUid,
|
||||
seatedEventData,
|
||||
setEmailVerificationModalVisible,
|
||||
setSeatedEventData,
|
||||
setSelectedTimeslot,
|
||||
verifyCode?.error,
|
||||
verifyCode?.isPending,
|
||||
verifyCode?.resetErrors,
|
||||
verifyCode?.setIsPending,
|
||||
verifyCode?.verifyCodeWithSessionNotRequired,
|
||||
verifyCode?.verifyCodeWithSessionRequired,
|
||||
isPlatform,
|
||||
shouldRenderCaptcha,
|
||||
isVerificationCodeSending,
|
||||
@@ -532,6 +513,25 @@ const BookerComponent = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<>
|
||||
{verifyCode && formEmail ? (
|
||||
<VerifyCodeDialog
|
||||
isOpenDialog={isEmailVerificationModalVisible}
|
||||
setIsOpenDialog={setEmailVerificationModalVisible}
|
||||
email={formEmail}
|
||||
isUserSessionRequiredToVerify={false}
|
||||
verifyCodeWithSessionNotRequired={verifyCode.verifyCodeWithSessionNotRequired}
|
||||
verifyCodeWithSessionRequired={verifyCode.verifyCodeWithSessionRequired}
|
||||
error={verifyCode.error}
|
||||
resetErrors={verifyCode.resetErrors}
|
||||
isPending={verifyCode.isPending}
|
||||
setIsPending={verifyCode.setIsPending}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
|
||||
<BookFormAsModal
|
||||
onCancel={() => setSelectedTimeslot(null)}
|
||||
visible={bookerState === "booking" && shouldShowFormInDialog}>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useRef } from "react";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { AvailableTimes, AvailableTimesSkeleton } from "@calcom/features/bookings";
|
||||
import type { IUseBookingLoadingStates } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useNonEmptyScheduleDays } from "@calcom/features/schedules";
|
||||
import type { Slot } from "@calcom/features/schedules";
|
||||
import { useSlotsForAvailableDates } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
@@ -12,6 +13,7 @@ import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
import { AvailableTimesHeader } from "../../components/AvailableTimesHeader";
|
||||
import { useBookerStore } from "../store";
|
||||
import type { useScheduleForEventReturnType } from "../utils/event";
|
||||
import { getQueryParam } from "../utils/query-param";
|
||||
|
||||
type AvailableTimeSlotsProps = {
|
||||
extraDays?: number;
|
||||
@@ -55,10 +57,12 @@ export const AvailableTimeSlots = ({
|
||||
isLoading,
|
||||
customClassNames,
|
||||
skipConfirmStep,
|
||||
seatsPerTimeSlot,
|
||||
onSubmit,
|
||||
...props
|
||||
}: AvailableTimeSlotsProps) => {
|
||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
||||
|
||||
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
|
||||
const setSeatedEventData = useBookerStore((state) => state.setSeatedEventData);
|
||||
const date = selectedDate || dayjs().format("YYYY-MM-DD");
|
||||
@@ -66,27 +70,6 @@ export const AvailableTimeSlots = ({
|
||||
const isColumnView = layout === BookerLayouts.COLUMN_VIEW;
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const onTimeSelect = (
|
||||
time: string,
|
||||
attendees: number,
|
||||
seatsPerTimeSlot?: number | null,
|
||||
bookingUid?: string
|
||||
) => {
|
||||
setSelectedTimeslot(time);
|
||||
if (seatsPerTimeSlot) {
|
||||
setSeatedEventData({
|
||||
seatsPerTimeSlot,
|
||||
attendees,
|
||||
bookingUid,
|
||||
showAvailableSeatsCount,
|
||||
});
|
||||
}
|
||||
if (skipConfirmStep) {
|
||||
onSubmit(time);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const nonEmptyScheduleDays = useNonEmptyScheduleDays(schedule?.slots);
|
||||
const nonEmptyScheduleDaysFromSelectedDate = nonEmptyScheduleDays.filter(
|
||||
(slot) => dayjs(selectedDate).diff(slot, "day") <= 0
|
||||
@@ -94,13 +77,53 @@ export const AvailableTimeSlots = ({
|
||||
|
||||
// Creates an array of dates to fetch slots for.
|
||||
// If `extraDays` is passed in, we will extend the array with the next `extraDays` days.
|
||||
const dates = !extraDays
|
||||
? [date]
|
||||
: nonEmptyScheduleDaysFromSelectedDate.length > 0
|
||||
? nonEmptyScheduleDaysFromSelectedDate.slice(0, extraDays)
|
||||
: [];
|
||||
const dates = useMemo(() => {
|
||||
if (!extraDays) return [date];
|
||||
if (nonEmptyScheduleDaysFromSelectedDate.length > 0) {
|
||||
return nonEmptyScheduleDaysFromSelectedDate.slice(0, extraDays);
|
||||
}
|
||||
return [];
|
||||
}, [date, extraDays, nonEmptyScheduleDaysFromSelectedDate]);
|
||||
|
||||
const slotsPerDay = useSlotsForAvailableDates(dates, schedule?.slots);
|
||||
const { slotsPerDay, toggleConfirmButton } = useSlotsForAvailableDates(dates, schedule?.slots);
|
||||
|
||||
const overlayCalendarToggled =
|
||||
getQueryParam("overlayCalendar") === "true" || localStorage.getItem("overlayCalendarSwitchDefault");
|
||||
|
||||
const onTimeSelect = useCallback(
|
||||
(time: string, attendees: number, seatsPerTimeSlot?: number | null, bookingUid?: string) => {
|
||||
setSelectedTimeslot(time);
|
||||
if (seatsPerTimeSlot) {
|
||||
setSeatedEventData({
|
||||
seatsPerTimeSlot,
|
||||
attendees,
|
||||
bookingUid,
|
||||
showAvailableSeatsCount,
|
||||
});
|
||||
}
|
||||
if (skipConfirmStep) {
|
||||
onSubmit(time);
|
||||
}
|
||||
return;
|
||||
},
|
||||
[onSubmit, setSeatedEventData, setSelectedTimeslot, skipConfirmStep, showAvailableSeatsCount]
|
||||
);
|
||||
|
||||
const handleSlotClick = useCallback(
|
||||
(selectedSlot: Slot, isOverlapping: boolean) => {
|
||||
if ((overlayCalendarToggled && isOverlapping) || skipConfirmStep) {
|
||||
toggleConfirmButton(selectedSlot);
|
||||
} else {
|
||||
onTimeSelect(
|
||||
selectedSlot.time,
|
||||
selectedSlot?.attendees || 0,
|
||||
seatsPerTimeSlot,
|
||||
selectedSlot.bookingUid
|
||||
);
|
||||
}
|
||||
},
|
||||
[overlayCalendarToggled, onTimeSelect, seatsPerTimeSlot, skipConfirmStep, toggleConfirmButton]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -150,6 +173,8 @@ export const AvailableTimeSlots = ({
|
||||
slots={slots.slots}
|
||||
showAvailableSeatsCount={showAvailableSeatsCount}
|
||||
skipConfirmStep={skipConfirmStep}
|
||||
seatsPerTimeSlot={seatsPerTimeSlot}
|
||||
handleSlotClick={handleSlotClick}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -124,7 +124,6 @@ export const BookEventForm = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Cloudflare Turnstile Captcha */}
|
||||
{!isPlatform && (
|
||||
<div className="text-subtle my-3 w-full text-xs">
|
||||
<Trans
|
||||
|
||||
@@ -3,10 +3,14 @@ import { useState, useEffect } from "react";
|
||||
import type { UseBookingFormReturnType } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { getBookingResponsesSchemaWithOptionalChecks } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
|
||||
import type { BookerEvent } from "../../../types";
|
||||
import type { BookerState } from "../../types";
|
||||
|
||||
const useSkipConfirmStep = (
|
||||
bookingForm: UseBookingFormReturnType["bookingForm"],
|
||||
bookerState: BookerState,
|
||||
isInstantMeeting: boolean,
|
||||
bookingFields?: BookerEvent["bookingFields"]
|
||||
) => {
|
||||
const bookingFormValues = bookingForm.getValues();
|
||||
@@ -34,8 +38,8 @@ const useSkipConfirmStep = (
|
||||
}
|
||||
};
|
||||
|
||||
checkSkipStep();
|
||||
}, [bookingFormValues, bookingFields, rescheduleUid]);
|
||||
bookerState === "selecting_time" && !isInstantMeeting && checkSkipStep();
|
||||
}, [bookingFormValues, bookingFields, rescheduleUid, bookerState]);
|
||||
|
||||
return canSkip;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// We do not need to worry about importing framer-motion here as it is lazy imported in Booker.
|
||||
import * as HoverCard from "@radix-ui/react-hover-card";
|
||||
import { AnimatePresence, m } from "framer-motion";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import type { IOutOfOfficeData } from "@calcom/core/getUserAvailability";
|
||||
@@ -9,7 +9,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import { OutOfOfficeInSlots } from "@calcom/features/bookings/Booker/components/OutOfOfficeInSlots";
|
||||
import type { IUseBookingLoadingStates } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import type { Slots } from "@calcom/features/schedules";
|
||||
import type { Slot } from "@calcom/features/schedules";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { getPaymentAppData } from "@calcom/lib/getPaymentAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -37,7 +37,7 @@ export type AvailableTimesProps = {
|
||||
} & Omit<SlotItemProps, "slot">;
|
||||
|
||||
type SlotItemProps = {
|
||||
slot: Slots[string][number];
|
||||
slot: Slot;
|
||||
seatsPerTimeSlot?: number | null;
|
||||
selectedSlots?: string[];
|
||||
onTimeSelect: TOnTimeSelect;
|
||||
@@ -52,6 +52,7 @@ type SlotItemProps = {
|
||||
skipConfirmStep?: boolean;
|
||||
shouldRenderCaptcha?: boolean;
|
||||
watchedCfToken?: string;
|
||||
handleSlotClick?: (slot: Slot, isOverlapping: boolean) => void;
|
||||
};
|
||||
|
||||
const SlotItem = ({
|
||||
@@ -68,6 +69,7 @@ const SlotItem = ({
|
||||
skipConfirmStep,
|
||||
shouldRenderCaptcha,
|
||||
watchedCfToken,
|
||||
handleSlotClick,
|
||||
}: SlotItemProps) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -106,26 +108,6 @@ const SlotItem = ({
|
||||
offset,
|
||||
});
|
||||
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
if (!showConfirm && ((overlayCalendarToggled && isOverlapping) || skipConfirmStep)) {
|
||||
setShowConfirm(true);
|
||||
return;
|
||||
}
|
||||
onTimeSelect(slot.time, slot?.attendees || 0, seatsPerTimeSlot, slot.bookingUid);
|
||||
}, [
|
||||
overlayCalendarToggled,
|
||||
isOverlapping,
|
||||
showConfirm,
|
||||
onTimeSelect,
|
||||
slot.time,
|
||||
slot?.attendees,
|
||||
slot.bookingUid,
|
||||
seatsPerTimeSlot,
|
||||
skipConfirmStep,
|
||||
]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<div className="flex gap-2">
|
||||
@@ -143,7 +125,7 @@ const SlotItem = ({
|
||||
data-testid="time"
|
||||
data-disabled={bookingFull}
|
||||
data-time={slot.time}
|
||||
onClick={onButtonClick}
|
||||
onClick={() => handleSlotClick && handleSlotClick(slot, isOverlapping)}
|
||||
className={classNames(
|
||||
`hover:border-brand-default min-h-9 mb-2 flex h-auto w-full flex-grow flex-col justify-center py-2`,
|
||||
selectedSlots?.includes(slot.time) && "border-brand-default",
|
||||
@@ -176,47 +158,40 @@ const SlotItem = ({
|
||||
</p>
|
||||
)}
|
||||
</Button>
|
||||
{showConfirm && (
|
||||
{!!slot.showConfirmButton && (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger asChild>
|
||||
<m.div initial={{ width: 0 }} animate={{ width: "auto" }} exit={{ width: 0 }}>
|
||||
{skipConfirmStep ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onTimeSelect(slot.time, slot?.attendees || 0, seatsPerTimeSlot, slot.bookingUid)
|
||||
}
|
||||
data-testid="skip-confirm-book-button"
|
||||
disabled={
|
||||
(!!shouldRenderCaptcha && !watchedCfToken) ||
|
||||
loadingStates?.creatingBooking ||
|
||||
loadingStates?.creatingRecurringBooking ||
|
||||
isVerificationCodeSending ||
|
||||
loadingStates?.creatingInstantBooking
|
||||
}
|
||||
color="primary"
|
||||
loading={
|
||||
(selectedTimeslot === slot.time && loadingStates?.creatingBooking) ||
|
||||
loadingStates?.creatingRecurringBooking ||
|
||||
isVerificationCodeSending ||
|
||||
loadingStates?.creatingInstantBooking
|
||||
}>
|
||||
{renderConfirmNotVerifyEmailButtonCond
|
||||
? isPaidEvent
|
||||
? t("pay_and_book")
|
||||
: t("confirm")
|
||||
: t("verify_email_email_button")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant={layout === "column_view" ? "icon" : "button"}
|
||||
StartIcon={layout === "column_view" ? "chevron-right" : undefined}
|
||||
onClick={() =>
|
||||
onTimeSelect(slot.time, slot?.attendees || 0, seatsPerTimeSlot, slot.bookingUid)
|
||||
}>
|
||||
{layout !== "column_view" && t("confirm")}
|
||||
</Button>
|
||||
)}
|
||||
<m.div key={slot.time} initial={{ width: 0 }} animate={{ width: "auto" }} exit={{ width: 0 }}>
|
||||
<Button
|
||||
variant={layout === "column_view" ? "icon" : "button"}
|
||||
StartIcon={layout === "column_view" ? "chevron-right" : undefined}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onTimeSelect(slot.time, slot?.attendees || 0, seatsPerTimeSlot, slot.bookingUid)
|
||||
}
|
||||
data-testid="skip-confirm-book-button"
|
||||
disabled={
|
||||
(!!shouldRenderCaptcha && !watchedCfToken) ||
|
||||
loadingStates?.creatingBooking ||
|
||||
loadingStates?.creatingRecurringBooking ||
|
||||
isVerificationCodeSending ||
|
||||
loadingStates?.creatingInstantBooking
|
||||
}
|
||||
color="primary"
|
||||
loading={
|
||||
(selectedTimeslot === slot.time && loadingStates?.creatingBooking) ||
|
||||
loadingStates?.creatingRecurringBooking ||
|
||||
isVerificationCodeSending ||
|
||||
loadingStates?.creatingInstantBooking
|
||||
}>
|
||||
{layout == "column_view"
|
||||
? ""
|
||||
: renderConfirmNotVerifyEmailButtonCond
|
||||
? isPaidEvent
|
||||
? t("pay_and_book")
|
||||
: t("confirm")
|
||||
: t("verify_email_email_button")}
|
||||
</Button>
|
||||
</m.div>
|
||||
</HoverCard.Trigger>
|
||||
{isOverlapping && (
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from "./components";
|
||||
export type { Slots } from "./lib/use-schedule";
|
||||
export type { Slots, Slot } from "./lib/use-schedule";
|
||||
export { useSchedule, useSlotsForDate, useNonEmptyScheduleDays } from "./lib/use-schedule";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { useSchedule } from "./useSchedule";
|
||||
export { useSlotsForDate } from "./useSlotsForDate";
|
||||
export { useNonEmptyScheduleDays } from "./useNonEmptyScheduleDays";
|
||||
export type { Slots, GetSchedule } from "./types";
|
||||
export type { Slots, GetSchedule, Slot } from "./types";
|
||||
|
||||
@@ -2,4 +2,6 @@ import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
|
||||
export type Slots = RouterOutputs["viewer"]["public"]["slots"]["getSchedule"]["slots"];
|
||||
|
||||
export type Slot = Slots[string][number] & { showConfirmButton?: boolean };
|
||||
|
||||
export type GetSchedule = RouterOutputs["viewer"]["public"]["slots"]["getSchedule"];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import type { Slots } from "./types";
|
||||
import type { Slots, Slot } from "./types";
|
||||
|
||||
/**
|
||||
* Gets slots for a specific date from the schedule cache.
|
||||
@@ -18,14 +18,35 @@ export const useSlotsForDate = (date: string | null, slots?: Slots) => {
|
||||
};
|
||||
|
||||
export const useSlotsForAvailableDates = (dates: (string | null)[], slots?: Slots) => {
|
||||
const slotsForDates = useMemo(() => {
|
||||
if (slots === undefined) return [];
|
||||
return dates
|
||||
const [slotsPerDay, setSlotsPerDay] = useState<{ date: string | null; slots: Slots[string] }[]>([]);
|
||||
|
||||
const toggleConfirmButton = useCallback((selectedSlot: Slot) => {
|
||||
setSlotsPerDay((prevSlotsPerDay) =>
|
||||
prevSlotsPerDay.map(({ date, slots }) => ({
|
||||
date,
|
||||
slots: slots.map((slot) => ({
|
||||
...slot,
|
||||
showConfirmButton: slot.time === selectedSlot.time ? !selectedSlot?.showConfirmButton : false,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (slots === undefined) {
|
||||
setSlotsPerDay([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSlots = dates
|
||||
.filter((date) => date !== null)
|
||||
.map((date) => ({
|
||||
slots: slots[`${date}`] || [],
|
||||
date,
|
||||
}));
|
||||
}, [dates, slots]);
|
||||
return slotsForDates;
|
||||
|
||||
setSlotsPerDay(updatedSlots);
|
||||
}, [JSON.stringify(dates), JSON.stringify(slots)]);
|
||||
|
||||
return { slotsPerDay, setSlotsPerDay, toggleConfirmButton } as const;
|
||||
};
|
||||
|
||||
@@ -521,8 +521,8 @@ export const BookerPlatformWrapper = (
|
||||
onOverlaySwitchStateChange={onOverlaySwitchStateChange}
|
||||
extraOptions={extraOptions ?? {}}
|
||||
bookings={{
|
||||
handleBookEvent: () => {
|
||||
handleBookEvent();
|
||||
handleBookEvent: (timeSlot?: string) => {
|
||||
handleBookEvent(timeSlot);
|
||||
return;
|
||||
},
|
||||
expiryTime: undefined,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import { useBookerTime } from "@calcom/features/bookings/Booker/components/hooks/useBookerTime";
|
||||
import type { UseBookingFormReturnType } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
@@ -7,9 +8,11 @@ import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||
import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
|
||||
import { showToast } from "@calcom/ui";
|
||||
|
||||
import type { UseCreateBookingInput } from "./useCreateBooking";
|
||||
|
||||
type Callbacks = { onSuccess?: () => void; onError?: (err: any) => void };
|
||||
type UseHandleBookingProps = {
|
||||
bookingForm: UseBookingFormReturnType["bookingForm"];
|
||||
event?: {
|
||||
@@ -20,9 +23,9 @@ type UseHandleBookingProps = {
|
||||
};
|
||||
metadata: Record<string, string>;
|
||||
hashedLink?: string | null;
|
||||
handleBooking: (input: UseCreateBookingInput) => void;
|
||||
handleInstantBooking: (input: BookingCreateBody) => void;
|
||||
handleRecBooking: (input: BookingCreateBody[]) => void;
|
||||
handleBooking: (input: UseCreateBookingInput, callbacks?: Callbacks) => void;
|
||||
handleInstantBooking: (input: BookingCreateBody, callbacks?: Callbacks) => void;
|
||||
handleRecBooking: (input: BookingCreateBody[], callbacks?: Callbacks) => void;
|
||||
locationUrl?: string;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
@@ -38,6 +41,7 @@ export const useHandleBookEvent = ({
|
||||
locationUrl,
|
||||
routingFormSearchParams,
|
||||
}: UseHandleBookingProps) => {
|
||||
const isPlatform = useIsPlatform();
|
||||
const setFormValues = useBookerStore((state) => state.setFormValues);
|
||||
const storeTimeSlot = useBookerStore((state) => state.selectedTimeslot);
|
||||
const duration = useBookerStore((state) => state.selectedDuration);
|
||||
@@ -54,10 +58,15 @@ export const useHandleBookEvent = ({
|
||||
const teamMemberEmail = useBookerStore((state) => state.teamMemberEmail);
|
||||
const crmOwnerRecordType = useBookerStore((state) => state.crmOwnerRecordType);
|
||||
const crmAppSlug = useBookerStore((state) => state.crmAppSlug);
|
||||
const handleError = (err: any) => {
|
||||
const errorMessage = err?.message ? t(err.message) : t("can_you_try_again");
|
||||
showToast(errorMessage, "error");
|
||||
};
|
||||
|
||||
const handleBookEvent = (inputTimeSlot?: string) => {
|
||||
const values = bookingForm.getValues();
|
||||
const timeslot = inputTimeSlot ?? storeTimeSlot;
|
||||
const callbacks = inputTimeSlot && !isPlatform ? { onError: handleError } : undefined;
|
||||
if (timeslot) {
|
||||
// Clears form values stored in store, so old values won't stick around.
|
||||
setFormValues({});
|
||||
@@ -101,11 +110,11 @@ export const useHandleBookEvent = ({
|
||||
};
|
||||
|
||||
if (isInstantMeeting) {
|
||||
handleInstantBooking(mapBookingToMutationInput(bookingInput));
|
||||
handleInstantBooking(mapBookingToMutationInput(bookingInput), callbacks);
|
||||
} else if (event.data?.recurringEvent?.freq && recurringEventCount && !rescheduleUid) {
|
||||
handleRecBooking(mapRecurringBookingToMutationInput(bookingInput, recurringEventCount));
|
||||
handleRecBooking(mapRecurringBookingToMutationInput(bookingInput, recurringEventCount), callbacks);
|
||||
} else {
|
||||
handleBooking({ ...mapBookingToMutationInput(bookingInput), locationUrl });
|
||||
handleBooking({ ...mapBookingToMutationInput(bookingInput), locationUrl }, callbacks);
|
||||
}
|
||||
// Clears form values stored in store, so old values won't stick around.
|
||||
setFormValues({});
|
||||
|
||||
Reference in New Issue
Block a user