Skip to content

feat: add booking question field types to routing forms#28929

Open
thegreatalxx wants to merge 1 commit intocalcom:mainfrom
thegreatalxx:cal-routing-forms-fix
Open

feat: add booking question field types to routing forms#28929
thegreatalxx wants to merge 1 commit intocalcom:mainfrom
thegreatalxx:cal-routing-forms-fix

Conversation

@thegreatalxx
Copy link
Copy Markdown

Adds new booking question field types to the routing forms editor. Fixes #28009.

@github-actions
Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @thegreatalxx! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

This pull request extends the routing forms system with six new field types: ADDRESS, MULTIEMAIL, CHECKBOX, RADIO, BOOLEAN, and URL. The RoutingFormFieldType enum is changed from a const enum to a standard enum to enable runtime access. New widget components and configurations are implemented for each field type. Form submission handling, insights column generation, and query builder configuration are updated to recognize and process these field types appropriately. The form editor UI is modified to display options configuration for checkbox and radio fields. Type detection logic in the insights module is broadened to classify the new field types for proper data transformation and rendering.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main objective: adding new booking question field types to routing forms.
Description check ✅ Passed The description is directly related to the changeset, clearly stating the addition of booking question field types and referencing the resolved issue.
Linked Issues check ✅ Passed All primary objectives from issue #28009 are met: six new field types added (Address, Multiple Emails, Checkbox Group, Radio Group, Boolean, URL) with proper widget architecture, query builder config updates, response transformation logic, form submission utilities, insights filtering, and routing form editor UI updates.
Out of Scope Changes check ✅ Passed All code changes directly support adding the new field types to routing forms. Import reorganizations and test updates are necessary supporting changes for the core feature implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch cal-routing-forms-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/app-store/routing-forms/lib/transformResponse.ts (1)

61-76: ⚠️ Potential issue | 🟠 Major

Boolean false never reaches the new boolean branch — it falls into the !value early return.

The guard at the top:

if (!value) {
  return "";
}

short-circuits for false, 0, and "". That means a boolean field whose answer is literally false will be returned as "" for jsonLogic evaluation, so routing rules like boolean_field == false will never match. The newly added boolean branch below only handles true / stringified values.

Consider handling boolean before the truthy-check, or narrowing the early return to undefined/null:

