Add 120 min meeting length option - #1
Conversation
| @@ -0,0 +1 @@ | |||
| export const DEFAULT_EVENT_DURATION_OPTIONS = [15, 30, 45, 60, 90, 120]; | |||
There was a problem hiding this comment.
[Maintainability] [CodeDuplication] The logic to render the <datalist> with DEFAULT_EVENT_DURATION_OPTIONS is repeated in three files (CreateEventTypeForm.tsx, DuplicateDialog.tsx, and EventSetupTab.tsx).
Consider renaming this file to durationOptions.tsx and exporting a reusable component (e.g., DurationDatalist) to encapsulate this markup and keep the code DRY.
Context for Agents
[CodeDuplication] The logic to render the `<datalist>` with `DEFAULT_EVENT_DURATION_OPTIONS` is repeated in three files (`CreateEventTypeForm.tsx`, `DuplicateDialog.tsx`, and `EventSetupTab.tsx`).
Consider renaming this file to `durationOptions.tsx` and exporting a reusable component (e.g., `DurationDatalist`) to encapsulate this markup and keep the code DRY.
File: apps/web/modules/event-types/components/durationOptions.ts
Line: 1| min="1" | ||
| placeholder="15" | ||
| label={t("duration")} | ||
| list={durationListId} | ||
| {...register("length", { valueAsNumber: true })} | ||
| addOnSuffix={t("minutes")} | ||
| /> |
There was a problem hiding this comment.
[Maintainability] [DECOMPOSED: Pattern Consistency] The duration field in DuplicateDialog.tsx is inconsistent with CreateEventTypeForm.tsx and EventSetupTab.tsx. It uses a hardcoded min="1" and is missing the max constraint and validation messages in the register call. For consistency and to ensure the same validation rules apply when duplicating an event type, use the shared constants and validation logic.
| min="1" | |
| placeholder="15" | |
| label={t("duration")} | |
| list={durationListId} | |
| {...register("length", { valueAsNumber: true })} | |
| addOnSuffix={t("minutes")} | |
| /> | |
| min={MIN_EVENT_DURATION_MINUTES} | |
| max={MAX_EVENT_DURATION_MINUTES} | |
| placeholder="15" | |
| label={t("duration")} | |
| list={durationListId} | |
| {...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").toLowerCase()} |
Note: You will need to import MIN_EVENT_DURATION_MINUTES and MAX_EVENT_DURATION_MINUTES from @calcom/lib/constants in this file.
Context for Agents
[DECOMPOSED: Pattern Consistency] The duration field in `DuplicateDialog.tsx` is inconsistent with `CreateEventTypeForm.tsx` and `EventSetupTab.tsx`. It uses a hardcoded `min="1"` and is missing the `max` constraint and validation messages in the `register` call. For consistency and to ensure the same validation rules apply when duplicating an event type, use the shared constants and validation logic.
```suggestion
min={MIN_EVENT_DURATION_MINUTES}
max={MAX_EVENT_DURATION_MINUTES}
placeholder="15"
label={t("duration")}
list={durationListId}
{...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").toLowerCase()}
```
Note: You will need to import `MIN_EVENT_DURATION_MINUTES` and `MAX_EVENT_DURATION_MINUTES` from `@calcom/lib/constants` in this file.
File: apps/web/modules/event-types/components/DuplicateDialog.tsx
Line: 188| <datalist id={durationListId}> | ||
| {DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => ( | ||
| <option key={minutes} value={minutes} /> | ||
| ))} | ||
| </datalist> |
There was a problem hiding this comment.
[Maintainability] [DECOMPOSED: Pattern Consistency] [CodeDuplication] The datalist rendering logic for duration presets is duplicated across CreateEventTypeForm.tsx, DuplicateDialog.tsx, and EventSetupTab.tsx. To follow the DRY principle and ensure consistency, consider abstracting this into a small reusable component (e.g., DurationDatalist) that takes the id as a prop and uses the shared DEFAULT_EVENT_DURATION_OPTIONS constant.
Context for Agents
[DECOMPOSED: Pattern Consistency] [CodeDuplication] The `datalist` rendering logic for duration presets is duplicated across `CreateEventTypeForm.tsx`, `DuplicateDialog.tsx`, and `EventSetupTab.tsx`. To follow the DRY principle and ensure consistency, consider abstracting this into a small reusable component (e.g., `DurationDatalist`) that takes the `id` as a prop and uses the shared `DEFAULT_EVENT_DURATION_OPTIONS` constant.
File: apps/web/modules/event-types/components/CreateEventTypeForm.tsx
Line: 157| <datalist id={durationListId}> | ||
| {DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => ( | ||
| <option key={minutes} value={minutes} /> | ||
| ))} | ||
| </datalist> |
There was a problem hiding this comment.
[Logic] [DECOMPOSED: File Group] The <datalist> markup and the logic to map over DEFAULT_EVENT_DURATION_OPTIONS are duplicated across CreateEventTypeForm.tsx, DuplicateDialog.tsx, and EventSetupTab.tsx. Consider extracting this into a reusable component (e.g., DurationDatalist) to improve maintainability and ensure consistency if the rendering logic needs to change in the future.
// apps/web/modules/event-types/components/DurationDatalist.tsx
import { DEFAULT_EVENT_DURATION_OPTIONS } from "./durationOptions";
export const DurationDatalist = ({ id }: { id: string }) => (
<datalist id={id}>
{DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => (
<option key={minutes} value={minutes} />
))}
</datalist>
);Context for Agents
[DECOMPOSED: File Group] The `<datalist>` markup and the logic to map over `DEFAULT_EVENT_DURATION_OPTIONS` are duplicated across `CreateEventTypeForm.tsx`, `DuplicateDialog.tsx`, and `EventSetupTab.tsx`. Consider extracting this into a reusable component (e.g., `DurationDatalist`) to improve maintainability and ensure consistency if the rendering logic needs to change in the future.
```tsx
// apps/web/modules/event-types/components/DurationDatalist.tsx
import { DEFAULT_EVENT_DURATION_OPTIONS } from "./durationOptions";
export const DurationDatalist = ({ id }: { id: string }) => (
<datalist id={id}>
{DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => (
<option key={minutes} value={minutes} />
))}
</datalist>
);
```
File: apps/web/modules/event-types/components/CreateEventTypeForm.tsx
Line: 157| <datalist id={durationListId}> | ||
| {DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => ( | ||
| <option key={minutes} value={minutes} /> | ||
| ))} | ||
| </datalist> |
There was a problem hiding this comment.
[Maintainability] [DECOMPOSED: Pattern Consistency] [CodeDuplication] The datalist markup for duration presets is identical across EventSetupTab.tsx, CreateEventTypeForm.tsx, and DuplicateDialog.tsx. Consider extracting this into a reusable component (e.g., DurationDatalist) within apps/web/modules/event-types/components/durationOptions.ts (renamed to .tsx) to centralize the logic and the DEFAULT_EVENT_DURATION_OPTIONS constant.
Context for Agents
[DECOMPOSED: Pattern Consistency] [CodeDuplication] The `datalist` markup for duration presets is identical across `EventSetupTab.tsx`, `CreateEventTypeForm.tsx`, and `DuplicateDialog.tsx`. Consider extracting this into a reusable component (e.g., `DurationDatalist`) within `apps/web/modules/event-types/components/durationOptions.ts` (renamed to `.tsx`) to centralize the logic and the `DEFAULT_EVENT_DURATION_OPTIONS` constant.
File: apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx
Line: 321
Shared duration presets with 120‑minute option for event type forms
This PR introduces a shared set of event duration presets and wires them into the event type creation, duplication, and setup forms using HTML
datalistsuggestions. It adds a 120‑minute preset and keeps existing min/max validation and multi‑duration behavior intact, while relying onuseIdto safely associate eachTextFieldwith its correspondingdatalist.The duration presets are centralized in a new
DEFAULT_EVENT_DURATION_OPTIONSconstant and rendered alongside the existing numeric inputs, allowing users to either pick a common duration or enter any valid custom value. Validation logic remains unchanged inCreateEventTypeFormandEventSetupTab, whileDuplicateDialogstill uses a simplermin="1"constraint.Key Changes:
• Added
DEFAULT_EVENT_DURATION_OPTIONS = [15, 30, 45, 60, 90, 120]inapps/web/modules/event-types/components/durationOptions.tsto centralize duration presets, including the new 120‑minute option.• Updated
apps/web/modules/event-types/components/CreateEventTypeForm.tsxto generate adurationListIdviauseId, passlist={durationListId}to the durationTextField, and render a matchingdatalistpopulated fromDEFAULT_EVENT_DURATION_OPTIONS.• Updated
apps/web/modules/event-types/components/DuplicateDialog.tsxto generate adurationListIdviauseId, wire it to the durationTextFieldvia thelistprop, and render adatalistusingDEFAULT_EVENT_DURATION_OPTIONS(still usingmin="1"and simplevalueAsNumbervalidation).• Updated the single-duration branch of
apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsxto useuseIdfordurationListId, connect the durationTextFieldvialist={durationListId}, and render a correspondingdatalistofDEFAULT_EVENT_DURATION_OPTIONSwhile preserving existing min/max validation.• Ensured JSX validity in
EventSetupTabby wrapping theTextFieldanddatalistin a fragment in the ternary branch, resolving the previously flagged adjacent-element syntax issue.Possible Issues:
• If the
TextFieldcomponent does not propagate thelistattribute to the underlyinginput, users will not see anydatalistsuggestions despite the markup being rendered.• If
MAX_EVENT_DURATION_MINUTESis configured below 120, users would see 120 in the presets but encounter a validation error when selecting it inCreateEventTypeFormorEventSetupTab.• Validation rules for the duration field are inconsistent:
CreateEventTypeFormandEventSetupTabenforceMIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTESwith localized error messages, whileDuplicateDialogonly enforcesmin="1"without max or custom messages.• The
datalistrendering code is duplicated across three components, which can increase maintenance cost if duration presets change in the future and a spot is missed.• Browsers with limited or no
datalistsupport will not show suggestions; while behavior gracefully degrades to manual numeric entry, this might make the new 120‑minute preset less discoverable for some users.Review Focus
• Confirm that the
TextFieldimplementation passes through thelistattribute to the underlyinginput, otherwise the rendereddatalistelements will have no effect.• Verify that all values in
DEFAULT_EVENT_DURATION_OPTIONS(especially 120) are within the configuredMIN_EVENT_DURATION_MINUTESandMAX_EVENT_DURATION_MINUTESand align with any server-side constraints for thelengthfield.• Check
DuplicateDialog.tsxfor consistency withCreateEventTypeForm.tsxandEventSetupTab.tsx— consider adopting the sameMIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTESimports andregister-level validation to avoid divergences in allowed durations.• Evaluate whether the repeated
datalistmapping logic across the three components should be refactored into a small reusable component (e.g.,DurationDatalist) that consumesDEFAULT_EVENT_DURATION_OPTIONSand anidprop, to reduce duplication.• Confirm that
useIdusage in all three components is compatible with the app’s React version and SSR setup to avoid hydration mismatches (especially if these forms can render server-side).Potential Impact
Functionality: Users will now see suggested duration values (15, 30, 45, 60, 90, 120) when setting event lengths in the create, duplicate, and single-duration setup flows via HTML
datalist. They can still enter arbitrary values, withCreateEventTypeFormandEventSetupTabenforcingMIN_EVENT_DURATION_MINUTESandMAX_EVENT_DURATION_MINUTESas before.DuplicateDialogcontinues to only enforcemin="1", so its validation behavior remains slightly less strict/consistent unless updated.Performance: The performance impact is negligible: each form now maps over a small fixed-size array to render
optionelements in adatalist. No new network calls or heavy computations are introduced.Security: No security-related logic is touched. Changes are limited to client-side form rendering and constants; server-side validation and authorization are unaffected.
Scalability: Scalability is effectively unchanged. The additional constant and a handful of
optionelements per form do not meaningfully affect rendering cost or system capacity.Affected Areas
• apps/web/modules/event-types/components/durationOptions.ts
• apps/web/modules/event-types/components/CreateEventTypeForm.tsx
• apps/web/modules/event-types/components/DuplicateDialog.tsx
• apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx
• Event type creation, duplication, and setup duration inputs in the web UI
Code Quality Assessment
apps/web/modules/event-types/components/durationOptions.ts: Simple and clear: defines a single shared
DEFAULT_EVENT_DURATION_OPTIONSarray. Consider later evolving this into a.tsxmodule exporting a smallDurationDatalistcomponent to remove duplicated markup elsewhere.apps/web/modules/event-types/components/CreateEventTypeForm.tsx: Integrates the
datalistcleanly with minimal changes and keeps existing validation usingMIN_EVENT_DURATION_MINUTESandMAX_EVENT_DURATION_MINUTES. ThedurationListIdis scoped to the component and correctly used both in theTextFieldlistprop anddatalistid.apps/web/modules/event-types/components/DuplicateDialog.tsx: Wires
useIdand thedatalistsimilarly to the other forms, but currently uses a simpler validation (min="1"andvalueAsNumber) instead of the sharedMIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTESpattern, which may be worth aligning for consistency.apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx: Adds
durationListIdand thedatalistin the single-duration branch without affecting the more complex multi-duration logic. JSX structure has been corrected by wrappingTextFieldplusdatalistin a fragment, and existing validation and managed-event-type locking behavior remain intact.Best Practices
Validation:
• Use shared constants like
MIN_EVENT_DURATION_MINUTESandMAX_EVENT_DURATION_MINUTESin all related forms to keep client-side and server-side constraints aligned.• Surface validation errors through localized messages in the
registerconfiguration so users receive consistent feedback across all flows.Code Reuse:
• Centralize shared configuration like duration presets in a constant such as
DEFAULT_EVENT_DURATION_OPTIONSto maintain consistency across forms.• Consider extracting the repeated
datalistmarkup into a small reusable component (e.g.,DurationDatalist) that consumes anidand usesDEFAULT_EVENT_DURATION_OPTIONS.Frontend:
• Use HTML
datalistcombined with thelistattribute oninput/TextFieldto provide suggested values while still allowing free-form user input.• Generate stable, unique IDs for associated elements (e.g.,
TextFieldanddatalist) usinguseIdto avoid collisions and potential SSR hydration issues.Testing Needed
• In
CreateEventTypeForm, focus the durationTextFieldand verify thedatalistsuggestions show 15, 30, 45, 60, 90, and 120; confirm selecting 120 results in a created event whoselengthis persisted as 120 and passes client/server validation.• In
DuplicateDialog, open the duplicate event type flow and confirm the duration input shows the same suggestions and that saving with 120 minutes creates a valid duplicate (verifylengthin the resulting event type).• In
EventSetupTab, withmultipleDurationdisabled, ensure the durationTextFieldshows thedatalistpresets, still enforcesMIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTES, and that error messages for out-of-range values remain unchanged.• Toggle
multipleDurationon and off inEventSetupTaband verify thedatalistonly appears in the single-duration branch and does not interfere with the multi-durationSelectlogic or default-duration selection.• Verify that the shared
TextFieldcomponent correctly forwards thelistprop down to the underlyinginputso that browsers actually use thedatalistsuggestions.• Optionally, regression-test older or less common browsers to ensure that, if
datalistis unsupported, manual numeric entry still works as before in all three flows.This summary was automatically generated by @propel-code-bot