Make location required in zodSchema as was there in production

pull/6560/head
Hariom Balhara 2023-02-16 14:31:23 +05:30
parent 83610f8c1b
commit 2e380580cd
8 changed files with 59 additions and 43 deletions

View File

@ -295,7 +295,7 @@ const BookingPage = ({
const defaultValues = () => {
if (!rescheduleUid) {
const defaults = {
responses: {} as z.infer<typeof bookingFormSchema>["responses"],
responses: {} as Partial<z.infer<typeof bookingFormSchema>["responses"]>,
};
const responses = eventType.bookingFields.reduce((responses, field) => {
@ -322,7 +322,7 @@ const BookingPage = ({
}
const defaults = {
responses: {} as z.infer<typeof bookingFormSchema>["responses"],
responses: {} as Partial<z.infer<typeof bookingFormSchema>["responses"]>,
};
const responses = eventType.bookingFields.reduce((responses, field) => {

View File

@ -5,26 +5,28 @@ import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/
import slugify from "@calcom/lib/slugify";
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
type BookingSelect = {
description: true;
customInputs: true;
attendees: {
select: {
email: true;
name: true;
};
};
location: true;
smsReminderNumber: true;
};
// Backward Compatibility for booking created before we had managed booking questions
function getResponsesFromOldBooking(
rawBooking: Prisma.BookingGetPayload<{
select: {
description: true;
customInputs: true;
attendees: {
select: {
email: true;
name: true;
};
};
location: true;
smsReminderNumber: true;
};
select: BookingSelect;
}>
) {
const customInputs = rawBooking.customInputs || {};
const responses = Object.keys(customInputs).reduce((acc, key) => {
acc[slugify(key) as keyof typeof acc] = customInputs[key as keyof typeof customInputs];
const responses = Object.keys(customInputs).reduce((acc, label) => {
acc[slugify(label) as keyof typeof acc] = customInputs[label as keyof typeof customInputs];
return acc;
}, {});
return {
@ -70,12 +72,10 @@ async function getBooking(
if (!rawBooking) {
return rawBooking;
}
const booking = {
...rawBooking,
responses: getBookingResponsesPartialSchema({
bookingFields,
}).parse(rawBooking.responses || getResponsesFromOldBooking(rawBooking)),
};
const booking = getBookingWithResponses(rawBooking, {
bookingFields,
});
if (booking) {
// @NOTE: had to do this because Server side cant return [Object objects]
@ -88,4 +88,23 @@ async function getBooking(
export type GetBookingType = Prisma.PromiseReturnType<typeof getBooking>;
export const getBookingWithResponses = <
T extends Prisma.BookingGetPayload<{
select: BookingSelect & {
responses: true;
};
}>
>(
booking: T,
eventType: {
bookingFields: z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">;
}
) => {
return {
...booking,
responses: getBookingResponsesPartialSchema({
bookingFields: eventType.bookingFields,
}).parse(booking.responses || getResponsesFromOldBooking(booking)),
};
};
export default getBooking;

View File

@ -29,7 +29,6 @@ import {
SystemField,
getBookingFieldsWithSystemFields,
} from "@calcom/features/bookings/lib/getBookingFields";
import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
import { parseRecurringEvent } from "@calcom/lib";
import CustomBranding from "@calcom/lib/CustomBranding";
import { APP_NAME } from "@calcom/lib/constants";
@ -48,6 +47,7 @@ import { Button, EmailInput, HeadSeo, Label } from "@calcom/ui";
import { FiX, FiChevronLeft, FiCheck, FiCalendar, FiExternalLink } from "@calcom/ui/components/icon";
import { timeZone } from "@lib/clock";
import { getBookingWithResponses } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import CancelBooking from "@components/booking/CancelBooking";
@ -1018,6 +1018,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
notFound: true,
};
}
const eventTypeRaw = !bookingInfoRaw.eventTypeId
? getDefaultEvent(eventTypeSlug || "")
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
@ -1027,10 +1028,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
};
}
const bookingInfo = {
...bookingInfoRaw,
responses: getBookingResponsesPartialSchema(eventTypeRaw).parse(bookingInfoRaw.responses),
};
const bookingInfo = getBookingWithResponses(bookingInfoRaw, eventTypeRaw);
// @NOTE: had to do this because Server side cant return [Object objects]
// probably fixable with json.stringify -> json.parse

View File

@ -193,7 +193,6 @@ export const ensureBookingInputsHaveSystemFields = ({
defaultLabel: "location",
type: "radioInput",
name: "location",
// Even though it should be required it is optional in production with backend choosing CalVideo as the fallback
required: false,
// Populated on the fly from locations. I don't want to duplicate storing locations and instead would like to be able to refer to locations in eventType.
// options: `eventType.locations`

View File

@ -7,7 +7,7 @@ type EventType = Parameters<typeof preprocess>[0]["eventType"];
export const getBookingResponsesPartialSchema = (eventType: EventType) => {
const schema = bookingResponses.unwrap().partial().and(z.record(z.any()));
return preprocess({ schema, eventType, forQueryParsing: true });
return preprocess({ schema, eventType, isPartialSchema: true });
};
// Should be used when we know that not all fields responses are present
@ -15,7 +15,7 @@ export const getBookingResponsesPartialSchema = (eventType: EventType) => {
// - Can happen when we are parsing a booking's responses (which was created before we added a new required field)
export default function getBookingResponsesSchema(eventType: EventType) {
const schema = bookingResponses.and(z.record(z.any()));
return preprocess({ schema, eventType, forQueryParsing: false });
return preprocess({ schema, eventType, isPartialSchema: false });
}
// TODO: Move preprocess of `booking.responses` to FormBuilder schema as that is going to parse the fields supported by FormBuilder
@ -23,14 +23,12 @@ export default function getBookingResponsesSchema(eventType: EventType) {
function preprocess<T extends z.ZodType>({
schema,
eventType,
forQueryParsing,
isPartialSchema,
}: {
schema: T;
forQueryParsing: boolean;
isPartialSchema: boolean;
eventType: {
bookingFields: z.infer<typeof eventTypeBookingFields> &
z.infer<typeof eventTypeBookingFields> &
z.BRAND<"HAS_SYSTEM_FIELDS">;
bookingFields: z.infer<typeof eventTypeBookingFields> & z.BRAND<"HAS_SYSTEM_FIELDS">;
};
}): z.ZodType<z.infer<T>, z.infer<T>, z.infer<T>> {
const preprocessed = z.preprocess(
@ -71,18 +69,18 @@ function preprocess<T extends z.ZodType>({
eventType.bookingFields.forEach((bookingField) => {
const value = responses[bookingField.name];
const stringSchema = z.string();
const emailSchema = forQueryParsing ? z.string() : z.string().email();
const phoneSchema = forQueryParsing
const emailSchema = isPartialSchema ? z.string() : z.string().email();
const phoneSchema = isPartialSchema
? z.string()
: z.string().refine((val) => isValidPhoneNumber(val));
// Tag the message with the input name so that the message can be shown at appropriate place
const m = (message: string) => `{${bookingField.name}}${message}`;
const isRequired = bookingField.required;
if ((forQueryParsing || !isRequired) && value === undefined) {
if ((isPartialSchema || !isRequired) && value === undefined) {
return;
}
if (isRequired && !forQueryParsing && !value)
if (isRequired && !isPartialSchema && !value)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: m(`error_required_field`) });
if (bookingField.type === "email") {
@ -178,7 +176,7 @@ function preprocess<T extends z.ZodType>({
});
})
);
if (forQueryParsing) {
if (isPartialSchema) {
// Query Params can be completely invalid, try to preprocess as much of it in correct format but in worst case simply don't prefill instead of crashing
return preprocessed.catch(() => {
console.error("Failed to preprocess query params, prefilling will be skipped");

View File

@ -407,6 +407,8 @@ async function handler(
isNotAnApiCall = false,
}: {
isNotAnApiCall?: boolean;
} = {
isNotAnApiCall: false,
}
) {
const { userId } = req;

View File

@ -166,11 +166,11 @@ export const FormBuilder = function FormBuilder({
onChange([
{
label: "Option 1",
value: "1",
value: "Option 1",
},
{
label: "Option 2",
value: "2",
value: "Option 2",
},
]);
}

View File

@ -204,7 +204,7 @@ export const bookingCreateSchemaLegacyPropsForApi = z.object({
name: z.string(),
guests: z.array(z.string()).optional(),
notes: z.string().optional(),
location: z.string().optional(),
location: z.string(),
smsReminderNumber: z.string().optional().nullable(),
rescheduleReason: z.string().optional(),
customInputs: z.array(z.object({ label: z.string(), value: z.union([z.string(), z.boolean()]) })),