@@ -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 = ({
)}
- {showConfirm && (
+ {!!slot.showConfirmButton && (
-
- {skipConfirmStep ? (
-
- ) : (
-
- )}
+
+
{isOverlapping && (
diff --git a/packages/features/schedules/index.ts b/packages/features/schedules/index.ts
index fe06d0e457..68433e2936 100644
--- a/packages/features/schedules/index.ts
+++ b/packages/features/schedules/index.ts
@@ -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";
diff --git a/packages/features/schedules/lib/use-schedule/index.ts b/packages/features/schedules/lib/use-schedule/index.ts
index 9b824cc91b..a4c448a7fc 100644
--- a/packages/features/schedules/lib/use-schedule/index.ts
+++ b/packages/features/schedules/lib/use-schedule/index.ts
@@ -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";
diff --git a/packages/features/schedules/lib/use-schedule/types.ts b/packages/features/schedules/lib/use-schedule/types.ts
index 390427fccf..6c791376da 100644
--- a/packages/features/schedules/lib/use-schedule/types.ts
+++ b/packages/features/schedules/lib/use-schedule/types.ts
@@ -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"];
diff --git a/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts b/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts
index 8014fa3689..ff22adb3cd 100644
--- a/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts
+++ b/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts
@@ -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;
};
diff --git a/packages/platform/atoms/booker/BookerPlatformWrapper.tsx b/packages/platform/atoms/booker/BookerPlatformWrapper.tsx
index 7217c6709f..583c644b5e 100644
--- a/packages/platform/atoms/booker/BookerPlatformWrapper.tsx
+++ b/packages/platform/atoms/booker/BookerPlatformWrapper.tsx
@@ -521,8 +521,8 @@ export const BookerPlatformWrapper = (
onOverlaySwitchStateChange={onOverlaySwitchStateChange}
extraOptions={extraOptions ?? {}}
bookings={{
- handleBookEvent: () => {
- handleBookEvent();
+ handleBookEvent: (timeSlot?: string) => {
+ handleBookEvent(timeSlot);
return;
},
expiryTime: undefined,
diff --git a/packages/platform/atoms/hooks/bookings/useHandleBookEvent.ts b/packages/platform/atoms/hooks/bookings/useHandleBookEvent.ts
index 830773fbf5..bfc4220fc5 100644
--- a/packages/platform/atoms/hooks/bookings/useHandleBookEvent.ts
+++ b/packages/platform/atoms/hooks/bookings/useHandleBookEvent.ts
@@ -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;
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({});