Proposed fix
 export function getFieldResponseForJsonLogic({
   field,
   value,
 }: {
   field: Pick<Field, "options" | "type">;
   value: FormResponse[string]["value"] | undefined;
 }) {
-  if (!value) {
+  if (field.type === "boolean") {
+    if (value === undefined || value === null || value === "") return "";
+    if (typeof value === "string") return value;
+    return String(value);
+  }
+  if (!value) {
     return "";
   }

Please also confirm how false values are actually produced upstream (form submission), since this determines the correct canonical shape ("false" vs false) for jsonLogic comparisons.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-store/routing-forms/lib/transformResponse.ts` around lines 61 -
76, The early falsy guard returns "" for false/0 which breaks boolean handling;
update the check to only bail on null/undefined (use if (value == null) return
"") or move the boolean branch (field.type === "boolean") above the falsy guard
so false values reach the boolean handling; ensure the boolean branch
converts/returns the canonical shape you expect for jsonLogic (handle both
string and boolean forms) and locate changes around the value variable and the
field.type === "boolean" case in transformResponse.ts.
packages/features/insights/server/routing-events.ts (1)

129-156: ⚠️ Potential issue | 🔴 Critical

CSV export doesn't handle the new field types — values for checkbox, radio, and boolean will be wrong or empty.

getRoutingFormHeaders now emits headers whose type can be "checkbox" or "radio" (and potentially "boolean", "address", "multiemail", "url" for non-option fields). In the CSV assembly below, only "select", "multiselect", and "number" are special-cased; everything else falls into field.valueString || "":

  • checkbox responses are stored in valueStringArray (array of option ids) → downloaded cells will be empty.
  • radio responses are stored in valueString as an option id → the raw UUID will be exported instead of the human-readable option label (unlike select, which is resolved through header.options).
  • boolean will at least render as a string, but worth confirming.

Please extend the branches to mirror the new types, e.g.:

Proposed fix
-          if (header.type === "select") {
+          if (header.type === "select" || header.type === "radio") {
             acc[header.label] = header.options?.find((option) => option.id === field.valueString)?.label;
-          } else if (header.type === "multiselect" && Array.isArray(field.valueStringArray)) {
+          } else if (
+            (header.type === "multiselect" || header.type === "checkbox") &&
+            Array.isArray(field.valueStringArray)
+          ) {
             acc[header.label] = field.valueStringArray
               .map((value) => header.options?.find((option) => option.id === value)?.label)
               .filter((label): label is string => label !== undefined)
               .sort()
               .join(", ");
           } else if (header.type === "number") {
             acc[header.label] = field.valueNumber?.toString() || "";
           } else {
             acc[header.label] = field.valueString || "";
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/features/insights/server/routing-events.ts` around lines 129 - 156,
The CSV assembly in dataWithFlatResponse's reducer only handles "select",
"multiselect", and "number", so new header types like "checkbox", "radio", and
"boolean" produce wrong or empty cells; update the reducer (the mapping inside
dataWithFlatResponse that iterates headers and inspects header.type and field)
to: treat "checkbox" like "multiselect" (read field.valueStringArray, map option
ids to header.options labels, filter/sort/join), treat "radio" like "select"
(resolve field.valueString via header.options to the option label), handle
"boolean" by reading field.valueBoolean?.toString() or rendering "" when
undefined, and for other multi-value types like "multiemail" also join
valueStringArray; keep existing fallbacks for "number" and default string
fields. Ensure you reference the header.options lookups and
field.valueString/field.valueStringArray/field.valueBoolean in the same reducer
so CSV labels are human-readable.
🧹 Nitpick comments (5)
packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts (1)

415-423: boolean type config looks minimal — verify operator support is sufficient.

Only equal/not_equal are supported with no is_empty/is_not_empty. For optional boolean questions (where the answer may be missing), users may want to route on "answered" vs "not answered". Consider whether that matters for the routing rule UX; low priority if the product only needs strict true/false matching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts`
around lines 415 - 423, The boolean type config in BasicConfig.ts currently only
allows "equal" and "not_equal" which prevents routing on unanswered optional
booleans; update the boolean config (the boolean object containing
defaultOperator, mainWidget and widgets.boolean) to include "is_empty" and
"is_not_empty" in widgets.boolean.operators (and adjust defaultOperator if
needed for your UX) so rules can detect answered vs not answered for optional
boolean questions.
apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx (1)

206-221: Prefer the now-runtime RoutingFormFieldType enum over string literals.

Since RoutingFormFieldType is no longer a const enum, consumers can use the enum values directly, which keeps this check in sync if identifiers ever change.

-          {["select", "multiselect", "checkbox", "radio"].includes(fieldType) ? (
+          {[
+            RoutingFormFieldType.SINGLE_SELECT,
+            RoutingFormFieldType.MULTI_SELECT,
+            RoutingFormFieldType.CHECKBOX,
+            RoutingFormFieldType.RADIO,
+          ].includes(fieldType as RoutingFormFieldType) ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/`(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx
around lines 206 - 221, Replace the string-literal type check with the runtime
enum to keep it in sync: update the conditional that checks fieldType in
FormEdit (the expression currently using ["select", "multiselect", "checkbox",
"radio"].includes(fieldType)) to use the RoutingFormFieldType enum values
instead (e.g., RoutingFormFieldType.Select, RoutingFormFieldType.MultiSelect,
RoutingFormFieldType.Checkbox, RoutingFormFieldType.Radio) so the
MultiOptionInput block is shown based on enum members rather than hard-coded
strings.
packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts (1)

7-31: Mock coverage looks good, but consider adding positive tests for the new types.

The widget mock is extended for all new types, but the existing test fixture mockForm (lines 34-54) still only exercises text, select, and multiselect. Adding at least one assertion that a checkbox/radio field produces listValues populated from options — and that a boolean/address/url/multiemail field has listValues: undefined — would lock in the new behavior in getQueryBuilderConfig.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts`
around lines 7 - 31, Update the test fixture and add positive assertions: extend
the existing mockForm used in getQueryBuilderConfig.test.ts to include at least
one field with type "checkbox" or "radio" (with options) and one or more fields
with types "boolean", "address", "url", or "multiemail"; then call
getQueryBuilderConfig and assert that the checkbox/radio field's resulting
config contains listValues populated from its options, while the
boolean/address/url/multiemail fields have listValues === undefined; use the
existing mocked FormFieldsInitialConfig and the mockForm variable to locate
where to add these cases and assertions.
packages/app-store/routing-forms/lib/FieldTypes.ts (1)

80-87: Confusing labels: BOOLEAN → "Checkbox" vs CHECKBOX → "Checkbox Group".

Two field types surface very similar-looking labels in the dropdown ("Checkbox" and "Checkbox Group"). Users adding a question are likely to pick the wrong one. Consider a clearer disambiguator (e.g., "Yes/No" / "Toggle" for BOOLEAN, "Checkboxes" for CHECKBOX). Also, UI strings here are hardcoded English — if i18n is expected for the editor, these should route through packages/i18n/locales/en/common.json and t(...) like other editor strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-store/routing-forms/lib/FieldTypes.ts` around lines 80 - 87, The
dropdown labels for RoutingFormFieldType.BOOLEAN and
RoutingFormFieldType.CHECKBOX are confusingly similar; update the label for
RoutingFormFieldType.BOOLEAN (in FieldTypes.ts) to a clearer term such as
"Yes/No" or "Toggle" and change RoutingFormFieldType.CHECKBOX to "Checkboxes"
(or similar plural) to disambiguate, and replace hardcoded English strings by
routing both labels through the i18n helper (t(...)) and adding the new keys to
packages/i18n/locales/en/common.json so the editor uses localized strings
consistently.
packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts (1)

158-171: Attribute side: CHECKBOX/RADIO aren't in attributeTypesMap — is this code path reachable?

attributeTypesMap (lines 31-36) only maps SINGLE_SELECT, MULTI_SELECT, TEXT, NUMBER. transformAttributesToCompatibleFormat throws for anything else, so attributeType here can never be "checkbox" or "radio" in practice. The added branches in isAttributeWithOptions are dead code today — fine to leave for future-proofing, but worth a short comment noting the intent, or simply keep the original select/multiselect check here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts` around lines
158 - 171, The new isAttributeWithOptions check includes "checkbox" and "radio"
but attributeType is derived via attributeTypesMap and
transformAttributesToCompatibleFormat which only produce "select",
"multiselect", "text", "number", so "checkbox"/"radio" are unreachable; either
remove them to restore the original select/multiselect check or add a short
comment near isAttributeWithOptions explaining this is future-proofing for
potential new mappings (and keep the listValues behavior unchanged in
fields[attribute.id] to ensure RAQB only gets listValues for real
select/multiselect types).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts`:
- Around line 54-91: getWidgetsWithoutFactory is missing entries for the custom
field types defined in getTypes (checkbox and radio), causing widgets[fieldType]
to be undefined in getQueryBuilderConfig; add entries for "checkbox" and "radio"
to getWidgetsWithoutFactory so they point to the corresponding base widgets
(checkbox -> BasicConfig.widgets.multiselect, radio ->
BasicConfig.widgets.select) so lookup returns a valid widget config and avoids
the runtime crash in getQueryBuilderConfig when accessing
widgets[fieldType].type.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx`:
- Around line 141-155: The three user-facing placeholders in uiConfig.tsx
(address.valuePlaceholder "Enter Address", url.valuePlaceholder "Enter URL", and
multiemail.valuePlaceholder "Enter email addresses") must be replaced with i18n
keys and corresponding entries added to the English locale; update the address,
url, and multiemail widget configs to use i18n lookups (e.g., via t('...') or
the project's translation helper) referencing new keys (e.g.,
query.placeholder.address, query.placeholder.url, query.placeholder.multiemail)
near AddressFactory, URLFactory, and MultiEmailFactory, and add those keys and
English strings to packages/i18n/locales/en/common.json.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx`:
- Line 249: Replace hardcoded UI strings in widgets.tsx (the placeholder prop
value "Enter address" and the "Yes"/"No" labels used elsewhere) with i18n
lookups: add new keys to packages/i18n/locales/en/common.json (e.g.,
"widgets.placeholderAddress", "widgets.yes", "widgets.no") and call the
translation function used across the app (e.g., useTranslation()/t) inside the
component to supply placeholder and label values instead of the literals; update
the JSX references to use t('widgets.placeholderAddress') and
t('widgets.yes')/t('widgets.no') for the occurrences currently using placeholder
and the Yes/No labels (including the instances around the symbols placeholder
and the label props in widgets.tsx).
- Around line 304-324: The early return based on listValues is placed before a
hook, breaking hook order; move the conditional return so hooks run
unconditionally. Specifically, inside CheckboxGroupWidget keep computing
currentValues and declaring the useCallback handleChange (which references
currentValues and setValue) every render, then after that check if (!listValues)
return null; this preserves hook order for useCallback and avoids conditional
hook execution.
- Around line 281-302: The MultiEmailWidget currently renders TextField with
type="email" which rejects comma-separated addresses; update the widget to
either use type="text" or enable multiple emails by adding the multiple
attribute to the rendered input. Locate the MultiEmailWidget function and change
the TextField prop from type="email" to type="text" if you want raw
comma-separated input, or add multiple (e.g., include multiple={true} in the
TextField props or spread it via customProps) so native email validation accepts
comma-separated addresses; keep placeholder and other props unchanged.

---

Outside diff comments:
In `@packages/app-store/routing-forms/lib/transformResponse.ts`:
- Around line 61-76: The early falsy guard returns "" for false/0 which breaks
boolean handling; update the check to only bail on null/undefined (use if (value
== null) return "") or move the boolean branch (field.type === "boolean") above
the falsy guard so false values reach the boolean handling; ensure the boolean
branch converts/returns the canonical shape you expect for jsonLogic (handle
both string and boolean forms) and locate changes around the value variable and
the field.type === "boolean" case in transformResponse.ts.

In `@packages/features/insights/server/routing-events.ts`:
- Around line 129-156: The CSV assembly in dataWithFlatResponse's reducer only
handles "select", "multiselect", and "number", so new header types like
"checkbox", "radio", and "boolean" produce wrong or empty cells; update the
reducer (the mapping inside dataWithFlatResponse that iterates headers and
inspects header.type and field) to: treat "checkbox" like "multiselect" (read
field.valueStringArray, map option ids to header.options labels,
filter/sort/join), treat "radio" like "select" (resolve field.valueString via
header.options to the option label), handle "boolean" by reading
field.valueBoolean?.toString() or rendering "" when undefined, and for other
multi-value types like "multiemail" also join valueStringArray; keep existing
fallbacks for "number" and default string fields. Ensure you reference the
header.options lookups and
field.valueString/field.valueStringArray/field.valueBoolean in the same reducer
so CSV labels are human-readable.

---

Nitpick comments:
In `@apps/web/app/`(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx:
- Around line 206-221: Replace the string-literal type check with the runtime
enum to keep it in sync: update the conditional that checks fieldType in
FormEdit (the expression currently using ["select", "multiselect", "checkbox",
"radio"].includes(fieldType)) to use the RoutingFormFieldType enum values
instead (e.g., RoutingFormFieldType.Select, RoutingFormFieldType.MultiSelect,
RoutingFormFieldType.Checkbox, RoutingFormFieldType.Radio) so the
MultiOptionInput block is shown based on enum members rather than hard-coded
strings.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts`:
- Around line 415-423: The boolean type config in BasicConfig.ts currently only
allows "equal" and "not_equal" which prevents routing on unanswered optional
booleans; update the boolean config (the boolean object containing
defaultOperator, mainWidget and widgets.boolean) to include "is_empty" and
"is_not_empty" in widgets.boolean.operators (and adjust defaultOperator if
needed for your UX) so rules can detect answered vs not answered for optional
boolean questions.

In
`@packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts`:
- Around line 7-31: Update the test fixture and add positive assertions: extend
the existing mockForm used in getQueryBuilderConfig.test.ts to include at least
one field with type "checkbox" or "radio" (with options) and one or more fields
with types "boolean", "address", "url", or "multiemail"; then call
getQueryBuilderConfig and assert that the checkbox/radio field's resulting
config contains listValues populated from its options, while the
boolean/address/url/multiemail fields have listValues === undefined; use the
existing mocked FormFieldsInitialConfig and the mockForm variable to locate
where to add these cases and assertions.

In `@packages/app-store/routing-forms/lib/FieldTypes.ts`:
- Around line 80-87: The dropdown labels for RoutingFormFieldType.BOOLEAN and
RoutingFormFieldType.CHECKBOX are confusingly similar; update the label for
RoutingFormFieldType.BOOLEAN (in FieldTypes.ts) to a clearer term such as
"Yes/No" or "Toggle" and change RoutingFormFieldType.CHECKBOX to "Checkboxes"
(or similar plural) to disambiguate, and replace hardcoded English strings by
routing both labels through the i18n helper (t(...)) and adding the new keys to
packages/i18n/locales/en/common.json so the editor uses localized strings
consistently.

In `@packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts`:
- Around line 158-171: The new isAttributeWithOptions check includes "checkbox"
and "radio" but attributeType is derived via attributeTypesMap and
transformAttributesToCompatibleFormat which only produce "select",
"multiselect", "text", "number", so "checkbox"/"radio" are unreachable; either
remove them to restore the original select/multiselect check or add a short
comment near isAttributeWithOptions explaining this is future-proofing for
potential new mappings (and keep the listValues behavior unchanged in
fields[attribute.id] to ensure RAQB only gets listValues for real
select/multiselect types).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ab26c8b-296f-4ef5-b812-54457a15e9e4

📥 Commits

Reviewing files that changed from the base of the PR and between 9efd0e6 and 97307eb.

📒 Files selected for processing (14)
  • apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx
  • apps/web/modules/insights/hooks/useInsightsColumns.tsx
  • packages/app-store/routing-forms/__tests__/config.test.ts
  • packages/app-store/routing-forms/__tests__/uiConfig.test.ts
  • packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts
  • packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts
  • packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx
  • packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx
  • packages/app-store/routing-forms/lib/FieldTypes.ts
  • packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts
  • packages/app-store/routing-forms/lib/formSubmissionUtils.ts
  • packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts
  • packages/app-store/routing-forms/lib/transformResponse.ts
  • packages/features/insights/server/routing-events.ts

Comment on lines +54 to +91
address: {
...BasicConfig.types.text,
widgets: {
...BasicConfig.types.text.widgets,
},
},
url: {
...BasicConfig.types.text,
widgets: {
...BasicConfig.types.text.widgets,
},
},
multiemail: {
...BasicConfig.types.text,
widgets: {
...BasicConfig.types.text.widgets,
},
},
checkbox: {
...BasicConfig.types.multiselect,
widgets: {
...BasicConfig.types.multiselect.widgets,
// Checkbox uses the checkbox widget for rendering but multiselect type for query logic
checkbox: {
...BasicConfig.types.multiselect.widgets.multiselect,
operators: [...multiSelectOperators],
},
},
},
radio: {
...BasicConfig.types.select,
widgets: {
...BasicConfig.types.select.widgets,
radio: {
...BasicConfig.types.select.widgets.select,
},
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect query-builder field configuration for explicit widget/preferWidgets wiring.
rg -n -C5 --type=ts 'preferWidgets|widget:|widgets:|RoutingFormFieldType\.(ADDRESS|URL|MULTIEMAIL|CHECKBOX|RADIO|BOOLEAN)|type: field\.type|field\.type' packages/app-store/routing-forms

Repository: calcom/cal.diy

Length of output: 44136


🏁 Script executed:

# Find and examine getWidgetsWithoutFactory implementation
rg -n "getWidgetsWithoutFactory" packages/app-store/routing-forms/components/react-awesome-query-builder/config/ -A 20

Repository: calcom/cal.diy

Length of output: 3871


🏁 Script executed:

# Also check if there's a separate step that adds widgets for new types
rg -n "widgets\[" packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts -B 5 -A 5

Repository: calcom/cal.diy

Length of output: 1286


🏁 Script executed:

# Check if widget being undefined is handled anywhere
sed -n '54,65p' packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts

Repository: calcom/cal.diy

Length of output: 450


🏁 Script executed:

# Check if checkbox/radio should be in getWidgetsWithoutFactory but are missing
sed -n '9,35p' packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts

Repository: calcom/cal.diy

Length of output: 853


Add checkbox and radio widgets to getWidgetsWithoutFactory.

The checkbox and radio type configurations are defined in getTypes() but their corresponding widget entries are missing from getWidgetsWithoutFactory. This causes getQueryBuilderConfig.ts:57-58 to access undefined when looking up widgets[fieldType] for these types, resulting in a runtime crash: Cannot read property 'type' of undefined.

Add checkbox and radio entries to getWidgetsWithoutFactory:

Widget entries to add
checkbox: {
  ...BasicConfig.widgets.multiselect,
},
radio: {
  ...BasicConfig.widgets.select,
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts`
around lines 54 - 91, getWidgetsWithoutFactory is missing entries for the custom
field types defined in getTypes (checkbox and radio), causing widgets[fieldType]
to be undefined in getQueryBuilderConfig; add entries for "checkbox" and "radio"
to getWidgetsWithoutFactory so they point to the corresponding base widgets
(checkbox -> BasicConfig.widgets.multiselect, radio ->
BasicConfig.widgets.select) so lookup returns a valid widget config and avoids
the runtime crash in getQueryBuilderConfig when accessing
widgets[fieldType].type.

Comment on lines +141 to +155
address: {
...widgets.text,
factory: AddressFactory,
valuePlaceholder: "Enter Address",
},
url: {
...widgets.text,
factory: URLFactory,
valuePlaceholder: "Enter URL",
},
multiemail: {
...widgets.text,
factory: MultiEmailFactory,
valuePlaceholder: "Enter email addresses",
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Translate the new placeholder strings.

Enter Address, Enter URL, and Enter email addresses are user-facing strings and should be backed by i18n keys. As per coding guidelines, **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx`
around lines 141 - 155, The three user-facing placeholders in uiConfig.tsx
(address.valuePlaceholder "Enter Address", url.valuePlaceholder "Enter URL", and
multiemail.valuePlaceholder "Enter email addresses") must be replaced with i18n
keys and corresponding entries added to the English locale; update the address,
url, and multiemail widget configs to use i18n lookups (e.g., via t('...') or
the project's translation helper) referencing new keys (e.g.,
query.placeholder.address, query.placeholder.url, query.placeholder.multiemail)
near AddressFactory, URLFactory, and MultiEmailFactory, and add those keys and
English strings to packages/i18n/locales/en/common.json.

type="text"
value={textValue}
noLabel={noLabel}
placeholder={placeholder || "Enter address"}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Move new user-facing strings into translations.

The new placeholders and Yes/No label should come from i18n instead of hardcoded literals. As per coding guidelines, **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

Also applies to: 272-272, 295-295, 381-381

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx`
at line 249, Replace hardcoded UI strings in widgets.tsx (the placeholder prop
value "Enter address" and the "Yes"/"No" labels used elsewhere) with i18n
lookups: add new keys to packages/i18n/locales/en/common.json (e.g.,
"widgets.placeholderAddress", "widgets.yes", "widgets.no") and call the
translation function used across the app (e.g., useTranslation()/t) inside the
component to supply placeholder and label values instead of the literals; update
the JSX references to use t('widgets.placeholderAddress') and
t('widgets.yes')/t('widgets.no') for the occurrences currently using placeholder
and the Yes/No labels (including the instances around the symbols placeholder
and the label props in widgets.tsx).

Comment on lines +281 to +302
function MultiEmailWidget(props: TextLikeComponentPropsRAQB) {
const { value, noLabel, setValue, readOnly, placeholder, customProps, ...remainingProps } = props;
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
setValue(val);
};
const textValue = value || "";
return (
<TextField
size="sm"
containerClassName="w-full mb-2"
type="email"
value={textValue}
noLabel={noLabel}
placeholder={placeholder || "Enter email addresses (comma-separated)"}
disabled={readOnly}
onChange={onChange}
{...remainingProps}
{...customProps}
/>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the actual file
find . -path "*/routing-forms/components/react-awesome-query-builder/widgets.tsx" -type f

Repository: calcom/cal.diy

Length of output: 143


🏁 Script executed:

# Get the file size first
wc -l packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx

Repository: calcom/cal.diy

Length of output: 145


🏁 Script executed:

# Read the specific lines around the MultiEmailWidget component
sed -n '270,310p' packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx

Repository: calcom/cal.diy

Length of output: 1073


🏁 Script executed:

# Search for TextField component definition to understand its props
fd -t f "TextField" -e ts -e tsx | head -20

Repository: calcom/cal.diy

Length of output: 160


🏁 Script executed:

# Search for TextField imports in the widgets file
head -50 packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx | grep -i textfield

Repository: calcom/cal.diy

Length of output: 123


🏁 Script executed:

# Look for validation logic around MultiEmailWidget
rg "MultiEmailWidget" -A 10 -B 5

Repository: calcom/cal.diy

Length of output: 8481


🏁 Script executed:

# Read the TextField component definition
head -100 packages/ui/components/form/inputs/TextField.tsx

Repository: calcom/cal.diy

Length of output: 2406


🏁 Script executed:

# Get full TextField file to understand its props and implementation
wc -l packages/ui/components/form/inputs/TextField.tsx

Repository: calcom/cal.diy

Length of output: 110


🏁 Script executed:

# Check the entire TextField implementation
cat packages/ui/components/form/inputs/TextField.tsx

Repository: calcom/cal.diy

Length of output: 7449


🏁 Script executed:

# Check if there's any validation for email fields
rg "type.*email" packages/app-store/routing-forms -A 3 -B 3

Repository: calcom/cal.diy

Length of output: 4438


🏁 Script executed:

# Look for validation logic or any processing of email values
rg "multiemail|MultiEmail" -A 5 -B 5 packages/app-store/routing-forms

Repository: calcom/cal.diy

Length of output: 15254


🏁 Script executed:

# Check form submission utils to see if there's comma-separated email handling
head -150 packages/app-store/routing-forms/lib/formSubmissionUtils.ts | tail -100

Repository: calcom/cal.diy

Length of output: 2984


🏁 Script executed:

# Search for any email validation functions
rg "email.*validat|validat.*email" packages/app-store/routing-forms -i

Repository: calcom/cal.diy

Length of output: 40


Change type="email" to type="text" or add multiple attribute.

The placeholder advertises "comma-separated" emails, but type="email" without the multiple attribute treats comma-separated addresses as invalid per the HTML spec. This creates a UX mismatch where users see the hint but the input rejects their input. Either use type="text" for raw comma-separated input or add the multiple attribute to properly support multiple email values with native validation.

Proposed fix
     <TextField
       size="sm"
       containerClassName="w-full mb-2"
       type="email"
+      multiple
       value={textValue}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx`
around lines 281 - 302, The MultiEmailWidget currently renders TextField with
type="email" which rejects comma-separated addresses; update the widget to
either use type="text" or enable multiple emails by adding the multiple
attribute to the rendered input. Locate the MultiEmailWidget function and change
the TextField prop from type="email" to type="text" if you want raw
comma-separated input, or add multiple (e.g., include multiple={true} in the
TextField props or spread it via customProps) so native email validation accepts
comma-separated addresses; keep placeholder and other props unchanged.

Comment on lines +304 to +324
function CheckboxGroupWidget({
listValues,
setValue,
value,
...remainingProps
}: SelectLikeComponentPropsRAQB<string[]>) {
if (!listValues) {
return null;
}

const currentValues = value || [];

const handleChange = useCallback(
(itemValue: string, checked: boolean) => {
const newValues = checked
? [...currentValues, itemValue]
: currentValues.filter((v) => v !== itemValue);
setValue(newValues);
},
[currentValues, setValue]
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for conditional React hook usage in this file.
rg -n -C4 'function CheckboxGroupWidget|useCallback|if \(!listValues\)' packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx

Repository: calcom/cal.diy

Length of output: 1600


🏁 Script executed:

sed -n '304,342p' packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx

Repository: calcom/cal.diy

Length of output: 1187


Move the early return after the useCallback hook.

useCallback is conditionally executed depending on listValues. When listValues changes between renders from falsy to truthy (or vice versa), React's hook order gets misaligned, causing the component to crash. Hooks must be called in the same order on every render.

Proposed fix
 function CheckboxGroupWidget({
   listValues,
   setValue,
   value,
   ...remainingProps
 }: SelectLikeComponentPropsRAQB<string[]>) {
-  if (!listValues) {
-    return null;
-  }
-
   const currentValues = value || [];
 
   const handleChange = useCallback(
     (itemValue: string, checked: boolean) => {
       const newValues = checked
         ? [...currentValues, itemValue]
         : currentValues.filter((v) => v !== itemValue);
       setValue(newValues);
     },
     [currentValues, setValue]
   );
+
+  if (!listValues) {
+    return null;
+  }
 
   return (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function CheckboxGroupWidget({
listValues,
setValue,
value,
...remainingProps
}: SelectLikeComponentPropsRAQB<string[]>) {
if (!listValues) {
return null;
}
const currentValues = value || [];
const handleChange = useCallback(
(itemValue: string, checked: boolean) => {
const newValues = checked
? [...currentValues, itemValue]
: currentValues.filter((v) => v !== itemValue);
setValue(newValues);
},
[currentValues, setValue]
);
function CheckboxGroupWidget({
listValues,
setValue,
value,
...remainingProps
}: SelectLikeComponentPropsRAQB<string[]>) {
const currentValues = value || [];
const handleChange = useCallback(
(itemValue: string, checked: boolean) => {
const newValues = checked
? [...currentValues, itemValue]
: currentValues.filter((v) => v !== itemValue);
setValue(newValues);
},
[currentValues, setValue]
);
if (!listValues) {
return null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx`
around lines 304 - 324, The early return based on listValues is placed before a
hook, breaking hook order; move the conditional return so hooks run
unconditionally. Specifically, inside CheckboxGroupWidget keep computing
currentValues and declaring the useCallback handleChange (which references
currentValues and setValue) every render, then after that check if (!listValues)
return null; this preserves hook order for useCallback and avoids conditional
hook execution.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants