diff --git a/apps/web/modules/event-types/components/tabs/advanced/FormBuilder.tsx b/apps/web/modules/event-types/components/tabs/advanced/FormBuilder.tsx index f08c51ea68e676..de6a10104180cb 100644 --- a/apps/web/modules/event-types/components/tabs/advanced/FormBuilder.tsx +++ b/apps/web/modules/event-types/components/tabs/advanced/FormBuilder.tsx @@ -588,6 +588,7 @@ function FieldEditDialog({ //resolver: zodResolver(fieldSchema), }); const formFieldType = fieldForm.getValues("type"); + const fieldName = fieldForm.watch("name"); useEffect(() => { if (!formFieldType) { @@ -713,6 +714,27 @@ function FieldEditDialog({ ) : null} + {formFieldType === "multiemail" && fieldName === "guests" && ( + { + if (value === "") { + return undefined; + } + const parsed = Number(value); + if (Number.isNaN(parsed) || !Number.isInteger(parsed) || parsed < 1) { + return undefined; + } + return parsed; + }, + })} + containerClassName="mt-6" + label={t("max_attendees")} + type="number" + min={1} + /> + )} + {formFieldType === "email" && ( = CommonProps & { name?: string; + maxEntries?: number; }; export type TextLikeComponentPropsRAQB = diff --git a/packages/features/bookings/lib/getBookingResponsesSchema.ts b/packages/features/bookings/lib/getBookingResponsesSchema.ts index f858b84203eb04..fb50c9318543c1 100644 --- a/packages/features/bookings/lib/getBookingResponsesSchema.ts +++ b/packages/features/bookings/lib/getBookingResponsesSchema.ts @@ -268,6 +268,13 @@ function preprocess({ } const emails = emailsParsed.data; + if (typeof bookingField.maxEntries === "number" && emails.length > bookingField.maxEntries) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: m("max_attendees_allowed", { count: bookingField.maxEntries }), + }); + continue; + } emails.sort().some((item, i) => { if (item === emails[i + 1]) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: m("duplicate_email") }); diff --git a/packages/features/form-builder/Components.tsx b/packages/features/form-builder/Components.tsx index ed72d16e3c72bc..fd669f2dca81b5 100644 --- a/packages/features/form-builder/Components.tsx +++ b/packages/features/form-builder/Components.tsx @@ -247,7 +247,11 @@ export const Components: Record = { factory: function MultiEmail({ value, readOnly, label, setValue, ...props }) { const placeholder = props.placeholder; const { t } = useLocale(); - value = value || []; + const maxEntries = props.maxEntries; + const listValue = value || []; + const hasReachedLimit = + typeof maxEntries === "number" && maxEntries > 0 && listValue.length >= maxEntries; + value = listValue; return ( <> {value.length ? ( @@ -293,13 +297,20 @@ export const Components: Record = { color="minimal" StartIcon="user-plus" className="my-2.5" + disabled={hasReachedLimit} onClick={() => { + if (hasReachedLimit) { + return; + } value.push(""); setValue(value); }}> {t("add_another")} )} + {!readOnly && typeof maxEntries === "number" && maxEntries > 0 && ( +

{t("max_attendees_allowed", { count: maxEntries })}

+ )} ) : ( <> @@ -311,7 +322,11 @@ export const Components: Record = { color="minimal" variant="button" StartIcon="user-plus" + disabled={hasReachedLimit} onClick={() => { + if (hasReachedLimit) { + return; + } value.push(""); setValue(value); }} diff --git a/packages/features/form-builder/FormBuilderField.tsx b/packages/features/form-builder/FormBuilderField.tsx index 3288772d612f16..5c7035c15f8438 100644 --- a/packages/features/form-builder/FormBuilderField.tsx +++ b/packages/features/form-builder/FormBuilderField.tsx @@ -312,6 +312,7 @@ export const ComponentForField = ({ placeholder={field.placeholder} name={field.name} label={field.label} + maxEntries={field.maxEntries} readOnly={readOnly} value={value as string[]} setValue={setValue as (arg: typeof value) => void} diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index 9dd03b80d581d3..d17bf8477a037a 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -1091,6 +1091,12 @@ export const baseFieldSchema = z.object({ */ maxLength: z.number().optional(), + /** + * It is the maximum number of entries that can be provided for list fields. + * It is used for types like `multiemail`. + */ + maxEntries: z.number().int().min(1).optional(), + // Emails that needs to be excluded excludeEmails: excludeOrRequireEmailSchema.optional(), // Emails that need to be required