Make location required in zodSchema as was there in production
parent
83610f8c1b
commit
2e380580cd
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -407,6 +407,8 @@ async function handler(
|
|||
isNotAnApiCall = false,
|
||||
}: {
|
||||
isNotAnApiCall?: boolean;
|
||||
} = {
|
||||
isNotAnApiCall: false,
|
||||
}
|
||||
) {
|
||||
const { userId } = req;
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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()]) })),
|
||||
|
|
Loading…
Reference in New Issue