import { useFormContext } from "react-hook-form"; import type { LocationObject } from "@calcom/app-store/locations"; import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect"; import { FormBuilderField } from "@calcom/features/form-builder"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { SystemField } from "../../../lib/getBookingFields"; export const BookingFields = ({ fields, locations, rescheduleUid, isDynamicGroupBooking, }: { fields: NonNullable["bookingFields"]; locations: LocationObject[]; rescheduleUid?: string; isDynamicGroupBooking: boolean; }) => { const { t } = useLocale(); const { watch, setValue } = useFormContext(); const locationResponse = watch("responses.location"); const currentView = rescheduleUid ? "reschedule" : ""; return ( // TODO: It might make sense to extract this logic into BookingFields config, that would allow to quickly configure system fields and their editability in fresh booking and reschedule booking view
{fields.map((field, index) => { // During reschedule by default all system fields are readOnly. Make them editable on case by case basis. // Allowing a system field to be edited might require sending emails to attendees, so we need to be careful let readOnly = (field.editable === "system" || field.editable === "system-but-optional") && !!rescheduleUid; let noLabel = false; let hidden = !!field.hidden; const fieldViews = field.views; if (fieldViews && !fieldViews.find((view) => view.id === currentView)) { return null; } if (field.name === SystemField.Enum.rescheduleReason) { // rescheduleReason is a reschedule specific field and thus should be editable during reschedule readOnly = false; } if (field.name === SystemField.Enum.smsReminderNumber) { // `smsReminderNumber` and location.optionValue when location.value===phone are the same data point. We should solve it in a better way in the Form Builder itself. // I think we should have a way to connect 2 fields together and have them share the same value in Form Builder if (locationResponse?.value === "phone") { setValue(`responses.${SystemField.Enum.smsReminderNumber}`, locationResponse?.optionValue); // Just don't render the field now, as the value is already connected to attendee phone location return null; } // `smsReminderNumber` can be edited during reschedule even though it's a system field readOnly = false; } if (field.name === SystemField.Enum.guests) { // No matter what user configured for Guests field, we don't show it for dynamic group booking as that doesn't support guests hidden = isDynamicGroupBooking ? true : !!field.hidden; } // We don't show `notes` field during reschedule if ( (field.name === SystemField.Enum.notes || field.name === SystemField.Enum.guests) && !!rescheduleUid ) { return null; } // Dynamically populate location field options if (field.name === SystemField.Enum.location && field.type === "radioInput") { if (!field.optionsInputs) { throw new Error("radioInput must have optionsInputs"); } const optionsInputs = field.optionsInputs; // TODO: Instead of `getLocationOptionsForSelect` options should be retrieved from dataStore[field.getOptionsAt]. It would make it agnostic of the `name` of the field. const options = getLocationOptionsForSelect(locations, t); options.forEach((option) => { const optionInput = optionsInputs[option.value as keyof typeof optionsInputs]; if (optionInput) { optionInput.placeholder = option.inputPlaceholder; } }); field.options = options.filter( (location): location is NonNullable<(typeof options)[number]> => !!location ); // If we have only one option and it has an input, we don't show the field label because Option name acts as label. // e.g. If it's just Attendee Phone Number option then we don't show `Location` label if (field.options.length === 1) { if (field.optionsInputs[field.options[0].value]) { noLabel = true; } else { // If there's only one option and it doesn't have an input, we don't show the field at all because it's visible in the left side bar hidden = true; } } } const label = noLabel ? "" : field.label || t(field.defaultLabel || ""); const placeholder = field.placeholder || t(field.defaultPlaceholder || ""); return (
); };