import type { TFunction } from "i18next"; import Link from "next/link"; import { useMemo, useState } from "react"; import type { FieldError } from "react-hook-form"; import { useIsPlatformBookerEmbed } from "@calcom/atoms/hooks/useIsPlatformBookerEmbed"; import type { BookerEvent } from "@calcom/features/bookings/types"; import ServerTrans from "@calcom/lib/components/ServerTrans"; import { WEBSITE_PRIVACY_POLICY_URL, WEBSITE_TERMS_URL } from "@calcom/lib/constants"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getPaymentAppData } from "@calcom/lib/getPaymentAppData"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { TimeFormat } from "@calcom/lib/timeFormat"; import { Alert } from "@calcom/ui/components/alert"; import { Button } from "@calcom/ui/components/button"; import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import { Form } from "@calcom/ui/components/form"; import { useBookerStore } from "../../store"; import { formatEventFromTime } from "../../utils/dates"; import { useBookerTime } from "../hooks/useBookerTime"; import type { UseBookingFormReturnType } from "../hooks/useBookingForm"; import type { IUseBookingErrors, IUseBookingLoadingStates } from "../hooks/useBookings"; import { BookingFields } from "./BookingFields"; import { FormSkeleton } from "./Skeleton"; type BookEventFormProps = { onCancel?: () => void; onSubmit: () => void; errorRef: React.RefObject; errors: UseBookingFormReturnType["errors"] & IUseBookingErrors; loadingStates: IUseBookingLoadingStates; children?: React.ReactNode; bookingForm: UseBookingFormReturnType["bookingForm"]; renderConfirmNotVerifyEmailButtonCond: boolean; extraOptions: Record; isPlatform?: boolean; isVerificationCodeSending: boolean; isTimeslotUnavailable: boolean; shouldRenderCaptcha?: boolean; confirmButtonDisabled?: boolean; classNames?: { confirmButton?: string; backButton?: string; }; }; export const BookEventForm = ({ onCancel, eventQuery, onSubmit, errorRef, errors, loadingStates, renderConfirmNotVerifyEmailButtonCond, bookingForm, children, extraOptions, isVerificationCodeSending, isPlatform = false, isTimeslotUnavailable, shouldRenderCaptcha, confirmButtonDisabled, classNames, }: Omit & { eventQuery: { isError: boolean; isPending: boolean; data?: Pick | null; }; }) => { const eventType = eventQuery.data; const setFormValues = useBookerStore((state) => state.setFormValues); const bookingData = useBookerStore((state) => state.bookingData); const rescheduleUid = useBookerStore((state) => state.rescheduleUid); const timeslot = useBookerStore((state) => state.selectedTimeslot); const username = useBookerStore((state) => state.username); const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting); const isPlatformBookerEmbed = useIsPlatformBookerEmbed(); const { timeFormat, timezone } = useBookerTime(); const [responseVercelIdHeader] = useState(null); const { t, i18n } = useLocale(); const isPaidEvent = useMemo(() => { if (!eventType?.price) return false; const paymentAppData = getPaymentAppData(eventType); return eventType?.price > 0 && !Number.isNaN(paymentAppData.price) && paymentAppData.price > 0; }, [eventType]); if (eventQuery.isError) return ; if (eventQuery.isPending || !eventQuery.data) return ; if (!timeslot) return ( ); if (!eventType) { console.warn("No event type found for event", extraOptions); return ; } const watchedCfToken = bookingForm.watch("cfToken"); return (
{ // Form data is saved in store. This way when user navigates back to // still change the timeslot, and comes back to the form, all their values // still exist. This gets cleared when the form is submitted. const values = bookingForm.getValues(); setFormValues(values); }} form={bookingForm} handleSubmit={onSubmit} noValidate> -1)} fields={eventType.bookingFields} locations={eventType.locations} rescheduleUid={rescheduleUid || undefined} bookingData={bookingData} /> {errors.hasFormErrors || errors.hasDataErrors ? (
) : isTimeslotUnavailable ? (
Please select a new time , ]} /> } />
) : null} {!isPlatform && (
Terms , Privacy Policy. , ]} />
)} {isPlatformBookerEmbed && (
{t("proceeding_agreement")}{" "} {t("terms")} {" "} {t("and")}{" "} {t("privacy_policy")} .
)}
{isInstantMeeting ? ( ) : ( <> {!!onCancel && ( )} )}
{children}
); }; const getError = ({ globalError, dataError, t, responseVercelIdHeader, timeFormat, timezone, language, }: { globalError: FieldError | undefined; // It feels like an implementation detail to reimplement the types of useMutation here. // Since they don't matter for this function, I'd rather disable them then giving you // the cognitive overload of thinking to update them here when anything changes. // eslint-disable-next-line @typescript-eslint/no-explicit-any dataError: any; t: TFunction; responseVercelIdHeader: string | null; timeFormat: TimeFormat; timezone: string; language: string; }) => { if (globalError) return globalError?.message; const error = dataError; let date = ""; if (error.message === ErrorCode.BookerLimitExceededReschedule) { const formattedDate = formatEventFromTime({ date: error.data.startTime, timeFormat, timeZone: timezone, language, }); date = `${formattedDate.date} ${formattedDate.time}`; } return error?.message ? ( <> {responseVercelIdHeader ?? ""} {t(error.message, { date })} ) : ( <>{t("can_you_try_again")} ); };