feat: add booking question field types to routing forms#28929
feat: add booking question field types to routing forms#28929thegreatalxx wants to merge 1 commit intocalcom:mainfrom
Conversation
|
Welcome to Cal.diy, @thegreatalxx! Thanks for opening this pull request. A few things to keep in mind:
A maintainer will review your PR soon. Thanks for contributing! |
📝 WalkthroughWalkthroughThis pull request extends the routing forms system with six new field types: ADDRESS, MULTIEMAIL, CHECKBOX, RADIO, BOOLEAN, and URL. The 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorBoolean
falsenever reaches the new boolean branch — it falls into the!valueearly return.The guard at the top:
if (!value) { return ""; }short-circuits for
false,0, and"". That means a boolean field whose answer is literallyfalsewill be returned as""for jsonLogic evaluation, so routing rules likeboolean_field == falsewill never match. The newly added boolean branch below only handlestrue/ 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
falsevalues are actually produced upstream (form submission), since this determines the correct canonical shape ("false"vsfalse) 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 | 🔴 CriticalCSV export doesn't handle the new field types — values for
checkbox,radio, andbooleanwill be wrong or empty.
getRoutingFormHeadersnow emits headers whosetypecan 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 intofield.valueString || "":
checkboxresponses are stored invalueStringArray(array of option ids) → downloaded cells will be empty.radioresponses are stored invalueStringas an option id → the raw UUID will be exported instead of the human-readable option label (unlikeselect, which is resolved throughheader.options).booleanwill 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:booleantype config looks minimal — verify operator support is sufficient.Only
equal/not_equalare supported with nois_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-runtimeRoutingFormFieldTypeenum over string literals.Since
RoutingFormFieldTypeis no longer aconst 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 exercisestext,select, andmultiselect. Adding at least one assertion that acheckbox/radiofield produceslistValuespopulated from options — and that aboolean/address/url/multiemailfield haslistValues: undefined— would lock in the new behavior ingetQueryBuilderConfig.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" vsCHECKBOX→ "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" forCHECKBOX). Also, UI strings here are hardcoded English — if i18n is expected for the editor, these should route throughpackages/i18n/locales/en/common.jsonandt(...)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/RADIOaren't inattributeTypesMap— is this code path reachable?
attributeTypesMap(lines 31-36) only mapsSINGLE_SELECT,MULTI_SELECT,TEXT,NUMBER.transformAttributesToCompatibleFormatthrows for anything else, soattributeTypehere can never be"checkbox"or"radio"in practice. The added branches inisAttributeWithOptionsare dead code today — fine to leave for future-proofing, but worth a short comment noting the intent, or simply keep the originalselect/multiselectcheck 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
📒 Files selected for processing (14)
apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsxapps/web/modules/insights/hooks/useInsightsColumns.tsxpackages/app-store/routing-forms/__tests__/config.test.tspackages/app-store/routing-forms/__tests__/uiConfig.test.tspackages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.tspackages/app-store/routing-forms/components/react-awesome-query-builder/config/config.tspackages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsxpackages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsxpackages/app-store/routing-forms/lib/FieldTypes.tspackages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.tspackages/app-store/routing-forms/lib/formSubmissionUtils.tspackages/app-store/routing-forms/lib/getQueryBuilderConfig.tspackages/app-store/routing-forms/lib/transformResponse.tspackages/features/insights/server/routing-events.ts
| 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, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🧩 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-formsRepository: 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 20Repository: 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 5Repository: 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.tsRepository: 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.tsRepository: 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.
| 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", | ||
| }, |
There was a problem hiding this comment.
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"} |
There was a problem hiding this comment.
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).
| 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} | ||
| /> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧩 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 fRepository: 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.tsxRepository: 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.tsxRepository: 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 -20Repository: 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 textfieldRepository: calcom/cal.diy
Length of output: 123
🏁 Script executed:
# Look for validation logic around MultiEmailWidget
rg "MultiEmailWidget" -A 10 -B 5Repository: calcom/cal.diy
Length of output: 8481
🏁 Script executed:
# Read the TextField component definition
head -100 packages/ui/components/form/inputs/TextField.tsxRepository: 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.tsxRepository: calcom/cal.diy
Length of output: 110
🏁 Script executed:
# Check the entire TextField implementation
cat packages/ui/components/form/inputs/TextField.tsxRepository: 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 3Repository: 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-formsRepository: 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 -100Repository: 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 -iRepository: 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.
| 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] | ||
| ); |
There was a problem hiding this comment.
🧩 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.tsxRepository: calcom/cal.diy
Length of output: 1600
🏁 Script executed:
sed -n '304,342p' packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsxRepository: 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.
| 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.
Adds new booking question field types to the routing forms editor. Fixes #28009.