import { useEffect } from "react"; import type { z } from "zod"; import Widgets from "@calcom/app-store/routing-forms/components/react-awesome-query-builder/widgets"; import type { TextLikeComponentProps, SelectLikeComponentProps, } from "@calcom/app-store/routing-forms/components/react-awesome-query-builder/widgets"; import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { BookingFieldType } from "@calcom/prisma/zod-utils"; import { PhoneInput, AddressInput, Button, Label, Group, RadioField, EmailField, Tooltip } from "@calcom/ui"; import { FiUserPlus, FiX } from "@calcom/ui/components/icon"; import { ComponentForField } from "./FormBuilder"; import type { fieldsSchema } from "./FormBuilderFieldsSchema"; type Component = | { propsType: "text"; factory: (props: TProps) => JSX.Element; } | { propsType: "textList"; factory: >(props: TProps) => JSX.Element; } | { propsType: "select"; factory: (props: TProps) => JSX.Element; } | { propsType: "boolean"; factory: >(props: TProps) => JSX.Element; } | { propsType: "multiselect"; factory: >(props: TProps) => JSX.Element; } | { // Objective type question with option having a possible input propsType: "objectiveWithInput"; factory: < TProps extends SelectLikeComponentProps<{ value: string; optionValue: string; }> & { optionsInputs: NonNullable[number]["optionsInputs"]>; value: { value: string; optionValue: string }; } & { name?: string; } >( props: TProps ) => JSX.Element; }; // TODO: Share FormBuilder components across react-query-awesome-builder(for Routing Forms) widgets. // There are certain differences b/w two. Routing Forms expect label to be provided by the widget itself and FormBuilder adds label itself and expect no label to be added by component. // Routing Form approach is better as it provides more flexibility to show the label in complex components. But that can't be done right now because labels are missing consistent asterisk required support across different components export const Components: Record = { text: { propsType: "text", factory: (props) => , }, textarea: { propsType: "text", // TODO: Make rows configurable in the form builder factory: (props) => , }, number: { propsType: "text", factory: (props) => , }, name: { propsType: "text", // Keep special "name" type field and later build split(FirstName and LastName) variant of it. factory: (props) => , }, phone: { propsType: "text", factory: ({ setValue, readOnly, ...props }) => { if (!props) { return
; } return ( { setValue(val); }} {...props} /> ); }, }, email: { propsType: "text", factory: (props) => { if (!props) { return
; } return ; }, }, address: { propsType: "text", factory: (props) => { return ( { props.setValue(val); }} {...props} /> ); }, }, multiemail: { propsType: "textList", //TODO: Make it a ui component factory: function MultiEmail({ value, readOnly, label, setValue, ...props }) { const placeholder = props.placeholder; const { t } = useLocale(); value = value || []; const inputClassName = "dark:placeholder:text-darkgray-600 focus:border-brand dark:border-darkgray-300 dark:text-darkgray-900 block w-full rounded-md border-gray-300 text-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:bg-transparent dark:selection:bg-green-500 disabled:dark:text-gray-500"; return ( <> {value.length ? (
    {value.map((field, index) => (
  • { value[index] = e.target.value; setValue(value); }} className={classNames(inputClassName, "border-r-0")} addOnClassname={classNames( "border-gray-300 border block border-l-0 disabled:bg-gray-200 disabled:hover:cursor-not-allowed bg-transparent disabled:text-gray-500 dark:border-darkgray-300 " )} placeholder={placeholder} label={<>} required addOnSuffix={ !readOnly ? ( ) : null } />
  • ))}
{!readOnly && ( )}
) : ( <> )} {!value.length && !readOnly && ( )} ); }, }, multiselect: { propsType: "multiselect", factory: (props) => { const newProps = { ...props, listValues: props.options.map((o) => ({ title: o.label, value: o.value })), }; return ; }, }, select: { propsType: "select", factory: (props) => { const newProps = { ...props, listValues: props.options.map((o) => ({ title: o.label, value: o.value })), }; return ; }, }, checkbox: { propsType: "multiselect", factory: ({ options, readOnly, setValue, value }) => { value = value || []; return (
{options.map((option, i) => { return ( ); })}
); }, }, radio: { propsType: "select", factory: ({ setValue, value, options }) => { return ( { setValue(e); }}> <> {options.map((option, i) => ( ))} ); }, }, radioInput: { propsType: "objectiveWithInput", factory: function RadioInputWithLabel({ name, options, optionsInputs, value, setValue, readOnly }) { useEffect(() => { if (!value) { setValue({ value: options[0]?.value, optionValue: "", }); } }, [options, setValue, value]); return (
{options.length > 1 ? ( options.map((option, i) => { return ( ); }) ) : ( // Show option itself as label because there is just one option // TODO: Support asterisk for required fields )}
{(() => { const optionField = optionsInputs[value?.value]; if (!optionField) { return null; } return (
{ setValue({ value: value?.value, optionValue: val, }); }} />
); })()}
); }, }, boolean: { propsType: "boolean", factory: ({ readOnly, label, value, setValue }) => { return (
{ if (e.target.checked) { setValue(true); } else { setValue(false); } }} className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500" placeholder="" checked={value} disabled={readOnly} />
); }, }, } as const; // Should use `statisfies` to check if the `type` is from supported types. But satisfies doesn't work with Next.js config