Skip to content

Add 120 min meeting length option - #1

Open
cvillere wants to merge 2 commits into
mainfrom
christian-branch
Open

Add 120 min meeting length option#1
cvillere wants to merge 2 commits into
mainfrom
christian-branch

Conversation

@cvillere

@cvillere cvillere commented Jan 20, 2026

Copy link
Copy Markdown

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 datalist suggestions. It adds a 120‑minute preset and keeps existing min/max validation and multi‑duration behavior intact, while relying on useId to safely associate each TextField with its corresponding datalist.

The duration presets are centralized in a new DEFAULT_EVENT_DURATION_OPTIONS constant 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 in CreateEventTypeForm and EventSetupTab, while DuplicateDialog still uses a simpler min="1" constraint.

Key Changes:
• Added DEFAULT_EVENT_DURATION_OPTIONS = [15, 30, 45, 60, 90, 120] in apps/web/modules/event-types/components/durationOptions.ts to centralize duration presets, including the new 120‑minute option.
• Updated apps/web/modules/event-types/components/CreateEventTypeForm.tsx to generate a durationListId via useId, pass list={durationListId} to the duration TextField, and render a matching datalist populated from DEFAULT_EVENT_DURATION_OPTIONS.
• Updated apps/web/modules/event-types/components/DuplicateDialog.tsx to generate a durationListId via useId, wire it to the duration TextField via the list prop, and render a datalist using DEFAULT_EVENT_DURATION_OPTIONS (still using min="1" and simple valueAsNumber validation).
• Updated the single-duration branch of apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx to use useId for durationListId, connect the duration TextField via list={durationListId}, and render a corresponding datalist of DEFAULT_EVENT_DURATION_OPTIONS while preserving existing min/max validation.
• Ensured JSX validity in EventSetupTab by wrapping the TextField and datalist in a fragment in the ternary branch, resolving the previously flagged adjacent-element syntax issue.

Possible Issues:
• If the TextField component does not propagate the list attribute to the underlying input, users will not see any datalist suggestions despite the markup being rendered.
• If MAX_EVENT_DURATION_MINUTES is configured below 120, users would see 120 in the presets but encounter a validation error when selecting it in CreateEventTypeForm or EventSetupTab.
• Validation rules for the duration field are inconsistent: CreateEventTypeForm and EventSetupTab enforce MIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTES with localized error messages, while DuplicateDialog only enforces min="1" without max or custom messages.
• The datalist rendering 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 datalist support 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 TextField implementation passes through the list attribute to the underlying input, otherwise the rendered datalist elements will have no effect.
• Verify that all values in DEFAULT_EVENT_DURATION_OPTIONS (especially 120) are within the configured MIN_EVENT_DURATION_MINUTES and MAX_EVENT_DURATION_MINUTES and align with any server-side constraints for the length field.
• Check DuplicateDialog.tsx for consistency with CreateEventTypeForm.tsx and EventSetupTab.tsx — consider adopting the same MIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTES imports and register-level validation to avoid divergences in allowed durations.
• Evaluate whether the repeated datalist mapping logic across the three components should be refactored into a small reusable component (e.g., DurationDatalist) that consumes DEFAULT_EVENT_DURATION_OPTIONS and an id prop, to reduce duplication.
• Confirm that useId usage 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, with CreateEventTypeForm and EventSetupTab enforcing MIN_EVENT_DURATION_MINUTES and MAX_EVENT_DURATION_MINUTES as before. DuplicateDialog continues to only enforce min="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 option elements in a datalist. 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 option elements 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_OPTIONS array. Consider later evolving this into a .tsx module exporting a small DurationDatalist component to remove duplicated markup elsewhere.

apps/web/modules/event-types/components/CreateEventTypeForm.tsx: Integrates the datalist cleanly with minimal changes and keeps existing validation using MIN_EVENT_DURATION_MINUTES and MAX_EVENT_DURATION_MINUTES. The durationListId is scoped to the component and correctly used both in the TextField list prop and datalist id.

