From 9f655ed819f52357c80dafc29654e4772e7a1cd5 Mon Sep 17 00:00:00 2001 From: Devanshu Sharma Date: Thu, 12 Jun 2025 22:39:21 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20added=20validation=20to=20limit=20event?= =?UTF-8?q?=20duration=20to=201440=20minutes=20(24=20ho=E2=80=A6=20(#21379?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: added validation to limit event duration to 1440 minutes (24 hours) * Update English locale strings in common.json * feat: introduce minimum event duration constant and update validations and fixed some suggested changes * fix: remove static error message for maximum event duration validation * fix: update minimum event duration to 1 minute and adjust validations accordingly --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: amrit --- apps/api/v1/lib/validations/event-type.ts | 8 ++++++-- apps/web/public/static/locales/en/common.json | 2 ++ .../components/CreateEventTypeForm.tsx | 16 ++++++++++++++-- .../components/tabs/setup/EventSetupTab.tsx | 16 ++++++++++++++-- packages/lib/constants.ts | 6 ++++++ packages/prisma/zod/custom/eventtype.ts | 5 +++-- 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/apps/api/v1/lib/validations/event-type.ts b/apps/api/v1/lib/validations/event-type.ts index 81cfb1a0f0..a1d855c7ac 100644 --- a/apps/api/v1/lib/validations/event-type.ts +++ b/apps/api/v1/lib/validations/event-type.ts @@ -1,6 +1,10 @@ import { z } from "zod"; -import { MAX_SEATS_PER_TIME_SLOT } from "@calcom/lib/constants"; +import { + MAX_SEATS_PER_TIME_SLOT, + MAX_EVENT_DURATION_MINUTES, + MIN_EVENT_DURATION_MINUTES, +} from "@calcom/lib/constants"; import slugify from "@calcom/lib/slugify"; import { _EventTypeModel as EventType, _HostModel } from "@calcom/prisma/zod"; import { customInputSchema, eventTypeBookingFields } from "@calcom/prisma/zod-utils"; @@ -80,7 +84,7 @@ const schemaEventTypeCreateParams = z title: z.string(), slug: z.string().transform((s) => slugify(s)), description: z.string().optional().nullable(), - length: z.number().int(), + length: z.number().int().min(MIN_EVENT_DURATION_MINUTES).max(MAX_EVENT_DURATION_MINUTES), metadata: z.any().optional(), recurringEvent: recurringEventInputSchema.optional(), seatsPerTimeSlot: z.number().optional(), diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index a76c811614..e407cc600f 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3241,5 +3241,7 @@ "meeting_start_time": "Meeting Start Time", "members_affected_by_disabling_delegation_credential": "Affected members", "no_members_affected_by_disabling_delegation_credential": "No members affected by disabling delegation credential", + "duration_min_error": "Duration must be at least {{min}} minute(s)", + "duration_max_error": "Duration cannot exceed {{max}} minutes (24 hours)", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/features/eventtypes/components/CreateEventTypeForm.tsx b/packages/features/eventtypes/components/CreateEventTypeForm.tsx index 86686bc2d6..bc1398046e 100644 --- a/packages/features/eventtypes/components/CreateEventTypeForm.tsx +++ b/packages/features/eventtypes/components/CreateEventTypeForm.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import type { UseFormReturn } from "react-hook-form"; import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform"; +import { MAX_EVENT_DURATION_MINUTES, MIN_EVENT_DURATION_MINUTES } from "@calcom/lib/constants"; import type { CreateEventTypeFormValues } from "@calcom/lib/hooks/useCreateEventType"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; @@ -124,11 +125,22 @@ export default function CreateEventTypeForm({ diff --git a/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx b/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx index 92ddd55b7e..932e3557d4 100644 --- a/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx +++ b/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx @@ -14,6 +14,7 @@ import type { SettingsToggleClassNames, } from "@calcom/features/eventtypes/lib/types"; import type { FormValues, LocationFormValues } from "@calcom/features/eventtypes/lib/types"; +import { MAX_EVENT_DURATION_MINUTES, MIN_EVENT_DURATION_MINUTES } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; import { slugify } from "@calcom/lib/slugify"; @@ -337,9 +338,20 @@ export const EventSetupTab = ( {...(isManagedEventType || isChildrenManagedEventType ? lengthLockedProps : {})} label={t("duration")} defaultValue={formMethods.getValues("length") ?? 15} - {...formMethods.register("length")} + {...formMethods.register("length", { + valueAsNumber: true, + min: { + value: MIN_EVENT_DURATION_MINUTES, + message: t("duration_min_error", { min: MIN_EVENT_DURATION_MINUTES }), + }, + max: { + value: MAX_EVENT_DURATION_MINUTES, + message: t("duration_max_error", { max: MAX_EVENT_DURATION_MINUTES }), + }, + })} addOnSuffix={<>{t("minutes")}} - min={1} + min={MIN_EVENT_DURATION_MINUTES} + max={MAX_EVENT_DURATION_MINUTES} /> )} {!lengthLockedProps.disabled && ( diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 20b4d95e74..1089e628ed 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -61,6 +61,12 @@ export const EMBED_LIB_URL = process.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_ export const TRIAL_LIMIT_DAYS = 14; export const MAX_SEATS_PER_TIME_SLOT = 1000; +/** Maximum duration allowed for an event in minutes (24 hours) */ +export const MAX_EVENT_DURATION_MINUTES = 1440; + +/** Minimum duration allowed for an event in minutes */ +export const MIN_EVENT_DURATION_MINUTES = 1; + export const HOSTED_CAL_FEATURES = process.env.NEXT_PUBLIC_HOSTED_CAL_FEATURES || !IS_SELF_HOSTED; export const PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS = diff --git a/packages/prisma/zod/custom/eventtype.ts b/packages/prisma/zod/custom/eventtype.ts index 56b720f9ef..d1374b5c5d 100755 --- a/packages/prisma/zod/custom/eventtype.ts +++ b/packages/prisma/zod/custom/eventtype.ts @@ -1,6 +1,7 @@ import { SchedulingType } from "@calcom/prisma/enums"; import { z } from "zod"; import * as imports from "../../zod-utils"; +import { MIN_EVENT_DURATION_MINUTES, MAX_EVENT_DURATION_MINUTES } from "@calcom/lib/constants"; // TODO: figure out why EventTypeModel is being called even if it's not imported here, causing a circular dependency // import { _EventTypeModel } from "../eventtype"; @@ -8,7 +9,7 @@ export const createEventTypeInput = z.object({ title: z.string().min(1), slug: imports.eventTypeSlug, description: z.string().nullish(), - length: z.number().int(), + length: z.number().int().min(MIN_EVENT_DURATION_MINUTES).max(MAX_EVENT_DURATION_MINUTES), hidden: z.boolean(), teamId: z.number().int().nullish(), schedulingType: z.nativeEnum(SchedulingType).nullish(), @@ -32,7 +33,7 @@ export const createEventTypeInput = z.object({ slug: z.string(), title: z.string().min(1), description: z.string(), - length: z.number(), + length: z.number().int().min(MIN_EVENT_DURATION_MINUTES).max(MAX_EVENT_DURATION_MINUTES), teamId: z.number().nullish(), }).strict();