Files
calendar/packages/app-store/routing-forms/components/FormInputFields.tsx
T
Joe Au-YeungGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
5ceda11a0b fix: add 'use client' to routing-forms components to resolve React error 185 (#25107)
Adds 'use client' directive to FormInputFields.tsx and widgets.tsx in the
routing-forms package. These components use React hooks (useLocale, setState)
but were missing the client directive, causing React error 185 (invalid hook
call) in production builds due to Next.js App Router bundling differences.

The error only occurred in production, not local dev, because production
builds optimize and bundle packages differently. The widgets.tsx file uses
the useLocale hook in the Button component, and FormInputFields.tsx passes
state setters to child components.

Fixes the reroute dialog error reported in production.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-11-13 18:36:46 +00:00

105 lines
3.8 KiB
TypeScript

"use client";
import type { Dispatch, SetStateAction } from "react";
import type { App_RoutingForms_Form } from "@calcom/prisma/client";
import { SkeletonText } from "@calcom/ui/components/skeleton";
import getFieldIdentifier from "../lib/getFieldIdentifier";
import { getQueryBuilderConfigForFormFields } from "../lib/getQueryBuilderConfig";
import isRouterLinkedField from "../lib/isRouterLinkedField";
import { getUIOptionsForSelect } from "../lib/selectOptions";
import { getFieldResponseForJsonLogic } from "../lib/transformResponse";
import type { SerializableForm, FormResponse } from "../types/types";
import { ConfigFor, withRaqbSettingsAndWidgets } from "./react-awesome-query-builder/config/uiConfig";
export type FormInputFieldsProps = {
form: Pick<SerializableForm<App_RoutingForms_Form>, "fields">;
/**
* Make sure that response is updated by setResponse
*/
response: FormResponse;
setResponse: Dispatch<SetStateAction<FormResponse>>;
/**
* Identifier of the fields that should be disabled
*/
disabledFields?: string[];
};
export default function FormInputFields(props: FormInputFieldsProps) {
const { form, response, setResponse, disabledFields = [] } = props;
const formFieldsQueryBuilderConfig = withRaqbSettingsAndWidgets({
config: getQueryBuilderConfigForFormFields(form),
configFor: ConfigFor.FormFields,
});
return (
<>
{form.fields?.map((field) => {
if (isRouterLinkedField(field)) {
// @ts-expect-error FIXME @hariombalhara
const routerField = field.routerField;
// A field that has been deleted from the main form would still be there in the duplicate form but disconnected
// In that case, it could mistakenly be categorized as RouterLinkedField, so if routerField is nullish, we use the field itself
field = routerField ?? field;
}
const widget = formFieldsQueryBuilderConfig.widgets[field.type];
if (!("factory" in widget)) {
return null;
}
const Component = widget.factory;
const options = getUIOptionsForSelect(field);
const fieldIdentifier = getFieldIdentifier(field);
return (
<div key={field.id} className="block flex-col sm:flex ">
<div className="min-w-48 mb-2 flex-grow">
<label id="slug-label" htmlFor="slug" className="text-default flex text-sm font-medium">
{field.label}
</label>
</div>
<Component
value={response[field.id]?.value ?? ""}
placeholder={field.placeholder ?? ""}
// required property isn't accepted by query-builder types
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */
required={!!field.required}
listValues={options}
disabled={disabledFields?.includes(fieldIdentifier)}
data-testid={`form-field-${fieldIdentifier}`}
setValue={(value: number | string | string[]) => {
setResponse(() => {
return {
...response,
[field.id]: {
label: field.label,
identifier: field?.identifier,
value: getFieldResponseForJsonLogic({ field, value }),
},
};
});
}}
/>
</div>
);
})}
</>
);
}
export const FormInputFieldsSkeleton = () => {
const numberOfFields = 5;
return (
<>
{Array.from({ length: numberOfFields }).map((_, index) => (
<div key={index} className="mb-4 block flex-col sm:flex ">
<SkeletonText className="mb-2 h-3.5 w-64" />
<SkeletonText className="mb-2 h-9 w-32 w-full" />
</div>
))}
</>
);
};