apps/web/modules/event-types/components/DuplicateDialog.tsx: Wires useId and the datalist similarly to the other forms, but currently uses a simpler validation (min="1" and valueAsNumber) instead of the shared MIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTES pattern, which may be worth aligning for consistency.

apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx: Adds durationListId and the datalist in the single-duration branch without affecting the more complex multi-duration logic. JSX structure has been corrected by wrapping TextField plus datalist in a fragment, and existing validation and managed-event-type locking behavior remain intact.

Best Practices

Validation:
• Use shared constants like MIN_EVENT_DURATION_MINUTES and MAX_EVENT_DURATION_MINUTES in all related forms to keep client-side and server-side constraints aligned.
• Surface validation errors through localized messages in the register configuration so users receive consistent feedback across all flows.

Code Reuse:
• Centralize shared configuration like duration presets in a constant such as DEFAULT_EVENT_DURATION_OPTIONS to maintain consistency across forms.
• Consider extracting the repeated datalist markup into a small reusable component (e.g., DurationDatalist) that consumes an id and uses DEFAULT_EVENT_DURATION_OPTIONS.

Frontend:
• Use HTML datalist combined with the list attribute on input/TextField to provide suggested values while still allowing free-form user input.
• Generate stable, unique IDs for associated elements (e.g., TextField and datalist) using useId to avoid collisions and potential SSR hydration issues.

Testing Needed

• In CreateEventTypeForm, focus the duration TextField and verify the datalist suggestions show 15, 30, 45, 60, 90, and 120; confirm selecting 120 results in a created event whose length is 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 (verify length in the resulting event type).
• In EventSetupTab, with multipleDuration disabled, ensure the duration TextField shows the datalist presets, still enforces MIN_EVENT_DURATION_MINUTES/MAX_EVENT_DURATION_MINUTES, and that error messages for out-of-range values remain unchanged.
• Toggle multipleDuration on and off in EventSetupTab and verify the datalist only appears in the single-duration branch and does not interfere with the multi-duration Select logic or default-duration selection.
• Verify that the shared TextField component correctly forwards the list prop down to the underlying input so that browsers actually use the datalist suggestions.
• Optionally, regression-test older or less common browsers to ensure that, if datalist is unsupported, manual numeric entry still works as before in all three flows.


This summary was automatically generated by @propel-code-bot

@cvillere cvillere closed this Jan 20, 2026
@cvillere cvillere reopened this Jan 20, 2026

@propel-code-bot propel-code-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Propel found 4 suggestion(s) in this PR

Comment thread apps/web/modules/event-types/components/tabs/setup/EventSetupTab.tsx Outdated
@@ -0,0 +1 @@
export const DEFAULT_EVENT_DURATION_OPTIONS = [15, 30, 45, 60, 90, 120];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended

[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

Comment on lines 182 to 188
min="1"
placeholder="15"
label={t("duration")}
list={durationListId}
{...register("length", { valueAsNumber: true })}
addOnSuffix={t("minutes")}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advisory

[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.

Suggested change
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

Comment on lines +153 to +157
<datalist id={durationListId}>
{DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => (
<option key={minutes} value={minutes} />
))}
</datalist>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advisory

[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

@propel-code-bot propel-code-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Propel found 2 suggestion(s) in this PR

Comment on lines +153 to +157
<datalist id={durationListId}>
{DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => (
<option key={minutes} value={minutes} />
))}
</datalist>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended

[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

Comment on lines +317 to +321
<datalist id={durationListId}>
{DEFAULT_EVENT_DURATION_OPTIONS.map((minutes) => (
<option key={minutes} value={minutes} />
))}
</datalist>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advisory

[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

@propel-code-bot propel-code-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, ship it! 🚢

Details
Why was this auto-approved?
APPROVED: 5 non-blocking comment(s) (Recommended/Advisory) can be addressed later - Relaxed policy satisfied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant