From a800529d98ecd4bb66d316e0ff3435aeb1ce28b2 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Mon, 13 Feb 2023 14:34:06 +0530 Subject: [PATCH 1/2] More fixes discovered during review --- apps/web/public/static/locales/en/common.json | 3 +- .../react-awesome-query-builder/widgets.tsx | 4 +-- .../embeds/embed-core/src/embed-iframe.ts | 2 +- .../features/bookings/lib/getBookingFields.ts | 24 ++++++++----- .../bookings/lib/getBookingResponsesSchema.ts | 35 ++++++++++--------- .../components/WorkflowStepContainer.tsx | 1 + packages/features/form-builder/Components.tsx | 22 ++---------- .../features/form-builder/FormBuilder.tsx | 2 +- packages/prisma/zod-utils.ts | 3 +- packages/trpc/server/routers/viewer/teams.tsx | 2 +- .../trpc/server/routers/viewer/workflows.tsx | 4 ++- 11 files changed, 49 insertions(+), 53 deletions(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 512c56c96c..5f7fd22f6a 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1594,5 +1594,6 @@ "event_type_seats": "{{numberOfSeats}} seats", "booking_questions_title": "Booking questions", "booking_questions_description": "Customize the questions asked on the booking page", - "add_a_booking_question": "Add a question" + "add_a_booking_question": "Add a question", + "duplicate_email": "Email is duplicate" } diff --git a/packages/app-store/ee/routing-forms/components/react-awesome-query-builder/widgets.tsx b/packages/app-store/ee/routing-forms/components/react-awesome-query-builder/widgets.tsx index dbd40adafa..ce8cb125f2 100644 --- a/packages/app-store/ee/routing-forms/components/react-awesome-query-builder/widgets.tsx +++ b/packages/app-store/ee/routing-forms/components/react-awesome-query-builder/widgets.tsx @@ -7,7 +7,7 @@ import { ProviderProps, } from "react-awesome-query-builder"; -import { Button as CalButton, Input, SelectWithValidation as Select, TextArea, TextField } from "@calcom/ui"; +import { Button as CalButton, Input, SelectWithValidation as Select, TextField } from "@calcom/ui"; import { FiTrash, FiPlus } from "@calcom/ui/components/icon"; export type CommonProps< @@ -63,7 +63,7 @@ export type TextLikeComponentProps = TextLikeComponentProps & { - customProps?: any; + customProps?: object; type?: "text" | "number" | "email" | "tel"; maxLength?: number; noLabel?: boolean; diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index 8db779b22b..addb350572 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -230,7 +230,7 @@ export const useIsEmbed = (embedSsr?: boolean) => { const _isValidNamespace = isValidNamespace(namespace); if (parent !== window && !_isValidNamespace) { log( - `Looks like you have iframed cal.com but not using Embed Snippet. Directly using an iframe isn't recommended.` + "Looks like you have iframed cal.com but not using Embed Snippet. Directly using an iframe isn't recommended." ); } setIsEmbed(window?.isEmbed?.() || false); diff --git a/packages/features/bookings/lib/getBookingFields.ts b/packages/features/bookings/lib/getBookingFields.ts index dcd1b790b0..0891a6240a 100644 --- a/packages/features/bookings/lib/getBookingFields.ts +++ b/packages/features/bookings/lib/getBookingFields.ts @@ -10,6 +10,7 @@ import { } from "@calcom/prisma/zod-utils"; type Fields = z.infer; + const EventTypeCustomInputType = { TEXT: "TEXT", TEXTLONG: "TEXTLONG", @@ -19,6 +20,20 @@ const EventTypeCustomInputType = { PHONE: "PHONE", }; +export const SystemField = z.enum(["name", "email", "location", "notes", "guests", "rescheduleReason"]); + +export const SystemFieldsEditability: Record, Fields[number]["editable"]> = { + name: "system", + email: "system", + location: "system", + notes: "system-but-optional", + guests: "system-but-optional", + rescheduleReason: "system", +}; + +/** + * This fn is the key to ensure on the fly mapping of customInputs to bookingFields and ensuring that all the systems fields are present and correctly ordered in bookingFields + */ export const getBookingFieldsWithSystemFields = ({ bookingFields, disableGuests, @@ -41,15 +56,6 @@ export const getBookingFieldsWithSystemFields = ({ customInputs: parsedCustomInputs, }); }; -export const SystemField = z.enum(["name", "email", "location", "notes", "guests", "rescheduleReason"]); -export const SystemFieldsEditability: Record, Fields[number]["editable"]> = { - name: "system", - email: "system", - location: "system", - notes: "system-but-optional", - guests: "system-but-optional", - rescheduleReason: "system", -}; export const ensureBookingInputsHaveSystemFields = ({ bookingFields, diff --git a/packages/features/bookings/lib/getBookingResponsesSchema.ts b/packages/features/bookings/lib/getBookingResponsesSchema.ts index 9c2347a209..271fb9da30 100644 --- a/packages/features/bookings/lib/getBookingResponsesSchema.ts +++ b/packages/features/bookings/lib/getBookingResponsesSchema.ts @@ -15,7 +15,8 @@ export default function getBookingResponsesSchema(eventType: EventType) { return preprocess({ schema, eventType, forQueryParsing: false }); } -// TODO: Move it to FormBuilder schema +// TODO: Move preprocess of `booking.responses` to FormBuilder schema as that is going to parse the fields supported by FormBuilder +// It allows anyone using FormBuilder to get the same preprocessing automatically function preprocess({ schema, eventType, @@ -79,28 +80,36 @@ function preprocess({ } if (isRequired && !forQueryParsing && !value) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: m(`Required`) }); + ctx.addIssue({ code: z.ZodIssueCode.custom, message: m(`error_required_field`) }); if (bookingField.type === "email") { // Email RegExp to validate if the input is a valid email if (!emailSchema.safeParse(value).success) { ctx.addIssue({ code: z.ZodIssueCode.custom, - //TODO: How to do translation in booker language here? - message: m("That doesn't look like an email address"), + message: m("email_validation_error"), }); } return; } if (bookingField.type === "multiemail") { - if (!emailSchema.array().safeParse(value).success) { + const emailsParsed = emailSchema.array().safeParse(value); + if (!emailsParsed.success) { ctx.addIssue({ code: z.ZodIssueCode.custom, - //TODO: How to do translation in booker language here? - message: m("That doesn't look like an email address"), + message: m("email_validation_error"), }); + return; } + + const emails = emailsParsed.data; + emails.sort().some((item, i) => { + if (item === emails[i + 1]) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: m("duplicate_email") }); + return true; + } + }); return; } @@ -113,7 +122,7 @@ function preprocess({ if (bookingField.type === "phone") { if (!phoneSchema.safeParse(value).success) { - ctx.addIssue({ code: z.ZodIssueCode.custom, message: m("Invalid Phone") }); + ctx.addIssue({ code: z.ZodIssueCode.custom, message: m("invalid_number") }); } return; } @@ -130,20 +139,14 @@ function preprocess({ if (bookingField.optionsInputs) { // Also, if the option is there with one input, we need to show just the input and not radio if (isRequired && bookingField.optionsInputs[value?.value]?.required && !value?.optionValue) { - ctx.addIssue({ code: z.ZodIssueCode.custom, message: m("Required Option Value") }); + ctx.addIssue({ code: z.ZodIssueCode.custom, message: m("error_required_field") }); } } return; } if ( - bookingField.type === "address" || - bookingField.type === "text" || - bookingField.type === "select" || - bookingField.type === "name" || - bookingField.type === "number" || - bookingField.type === "radio" || - bookingField.type === "textarea" + ["address", "text", "select", "name", "number", "radio", "textarea"].includes(bookingField.type) ) { const schema = stringSchema; if (!schema.safeParse(value).success) { diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index ff9987618e..b3bb7bc119 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -401,6 +401,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { id={`steps.${step.stepNumber - 1}.sendTo`} className="min-w-fit sm:rounded-tl-md sm:rounded-bl-md sm:border-r-transparent" required + value={value} onChange={(val) => { const isAlreadyVerified = !!verifiedNumbers ?.concat([]) diff --git a/packages/features/form-builder/Components.tsx b/packages/features/form-builder/Components.tsx index a2029f7224..606a9a65fe 100644 --- a/packages/features/form-builder/Components.tsx +++ b/packages/features/form-builder/Components.tsx @@ -98,9 +98,7 @@ export const Components: Record = { if (!props) { return
; } - // FIXME: type=email is removed so that RHF validations can work - // But, RHF errors are not integrated in Routing Forms form - return ; + return ; }, }, address: { @@ -143,16 +141,9 @@ export const Components: Record = { value[index] = e.target.value; setValue(value); }} - className={classNames( - inputClassName, - // bookingForm.formState.errors.guests?.[index] && - // "!focus:ring-red-700 !border-red-700", - "border-r-0" - )} + className={classNames(inputClassName, "border-r-0")} addOnClassname={classNames( "border-gray-300 border block border-l-0 disabled:bg-gray-200 disabled:hover:cursor-not-allowed bg-transparent disabled:text-gray-500 dark:border-darkgray-300 " - // bookingForm.formState.errors.guests?.[index] && - // "!focus:ring-red-700 !border-red-700" )} placeholder={placeholder} label={<>} @@ -171,14 +162,6 @@ export const Components: Record = { } /> - {/* {bookingForm.formState.errors.guests?.[index] && ( -
- -

- {bookingForm.formState.errors.guests?.[index]?.message} -

-
- )} */} ))} @@ -187,7 +170,6 @@ export const Components: Record = { color="minimal" StartIcon={FiUserPlus} className="my-2.5" - // className="mb-1 block text-sm font-medium text-gray-700 dark:text-white" onClick={() => { value.push(""); setValue(value); diff --git a/packages/features/form-builder/FormBuilder.tsx b/packages/features/form-builder/FormBuilder.tsx index 47a5642181..0e47d2e8b9 100644 --- a/packages/features/form-builder/FormBuilder.tsx +++ b/packages/features/form-builder/FormBuilder.tsx @@ -696,7 +696,7 @@ export const FormBuilderField = ({ return null; } - message = message.replace(/\{[^}]+\}(.*)/, "$1"); + message = message.replace(/\{[^}]+\}(.*)/, "$1").trim(); if (field.hidden) { console.error(`Error message for hidden field:${field.name} => ${message}`); } diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index f625383c73..394a1945a0 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -57,7 +57,8 @@ export const eventTypeBookingFields = formBuilderFieldsSchema; export const BookingFieldType = eventTypeBookingFields.element.shape.type.Enum; export type BookingFieldType = typeof BookingFieldType extends z.Values ? T[number] : never; -// Validation of user added bookingFields's responses happen using getBookingResponsesSchema which requires eventType. +// Validation of user added bookingFields' responses happen using `getBookingResponsesSchema` which requires `eventType`. +// So it is a dynamic validation and thus entire validation can't exist here export const bookingResponses = z .object({ email: z.string(), diff --git a/packages/trpc/server/routers/viewer/teams.tsx b/packages/trpc/server/routers/viewer/teams.tsx index 0f3a78d06b..b0a55cd6ce 100644 --- a/packages/trpc/server/routers/viewer/teams.tsx +++ b/packages/trpc/server/routers/viewer/teams.tsx @@ -706,7 +706,7 @@ export const viewerTeamsRouter = router({ }, }, }); - return { hasTeamPlan: hasTeamPlan }; + return { hasTeamPlan: !!hasTeamPlan }; }), listInvites: authedProcedure.query(async ({ ctx }) => { const userId = ctx.user.id; diff --git a/packages/trpc/server/routers/viewer/workflows.tsx b/packages/trpc/server/routers/viewer/workflows.tsx index d08eda016f..55c3aa3f64 100644 --- a/packages/trpc/server/routers/viewer/workflows.tsx +++ b/packages/trpc/server/routers/viewer/workflows.tsx @@ -532,7 +532,9 @@ export const workflowsRouter = router({ for (const eventTypeId of activeOn) { await upsertSmsReminderFieldForBooking({ workflowId: id, - isSmsReminderNumberRequired: input.steps.some((s) => s.numberRequired), + isSmsReminderNumberRequired: input.steps.some( + (s) => s.action === WorkflowActions.SMS_ATTENDEE && s.numberRequired + ), eventTypeId, }); } From 0d853b4dce978b53fe5ed2c752b25d4349043c0a Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Mon, 13 Feb 2023 14:49:48 +0530 Subject: [PATCH 2/2] Update packages/ui/components/form/inputs/Input.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Omar López --- packages/ui/components/form/inputs/Input.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/components/form/inputs/Input.tsx b/packages/ui/components/form/inputs/Input.tsx index 479dc55733..bf44769913 100644 --- a/packages/ui/components/form/inputs/Input.tsx +++ b/packages/ui/components/form/inputs/Input.tsx @@ -339,7 +339,6 @@ const PlainForm = (props: FormProps, ref: Ref { - console.log(err); // FIXME: Booking Pages don't have toast, so this error is never shown showToast(`${getErrorFromUnknown(err).message}`, "error"); });