refactor: v2 and atoms booking fields (#16685)
* refactor: define inputs and outputs by separating default and custom fields * refactor: event type api <-> internal transformers * refactor: v2 use refactored libraries to transform input and output booking fields * refactor: dont allow custom slugs with default reserved slugs * refactor: input service store only specific default system fields * refactor: atoms display only specific default system fields * refactor: delete unused system field constants * fix: tests * fix: old event types * fix: imports * fix: import * chore: release libraries and use v2 with them * fix: unit tests * refactor: push email in case of missing phone * refactor: satisfies check for event-type locations
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.34",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.36",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
+11
-1
@@ -15,6 +15,7 @@ import {
|
||||
updateEventType,
|
||||
EventTypesPublic,
|
||||
getEventTypesPublic,
|
||||
systemBeforeFieldEmail,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
@@ -127,8 +128,17 @@ export class EventTypesService_2024_04_15 {
|
||||
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput_2024_04_15, user: UserWithProfile) {
|
||||
this.checkCanUpdateEventType(user.id, eventTypeId);
|
||||
const eventTypeUser = await this.getUserToUpdateEvent(user);
|
||||
const bookingFields = [...(body.bookingFields || [])];
|
||||
|
||||
if (
|
||||
!bookingFields.find((field) => field.type === "email") &&
|
||||
!bookingFields.find((field) => field.type === "phone")
|
||||
) {
|
||||
bookingFields.push(systemBeforeFieldEmail);
|
||||
}
|
||||
|
||||
await updateEventType({
|
||||
input: { id: eventTypeId, ...body },
|
||||
input: { id: eventTypeId, ...body, bookingFields },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
+25
-4
@@ -250,7 +250,6 @@ describe("Event types Endpoints", () => {
|
||||
expect(createdEventType.description).toEqual(body.description);
|
||||
expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes);
|
||||
expect(createdEventType.locations).toEqual(body.locations);
|
||||
expect(createdEventType.bookingFields).toEqual(body.bookingFields);
|
||||
expect(createdEventType.ownerId).toEqual(user.id);
|
||||
expect(createdEventType.scheduleId).toEqual(firstSchedule.id);
|
||||
expect(createdEventType.bookingLimitsCount).toEqual(body.bookingLimitsCount);
|
||||
@@ -259,6 +258,16 @@ describe("Event types Endpoints", () => {
|
||||
expect(createdEventType.offsetStart).toEqual(body.offsetStart);
|
||||
expect(createdEventType.bookingWindow).toEqual(body.bookingWindow);
|
||||
expect(createdEventType.recurrence).toEqual(body.recurrence);
|
||||
|
||||
const responseBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", type: "name" },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
...responseBookingFields.map((field) => ({ isDefault: false, ...field })),
|
||||
];
|
||||
|
||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
eventType = responseBody.data;
|
||||
});
|
||||
});
|
||||
@@ -476,6 +485,16 @@ describe("Event types Endpoints", () => {
|
||||
let legacyEventTypeId1: number;
|
||||
let legacyEventTypeId2: number;
|
||||
|
||||
const expectedReturnSystemFields = [
|
||||
{ isDefault: true, required: true, slug: "name", type: "name" },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
{ isDefault: true, type: "radioInput", slug: "location", required: false },
|
||||
{ isDefault: true, required: true, slug: "title", type: "text" },
|
||||
{ isDefault: true, required: false, slug: "notes", type: "textarea" },
|
||||
{ isDefault: true, required: false, slug: "guests", type: "multiemail" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
@@ -545,7 +564,7 @@ describe("Event types Endpoints", () => {
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should return empty bookingFields if system fields are the only one in database", async () => {
|
||||
it("should return system bookingFields stored in database", async () => {
|
||||
const legacyEventTypeInput = {
|
||||
title: "legacy event type",
|
||||
description: "legacy event type description",
|
||||
@@ -638,11 +657,11 @@ describe("Event types Endpoints", () => {
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const fetchedEventType = responseBody.data;
|
||||
expect(fetchedEventType.bookingFields).toEqual([]);
|
||||
expect(fetchedEventType.bookingFields).toEqual(expectedReturnSystemFields);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return user created bookingFields among system fields in the database", async () => {
|
||||
it("should return user created bookingFields with system fields", async () => {
|
||||
const userDefinedBookingField = {
|
||||
name: "team",
|
||||
type: "textarea",
|
||||
@@ -755,7 +774,9 @@ describe("Event types Endpoints", () => {
|
||||
const fetchedEventType = responseBody.data;
|
||||
|
||||
expect(fetchedEventType.bookingFields).toEqual([
|
||||
...expectedReturnSystemFields,
|
||||
{
|
||||
isDefault: false,
|
||||
type: userDefinedBookingField.type,
|
||||
slug: userDefinedBookingField.name,
|
||||
label: userDefinedBookingField.label,
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
getEventTypeById,
|
||||
transformApiEventTypeBookingFields,
|
||||
transformApiEventTypeLocations,
|
||||
transformBookingFieldsApiToInternal,
|
||||
transformLocationsApiToInternal,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
@@ -30,8 +30,8 @@ type InputEventTransformed = Omit<
|
||||
> & {
|
||||
length: number;
|
||||
slug: string;
|
||||
locations?: ReturnType<typeof transformApiEventTypeLocations>;
|
||||
bookingFields?: ReturnType<typeof transformApiEventTypeBookingFields>;
|
||||
locations?: ReturnType<typeof transformLocationsApiToInternal>;
|
||||
bookingFields?: ReturnType<typeof transformBookingFieldsApiToInternal>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
||||
+34
-13
@@ -1,12 +1,16 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
transformApiEventTypeBookingFields,
|
||||
transformApiEventTypeLocations,
|
||||
transformApiEventTypeIntervalLimits,
|
||||
transformApiEventTypeFutureBookingLimits,
|
||||
transformApiEventTypeRecurrence,
|
||||
transformBookingFieldsApiToInternal,
|
||||
transformLocationsApiToInternal,
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemAfterFieldRescheduleReason,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { systemBeforeFieldLocation } from "@calcom/platform-libraries";
|
||||
import { CreateEventTypeInput_2024_06_14, UpdateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
@@ -30,11 +34,12 @@ export class InputEventTypesService_2024_06_14 {
|
||||
...rest
|
||||
} = inputEventType;
|
||||
|
||||
const hasMultipleLocations = (locations || defaultLocations).length > 1;
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: this.transformInputLocations(locations || defaultLocations),
|
||||
bookingFields: this.transformInputBookingFields(bookingFields),
|
||||
bookingFields: this.transformInputBookingFields(bookingFields, hasMultipleLocations),
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
? this.transformInputIntervalLimits(bookingLimitsDuration)
|
||||
@@ -59,11 +64,15 @@ export class InputEventTypesService_2024_06_14 {
|
||||
...rest
|
||||
} = inputEventType;
|
||||
|
||||
const hasMultipleLocations = !!(locations && locations?.length > 1);
|
||||
|
||||
const eventType = {
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: locations ? this.transformInputLocations(locations) : undefined,
|
||||
bookingFields: bookingFields ? this.transformInputBookingFields(bookingFields) : undefined,
|
||||
bookingFields: bookingFields
|
||||
? this.transformInputBookingFields(bookingFields, hasMultipleLocations)
|
||||
: undefined,
|
||||
schedule: scheduleId,
|
||||
bookingLimits: bookingLimitsCount ? this.transformInputIntervalLimits(bookingLimitsCount) : undefined,
|
||||
durationLimits: bookingLimitsDuration
|
||||
@@ -77,23 +86,35 @@ export class InputEventTypesService_2024_06_14 {
|
||||
}
|
||||
|
||||
transformInputLocations(inputLocations: CreateEventTypeInput_2024_06_14["locations"]) {
|
||||
return transformApiEventTypeLocations(inputLocations);
|
||||
return transformLocationsApiToInternal(inputLocations);
|
||||
}
|
||||
|
||||
transformInputBookingFields(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]) {
|
||||
return transformApiEventTypeBookingFields(inputBookingFields);
|
||||
transformInputBookingFields(
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
|
||||
hasMultipleLocations: boolean
|
||||
) {
|
||||
const defaultFieldsBefore = [systemBeforeFieldName, systemBeforeFieldEmail];
|
||||
// note(Lauris): if event type has multiple locations then a radio button booking field has to be displayed to allow booker to pick location
|
||||
if (hasMultipleLocations) {
|
||||
defaultFieldsBefore.push(systemBeforeFieldLocation);
|
||||
}
|
||||
|
||||
const customFields = transformBookingFieldsApiToInternal(inputBookingFields);
|
||||
const defaultFieldsAfter = [systemAfterFieldRescheduleReason];
|
||||
|
||||
return [...defaultFieldsBefore, ...customFields, ...defaultFieldsAfter];
|
||||
}
|
||||
|
||||
transformInputIntervalLimits(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]) {
|
||||
return transformApiEventTypeIntervalLimits(inputBookingFields);
|
||||
return transformIntervalLimitsApiToInternal(inputBookingFields);
|
||||
}
|
||||
|
||||
transformInputBookingWindow(inputBookingWindow: CreateEventTypeInput_2024_06_14["bookingWindow"]) {
|
||||
const res = transformApiEventTypeFutureBookingLimits(inputBookingWindow);
|
||||
const res = transformFutureBookingLimitsApiToInternal(inputBookingWindow);
|
||||
return !!res ? res : {};
|
||||
}
|
||||
|
||||
transformInputRecurrignEvent(recurrence: CreateEventTypeInput_2024_06_14["recurrence"]) {
|
||||
return transformApiEventTypeRecurrence(recurrence);
|
||||
return transformRecurrenceApiToInternal(recurrence);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-14
@@ -4,17 +4,17 @@ import type { EventType, User, Schedule } from "@prisma/client";
|
||||
import {
|
||||
EventTypeMetaDataSchema,
|
||||
userMetadata,
|
||||
getResponseEventTypeLocations,
|
||||
getResponseEventTypeBookingFields,
|
||||
transformLocationsInternalToApi,
|
||||
transformBookingFieldsInternalToApi,
|
||||
parseRecurringEvent,
|
||||
TransformedLocationsSchema,
|
||||
BookingFieldsSchema,
|
||||
SystemField,
|
||||
UserField,
|
||||
CustomField,
|
||||
parseBookingLimit,
|
||||
getResponseEventTypeIntervalLimits,
|
||||
getResponseEventTypeFutureBookingLimits,
|
||||
getResponseEventTypeRecurrence,
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { TransformFutureBookingsLimitSchema_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@@ -144,19 +144,20 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
|
||||
transformLocations(locations: any) {
|
||||
if (!locations) return [];
|
||||
return getResponseEventTypeLocations(TransformedLocationsSchema.parse(locations));
|
||||
return transformLocationsInternalToApi(TransformedLocationsSchema.parse(locations));
|
||||
}
|
||||
|
||||
transformBookingFields(inputBookingFields: (SystemField | UserField)[] | null) {
|
||||
if (!inputBookingFields) return [];
|
||||
const userFields = inputBookingFields.filter((field) => field.editable === "user") as UserField[];
|
||||
return getResponseEventTypeBookingFields(userFields);
|
||||
transformBookingFields(bookingFields: (SystemField | CustomField)[] | null) {
|
||||
if (!bookingFields) return [];
|
||||
|
||||
return transformBookingFieldsInternalToApi(bookingFields);
|
||||
}
|
||||
|
||||
transformRecurringEvent(recurringEvent: any) {
|
||||
if (!recurringEvent) return null;
|
||||
const recurringEventParsed = parseRecurringEvent(recurringEvent);
|
||||
return getResponseEventTypeRecurrence(recurringEventParsed);
|
||||
if (!recurringEventParsed) return null;
|
||||
return transformRecurrenceInternalToApi(recurringEventParsed);
|
||||
}
|
||||
|
||||
transformMetadata(metadata: any) {
|
||||
@@ -182,10 +183,10 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
|
||||
transformIntervalLimits(bookingLimits: any) {
|
||||
const bookingLimitsParsed = parseBookingLimit(bookingLimits);
|
||||
return getResponseEventTypeIntervalLimits(bookingLimitsParsed);
|
||||
return transformIntervalLimitsInternalToApi(bookingLimitsParsed);
|
||||
}
|
||||
|
||||
transformBookingWindow(bookingLimits: TransformFutureBookingsLimitSchema_2024_06_14) {
|
||||
return getResponseEventTypeFutureBookingLimits(bookingLimits);
|
||||
return transformFutureBookingLimitsInternalToApi(bookingLimits);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,4 +22,7 @@ const env: Partial<Omit<Environment, "NODE_ENV">> = {
|
||||
process.env = {
|
||||
...env,
|
||||
...process.env,
|
||||
NEXT_PUBLIC_VAPID_PUBLIC_KEY:
|
||||
"BIds0AQJ96xGBjTSMHTOqLBLutQE7Lu32KKdgSdy7A2cS4mKI2cgb3iGkhDJa5Siy-stezyuPm8qpbhmNxdNHMw",
|
||||
VAPID_PRIVATE_KEY: "6cJtkASCar5sZWguIAW7OjvyixpBw9p8zL8WDDwk9Jk",
|
||||
};
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookingWindowPeriodOutputTypeEnum_2024_06_14,
|
||||
BookingLimitsEnum_2024_06_14,
|
||||
Frequency,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import {
|
||||
type CreateEventTypeInput_2024_06_14,
|
||||
type Integration_2024_06_14,
|
||||
type BusinessDaysWindow_2024_06_14,
|
||||
type RangeWindow_2024_06_14,
|
||||
type TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
type BookingLimitsKeyOutputType_2024_06_14,
|
||||
type TransformBookingLimitsSchema_2024_06_14,
|
||||
type TransformRecurringEventSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
const integrationsMapping: Record<Integration_2024_06_14, string> = {
|
||||
"cal-video": "integrations:daily",
|
||||
};
|
||||
|
||||
function transformApiEventTypeLocations(inputLocations: CreateEventTypeInput_2024_06_14["locations"]) {
|
||||
if (!inputLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return inputLocations.map((location) => {
|
||||
const { type } = location;
|
||||
switch (type) {
|
||||
case "address":
|
||||
return { type: "inPerson", address: location.address, displayLocationPublicly: location.public };
|
||||
case "link":
|
||||
return { type: "link", link: location.link, displayLocationPublicly: location.public };
|
||||
case "integration":
|
||||
const integrationLabel = integrationsMapping[location.integration];
|
||||
return { type: integrationLabel };
|
||||
case "phone":
|
||||
return {
|
||||
type: "userPhone",
|
||||
hostPhoneNumber: location.phone,
|
||||
displayLocationPublicly: location.public,
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported location type '${type}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const integrationsMappingSchema = {
|
||||
"cal-video": z.literal("integrations:daily"),
|
||||
};
|
||||
|
||||
const InPersonSchema = z.object({
|
||||
type: z.literal("inPerson"),
|
||||
address: z.string(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const LinkSchema = z.object({
|
||||
type: z.literal("link"),
|
||||
link: z.string().url(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const IntegrationSchema = z.object({
|
||||
type: z.union([integrationsMappingSchema["cal-video"], integrationsMappingSchema["cal-video"]]),
|
||||
});
|
||||
|
||||
const UserPhoneSchema = z.object({
|
||||
type: z.literal("userPhone"),
|
||||
hostPhoneNumber: z.string(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const TransformedLocationSchema = z.union([InPersonSchema, LinkSchema, IntegrationSchema, UserPhoneSchema]);
|
||||
export const TransformedLocationsSchema = z.array(TransformedLocationSchema);
|
||||
|
||||
function transformApiEventTypeBookingFields(
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]
|
||||
) {
|
||||
if (!inputBookingFields) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const customBookingFields = inputBookingFields.map((field) => {
|
||||
const commonFields: UserField = {
|
||||
name: field.slug,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: field.required,
|
||||
placeholder: "placeholder" in field && field.placeholder ? field.placeholder : "",
|
||||
};
|
||||
|
||||
const options = "options" in field && field.options ? transformSelectOptions(field.options) : undefined;
|
||||
|
||||
if (!options) {
|
||||
return commonFields;
|
||||
}
|
||||
|
||||
return {
|
||||
...commonFields,
|
||||
options,
|
||||
};
|
||||
});
|
||||
|
||||
return customBookingFields;
|
||||
}
|
||||
|
||||
function transformApiEventTypeIntervalLimits(
|
||||
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]
|
||||
) {
|
||||
const res: TransformBookingLimitsSchema_2024_06_14 = {};
|
||||
inputBookingLimits &&
|
||||
Object.entries(inputBookingLimits).map(([key, value]) => {
|
||||
const outputKey: BookingLimitsKeyOutputType_2024_06_14 = BookingLimitsEnum_2024_06_14[
|
||||
key as keyof typeof BookingLimitsEnum_2024_06_14
|
||||
] satisfies BookingLimitsKeyOutputType_2024_06_14;
|
||||
res[outputKey] = value;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
function transformApiEventTypeFutureBookingLimits(
|
||||
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingWindow"]
|
||||
): TransformFutureBookingsLimitSchema_2024_06_14 | undefined {
|
||||
switch (inputBookingLimits?.type) {
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays:
|
||||
return {
|
||||
periodDays: (inputBookingLimits as BusinessDaysWindow_2024_06_14).value,
|
||||
periodType: !!(inputBookingLimits as BusinessDaysWindow_2024_06_14).rolling
|
||||
? BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW
|
||||
: BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING,
|
||||
periodCountCalendarDays: false,
|
||||
};
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays:
|
||||
return {
|
||||
periodDays: (inputBookingLimits as BusinessDaysWindow_2024_06_14).value,
|
||||
periodType: !!(inputBookingLimits as BusinessDaysWindow_2024_06_14).rolling
|
||||
? BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW
|
||||
: BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING,
|
||||
periodCountCalendarDays: true,
|
||||
};
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.range:
|
||||
return {
|
||||
periodType: BookingWindowPeriodOutputTypeEnum_2024_06_14.RANGE,
|
||||
periodStartDate: new Date((inputBookingLimits as RangeWindow_2024_06_14).value[0]),
|
||||
periodEndDate: new Date((inputBookingLimits as RangeWindow_2024_06_14).value[1]),
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function transformApiEventTypeRecurrence(
|
||||
recurrence: CreateEventTypeInput_2024_06_14["recurrence"]
|
||||
): TransformRecurringEventSchema_2024_06_14 | undefined {
|
||||
if (!recurrence) return undefined;
|
||||
return {
|
||||
interval: recurrence.interval,
|
||||
count: recurrence.occurrences,
|
||||
freq: Frequency[recurrence.frequency as keyof typeof Frequency],
|
||||
} satisfies TransformRecurringEventSchema_2024_06_14;
|
||||
}
|
||||
|
||||
export function transformSelectOptions(options: string[]) {
|
||||
return options.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
}));
|
||||
}
|
||||
|
||||
const FieldTypeEnum = z.enum([
|
||||
"number",
|
||||
"boolean",
|
||||
"address",
|
||||
"name",
|
||||
"text",
|
||||
"textarea",
|
||||
"email",
|
||||
"phone",
|
||||
"multiemail",
|
||||
"select",
|
||||
"multiselect",
|
||||
"checkbox",
|
||||
"radio",
|
||||
"radioInput",
|
||||
]);
|
||||
|
||||
const UserFieldsSchema = z.object({
|
||||
name: z.string(),
|
||||
type: FieldTypeEnum,
|
||||
label: z.string(),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("user"),
|
||||
type: z.literal("user"),
|
||||
label: z.literal("User"),
|
||||
fieldRequired: z.literal(true),
|
||||
})
|
||||
),
|
||||
editable: z.literal("user"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string().optional(),
|
||||
options: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const SystemFieldsSchema = z.object({
|
||||
name: z.string(),
|
||||
type: FieldTypeEnum,
|
||||
defaultLabel: z.string(),
|
||||
label: z.string().optional(),
|
||||
editable: z.enum(["system-but-optional", "system"]),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("default"),
|
||||
type: z.literal("default"),
|
||||
label: z.literal("Default"),
|
||||
})
|
||||
),
|
||||
views: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.enum(["reschedule"]),
|
||||
label: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
defaultPlaceholder: z.enum(["", "share_additional_notes", "email", "reschedule_placeholder"]).optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
required: z.boolean(),
|
||||
hideWhenJustOneOption: z.boolean().optional(),
|
||||
getOptionsAt: z.enum(["locations"]).optional(),
|
||||
optionsInputs: z
|
||||
.object({
|
||||
attendeeInPerson: z.object({
|
||||
type: z.literal("address"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string(),
|
||||
}),
|
||||
phone: z.object({
|
||||
type: z.literal("phone"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string(),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type SystemField = z.infer<typeof SystemFieldsSchema>;
|
||||
|
||||
export type UserField = z.infer<typeof UserFieldsSchema>;
|
||||
|
||||
export const BookingFieldsSchema = z.array(z.union([UserFieldsSchema, SystemFieldsSchema]));
|
||||
|
||||
export {
|
||||
transformApiEventTypeLocations,
|
||||
transformApiEventTypeBookingFields,
|
||||
transformApiEventTypeIntervalLimits,
|
||||
transformApiEventTypeFutureBookingLimits,
|
||||
transformApiEventTypeRecurrence,
|
||||
};
|
||||
@@ -1,279 +0,0 @@
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookingWindowPeriodOutputTypeEnum_2024_06_14,
|
||||
BookingLimitsEnum_2024_06_14,
|
||||
Frequency,
|
||||
FrequencyInput,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
AddressLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
LinkLocation_2024_06_14,
|
||||
PhoneLocation_2024_06_14,
|
||||
Integration_2024_06_14,
|
||||
BookingLimitsKeysInputType,
|
||||
TransformBookingLimitsSchema_2024_06_14,
|
||||
TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
BookingWindow_2024_06_14,
|
||||
RangeWindow_2024_06_14,
|
||||
CalendarDaysWindow_2024_06_14,
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
BookingField_2024_06_14,
|
||||
Recurrence_2024_06_14,
|
||||
TransformRecurringEventSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { transformApiEventTypeBookingFields, transformApiEventTypeLocations } from "./api-request";
|
||||
|
||||
const reverseIntegrationsMapping: Record<string, Integration_2024_06_14> = {
|
||||
"integrations:daily": "cal-video",
|
||||
};
|
||||
|
||||
function getResponseEventTypeLocations(
|
||||
transformedLocations: ReturnType<typeof transformApiEventTypeLocations>
|
||||
) {
|
||||
if (!transformedLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return transformedLocations.map((location) => {
|
||||
switch (location.type) {
|
||||
case "inPerson": {
|
||||
if (!location.address) {
|
||||
throw new Error("Address location must have an address");
|
||||
}
|
||||
const addressLocation: AddressLocation_2024_06_14 = {
|
||||
type: "address",
|
||||
address: location.address,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return addressLocation;
|
||||
}
|
||||
case "link": {
|
||||
if (!location.link) {
|
||||
throw new Error("Link location must have a link");
|
||||
}
|
||||
const linkLocation: LinkLocation_2024_06_14 = {
|
||||
type: "link",
|
||||
link: location.link,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return linkLocation;
|
||||
}
|
||||
case "userPhone": {
|
||||
if (!location.hostPhoneNumber) {
|
||||
throw new Error("Phone location must have a phone number");
|
||||
}
|
||||
const phoneLocation: PhoneLocation_2024_06_14 = {
|
||||
type: "phone",
|
||||
phone: location.hostPhoneNumber,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return phoneLocation;
|
||||
}
|
||||
default: {
|
||||
const integrationType = reverseIntegrationsMapping[location.type];
|
||||
if (!integrationType) {
|
||||
throw new Error(`Unsupported integration type '${location.type}'.`);
|
||||
}
|
||||
const integration: IntegrationLocation_2024_06_14 = {
|
||||
type: "integration",
|
||||
integration: integrationType,
|
||||
};
|
||||
return integration;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getResponseEventTypeBookingFields(
|
||||
transformedBookingFields: ReturnType<typeof transformApiEventTypeBookingFields>
|
||||
): BookingField_2024_06_14[] {
|
||||
if (!transformedBookingFields) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return transformedBookingFields.map((field) => {
|
||||
switch (field.type) {
|
||||
case "name":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "email":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "phone":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "address":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "text":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "textarea":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "multiemail":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "boolean":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
options: field.options ? field.options.map((option) => option.value) : [],
|
||||
};
|
||||
case "multiselect":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "checkbox":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "radio":
|
||||
return {
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported booking field type '${field.type}'.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getResponseEventTypeIntervalLimits(
|
||||
transformedBookingFields: TransformBookingLimitsSchema_2024_06_14 | null
|
||||
) {
|
||||
if (!transformedBookingFields) {
|
||||
return undefined;
|
||||
}
|
||||
const res: { [K in BookingLimitsKeysInputType]?: number } = {};
|
||||
transformedBookingFields &&
|
||||
Object.entries(transformedBookingFields).map(([key, value]) => {
|
||||
const outputKey: BookingLimitsKeysInputType | undefined = Object.keys(
|
||||
BookingLimitsEnum_2024_06_14
|
||||
).find(
|
||||
(item) => BookingLimitsEnum_2024_06_14[item as keyof typeof BookingLimitsEnum_2024_06_14] === key
|
||||
) as BookingLimitsKeysInputType;
|
||||
|
||||
if (outputKey) {
|
||||
res[outputKey] = value as number;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
function getResponseEventTypeFutureBookingLimits(
|
||||
transformedFutureBookingsLimitsFields: TransformFutureBookingsLimitSchema_2024_06_14
|
||||
): BookingWindow_2024_06_14 | undefined {
|
||||
switch (transformedFutureBookingsLimitsFields?.periodType) {
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.RANGE:
|
||||
return {
|
||||
type: BookingWindowPeriodInputTypeEnum_2024_06_14.range,
|
||||
value: [
|
||||
transformedFutureBookingsLimitsFields?.periodStartDate?.toISOString().split("T")[0],
|
||||
transformedFutureBookingsLimitsFields?.periodEndDate?.toISOString().split("T")[0],
|
||||
],
|
||||
} as RangeWindow_2024_06_14;
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW:
|
||||
return {
|
||||
type: transformedFutureBookingsLimitsFields.periodCountCalendarDays
|
||||
? BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays
|
||||
: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
|
||||
value: transformedFutureBookingsLimitsFields.periodDays,
|
||||
rolling: true,
|
||||
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING:
|
||||
return {
|
||||
type: transformedFutureBookingsLimitsFields.periodCountCalendarDays
|
||||
? BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays
|
||||
: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
|
||||
value: transformedFutureBookingsLimitsFields.periodDays,
|
||||
rolling: false,
|
||||
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getResponseEventTypeRecurrence(
|
||||
transformRecurringEvent: TransformRecurringEventSchema_2024_06_14
|
||||
): Recurrence_2024_06_14 {
|
||||
return {
|
||||
interval: transformRecurringEvent.interval,
|
||||
occurrences: transformRecurringEvent.count,
|
||||
frequency: FrequencyInput[Frequency[transformRecurringEvent.freq] as keyof typeof FrequencyInput],
|
||||
} satisfies Recurrence_2024_06_14;
|
||||
}
|
||||
|
||||
export {
|
||||
getResponseEventTypeLocations,
|
||||
getResponseEventTypeBookingFields,
|
||||
getResponseEventTypeIntervalLimits,
|
||||
getResponseEventTypeFutureBookingLimits,
|
||||
getResponseEventTypeRecurrence,
|
||||
};
|
||||
+73
-143
@@ -2,24 +2,24 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import { FrequencyInput } from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
BookingField_2024_06_14,
|
||||
InputBookingField_2024_06_14,
|
||||
Location_2024_06_14,
|
||||
BookingLimitsCount_2024_06_14,
|
||||
BookingWindow_2024_06_14,
|
||||
Recurrence_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { UserField } from "./api-request";
|
||||
import type { CustomField } from "../internal-to-api/booking-fields";
|
||||
import {
|
||||
transformApiEventTypeLocations,
|
||||
transformApiEventTypeBookingFields,
|
||||
transformSelectOptions,
|
||||
transformApiEventTypeIntervalLimits,
|
||||
transformApiEventTypeFutureBookingLimits,
|
||||
transformApiEventTypeRecurrence,
|
||||
} from "./api-request";
|
||||
transformLocationsApiToInternal,
|
||||
transformBookingFieldsApiToInternal,
|
||||
transformSelectOptionsApiToInternal,
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
} from "./index";
|
||||
|
||||
describe("transformApiEventTypeLocations", () => {
|
||||
describe("transformLocationsApiToInternal", () => {
|
||||
it("should transform address", () => {
|
||||
const input: Location_2024_06_14[] = [
|
||||
{
|
||||
@@ -31,7 +31,7 @@ describe("transformApiEventTypeLocations", () => {
|
||||
|
||||
const expectedOutput = [{ type: "inPerson", address: "London road 10-1", displayLocationPublicly: true }];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
const result = transformLocationsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -49,7 +49,7 @@ describe("transformApiEventTypeLocations", () => {
|
||||
{ type: "link", link: "https://customvideo.com/join/123456", displayLocationPublicly: true },
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
const result = transformLocationsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -64,7 +64,7 @@ describe("transformApiEventTypeLocations", () => {
|
||||
|
||||
const expectedOutput = [{ type: "integrations:daily" }];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
const result = transformLocationsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -82,85 +82,15 @@ describe("transformApiEventTypeLocations", () => {
|
||||
{ type: "userPhone", hostPhoneNumber: "+37120993151", displayLocationPublicly: true },
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeLocations(input);
|
||||
const result = transformLocationsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformApiEventTypeBookingFields", () => {
|
||||
it("should transform name field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "name",
|
||||
slug: "name",
|
||||
label: "Your name",
|
||||
required: true,
|
||||
placeholder: "alice",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform email field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
type: "email",
|
||||
slug: "email",
|
||||
label: "Your email",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
describe("transformBookingFieldsApiToInternal", () => {
|
||||
it("should transform phone field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "phone",
|
||||
slug: "phone",
|
||||
label: "Your phone number",
|
||||
@@ -168,9 +98,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
placeholder: "123456789",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -189,13 +119,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform address field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "address",
|
||||
slug: "address",
|
||||
label: "Your address",
|
||||
@@ -203,9 +133,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
placeholder: "1234 Main St",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -224,13 +154,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform text field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "text",
|
||||
slug: "text",
|
||||
label: "Your text",
|
||||
@@ -238,9 +168,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
placeholder: "Enter your text",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -259,13 +189,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform number field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "number",
|
||||
slug: "number",
|
||||
label: "Your number",
|
||||
@@ -273,9 +203,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
placeholder: "100",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -294,13 +224,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform textarea field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "textarea",
|
||||
slug: "textarea",
|
||||
label: "Your detailed information",
|
||||
@@ -308,9 +238,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
placeholder: "Detailed description here...",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -329,13 +259,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform select field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "select",
|
||||
slug: "select",
|
||||
label: "Your selection",
|
||||
@@ -344,9 +274,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
options: ["Option 1", "Option 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -362,17 +292,17 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: bookingField.placeholder,
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
options: transformSelectOptionsApiToInternal(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform multiselect field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "multiselect",
|
||||
slug: "multiselect",
|
||||
label: "Your multiple selections",
|
||||
@@ -380,9 +310,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
options: ["Option 1", "Option 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -398,17 +328,17 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
options: transformSelectOptionsApiToInternal(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform multiemail field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "multiemail",
|
||||
slug: "multiemail",
|
||||
label: "Your multiple emails",
|
||||
@@ -416,9 +346,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
placeholder: "example@example.com",
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -437,13 +367,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform checkbox field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "checkbox",
|
||||
slug: "checkbox",
|
||||
label: "Your checkboxes",
|
||||
@@ -451,9 +381,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
options: ["Checkbox 1", "Checkbox 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -469,17 +399,17 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
options: transformSelectOptionsApiToInternal(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform radio field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "radio",
|
||||
slug: "radio",
|
||||
label: "Your radio buttons",
|
||||
@@ -487,9 +417,9 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
options: ["Radio 1", "Radio 2"],
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -505,26 +435,26 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptions(bookingField.options),
|
||||
options: transformSelectOptionsApiToInternal(bookingField.options),
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform boolean field", () => {
|
||||
const bookingField: BookingField_2024_06_14 = {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "boolean",
|
||||
slug: "boolean",
|
||||
label: "Agree to terms?",
|
||||
required: true,
|
||||
};
|
||||
|
||||
const input: BookingField_2024_06_14[] = [bookingField];
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: UserField[] = [
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
@@ -543,13 +473,13 @@ describe("transformApiEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformApiEventTypeBookingFields(input);
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformApiEventTypeIntervalLimits", () => {
|
||||
describe("transformIntervalLimitsApiToInternal", () => {
|
||||
it("should transform booking limits count or booking limits duration", () => {
|
||||
const input: BookingLimitsCount_2024_06_14 = {
|
||||
day: 2,
|
||||
@@ -564,13 +494,13 @@ describe("transformApiEventTypeIntervalLimits", () => {
|
||||
PER_MONTH: 22,
|
||||
PER_YEAR: 33,
|
||||
};
|
||||
const result = transformApiEventTypeIntervalLimits(input);
|
||||
const result = transformIntervalLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformApiEventTypeFutureBookingLimits", () => {
|
||||
describe("transformFutureBookingLimitsApiToInternal", () => {
|
||||
it("should transform range type", () => {
|
||||
const input: BookingWindow_2024_06_14 = {
|
||||
type: "range",
|
||||
@@ -583,7 +513,7 @@ describe("transformApiEventTypeFutureBookingLimits", () => {
|
||||
periodEndDate: new Date("2024-08-28"),
|
||||
};
|
||||
|
||||
const result = transformApiEventTypeFutureBookingLimits(input);
|
||||
const result = transformFutureBookingLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -600,7 +530,7 @@ describe("transformApiEventTypeFutureBookingLimits", () => {
|
||||
periodCountCalendarDays: true,
|
||||
};
|
||||
|
||||
const result = transformApiEventTypeFutureBookingLimits(input);
|
||||
const result = transformFutureBookingLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -617,7 +547,7 @@ describe("transformApiEventTypeFutureBookingLimits", () => {
|
||||
periodCountCalendarDays: true,
|
||||
};
|
||||
|
||||
const result = transformApiEventTypeFutureBookingLimits(input);
|
||||
const result = transformFutureBookingLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -634,7 +564,7 @@ describe("transformApiEventTypeFutureBookingLimits", () => {
|
||||
periodCountCalendarDays: false,
|
||||
};
|
||||
|
||||
const result = transformApiEventTypeFutureBookingLimits(input);
|
||||
const result = transformFutureBookingLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -651,13 +581,13 @@ describe("transformApiEventTypeFutureBookingLimits", () => {
|
||||
periodCountCalendarDays: false,
|
||||
};
|
||||
|
||||
const result = transformApiEventTypeFutureBookingLimits(input);
|
||||
const result = transformFutureBookingLimitsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transformApiEventTypeRecurrence", () => {
|
||||
describe("transformRecurrenceApiToInternal", () => {
|
||||
it("should transform recurrence", () => {
|
||||
const input: Recurrence_2024_06_14 = {
|
||||
frequency: FrequencyInput.weekly,
|
||||
@@ -669,7 +599,7 @@ describe("transformApiEventTypeRecurrence", () => {
|
||||
count: 10,
|
||||
freq: 2,
|
||||
};
|
||||
const result = transformApiEventTypeRecurrence(input);
|
||||
const result = transformRecurrenceApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { type CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
import type { CustomField } from "../internal-to-api";
|
||||
|
||||
export function transformBookingFieldsApiToInternal(
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]
|
||||
) {
|
||||
if (!inputBookingFields) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const customBookingFields = inputBookingFields.map((field) => {
|
||||
const commonFields: CustomField = {
|
||||
name: field.slug,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: field.required,
|
||||
placeholder: "placeholder" in field && field.placeholder ? field.placeholder : "",
|
||||
};
|
||||
|
||||
const options =
|
||||
"options" in field && field.options ? transformSelectOptionsApiToInternal(field.options) : undefined;
|
||||
|
||||
if (!options) {
|
||||
return commonFields;
|
||||
}
|
||||
|
||||
return {
|
||||
...commonFields,
|
||||
options,
|
||||
};
|
||||
});
|
||||
|
||||
return customBookingFields;
|
||||
}
|
||||
|
||||
export function transformSelectOptionsApiToInternal(options: string[]) {
|
||||
return options.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookingWindowPeriodOutputTypeEnum_2024_06_14,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import {
|
||||
type CreateEventTypeInput_2024_06_14,
|
||||
type BusinessDaysWindow_2024_06_14,
|
||||
type RangeWindow_2024_06_14,
|
||||
type TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformFutureBookingLimitsApiToInternal(
|
||||
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingWindow"]
|
||||
): TransformFutureBookingsLimitSchema_2024_06_14 | undefined {
|
||||
switch (inputBookingLimits?.type) {
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays:
|
||||
return {
|
||||
periodDays: (inputBookingLimits as BusinessDaysWindow_2024_06_14).value,
|
||||
periodType: !!(inputBookingLimits as BusinessDaysWindow_2024_06_14).rolling
|
||||
? BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW
|
||||
: BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING,
|
||||
periodCountCalendarDays: false,
|
||||
};
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays:
|
||||
return {
|
||||
periodDays: (inputBookingLimits as BusinessDaysWindow_2024_06_14).value,
|
||||
periodType: !!(inputBookingLimits as BusinessDaysWindow_2024_06_14).rolling
|
||||
? BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW
|
||||
: BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING,
|
||||
periodCountCalendarDays: true,
|
||||
};
|
||||
case BookingWindowPeriodInputTypeEnum_2024_06_14.range:
|
||||
return {
|
||||
periodType: BookingWindowPeriodOutputTypeEnum_2024_06_14.RANGE,
|
||||
periodStartDate: new Date((inputBookingLimits as RangeWindow_2024_06_14).value[0]),
|
||||
periodEndDate: new Date((inputBookingLimits as RangeWindow_2024_06_14).value[1]),
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./booking-fields";
|
||||
export * from "./locations";
|
||||
export * from "./recurrence";
|
||||
export * from "./interval-limits";
|
||||
export * from "./future-booking-limits";
|
||||
@@ -0,0 +1,20 @@
|
||||
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-enums/monorepo";
|
||||
import {
|
||||
type CreateEventTypeInput_2024_06_14,
|
||||
type BookingLimitsKeyOutputType_2024_06_14,
|
||||
type TransformBookingLimitsSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformIntervalLimitsApiToInternal(
|
||||
inputBookingLimits: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]
|
||||
) {
|
||||
const res: TransformBookingLimitsSchema_2024_06_14 = {};
|
||||
inputBookingLimits &&
|
||||
Object.entries(inputBookingLimits).map(([key, value]) => {
|
||||
const outputKey: BookingLimitsKeyOutputType_2024_06_14 = BookingLimitsEnum_2024_06_14[
|
||||
key as keyof typeof BookingLimitsEnum_2024_06_14
|
||||
] satisfies BookingLimitsKeyOutputType_2024_06_14;
|
||||
res[outputKey] = value;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { type CreateEventTypeInput_2024_06_14, type Integration_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
const integrationsMapping: Record<Integration_2024_06_14, "integrations:daily"> = {
|
||||
"cal-video": "integrations:daily",
|
||||
};
|
||||
|
||||
export function transformLocationsApiToInternal(
|
||||
inputLocations: CreateEventTypeInput_2024_06_14["locations"]
|
||||
) {
|
||||
if (!inputLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return inputLocations.map((location) => {
|
||||
const { type } = location;
|
||||
switch (type) {
|
||||
case "address":
|
||||
return {
|
||||
type: "inPerson",
|
||||
address: location.address,
|
||||
displayLocationPublicly: location.public,
|
||||
} satisfies InPersonLocation;
|
||||
case "link":
|
||||
return {
|
||||
type: "link",
|
||||
link: location.link,
|
||||
displayLocationPublicly: location.public,
|
||||
} satisfies LinkLocation;
|
||||
case "integration":
|
||||
const integrationLabel = integrationsMapping[location.integration];
|
||||
return { type: integrationLabel } satisfies IntegrationLocation;
|
||||
case "phone":
|
||||
return {
|
||||
type: "userPhone",
|
||||
hostPhoneNumber: location.phone,
|
||||
displayLocationPublicly: location.public,
|
||||
} satisfies UserPhoneLocation;
|
||||
default:
|
||||
throw new Error(`Unsupported location type '${type}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const integrationsMappingSchema = {
|
||||
"cal-video": z.literal("integrations:daily"),
|
||||
};
|
||||
|
||||
const InPersonSchema = z.object({
|
||||
type: z.literal("inPerson"),
|
||||
address: z.string(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const LinkSchema = z.object({
|
||||
type: z.literal("link"),
|
||||
link: z.string().url(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const IntegrationSchema = z.object({
|
||||
type: z.union([integrationsMappingSchema["cal-video"], integrationsMappingSchema["cal-video"]]),
|
||||
});
|
||||
|
||||
const UserPhoneSchema = z.object({
|
||||
type: z.literal("userPhone"),
|
||||
hostPhoneNumber: z.string(),
|
||||
displayLocationPublicly: z.boolean().default(false),
|
||||
});
|
||||
|
||||
type InPersonLocation = z.infer<typeof InPersonSchema>;
|
||||
type LinkLocation = z.infer<typeof LinkSchema>;
|
||||
type IntegrationLocation = z.infer<typeof IntegrationSchema>;
|
||||
type UserPhoneLocation = z.infer<typeof UserPhoneSchema>;
|
||||
|
||||
const TransformedLocationSchema = z.union([InPersonSchema, LinkSchema, IntegrationSchema, UserPhoneSchema]);
|
||||
export const TransformedLocationsSchema = z.array(TransformedLocationSchema);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Frequency } from "@calcom/platform-enums/monorepo";
|
||||
import {
|
||||
type CreateEventTypeInput_2024_06_14,
|
||||
type TransformRecurringEventSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformRecurrenceApiToInternal(
|
||||
recurrence: CreateEventTypeInput_2024_06_14["recurrence"]
|
||||
): TransformRecurringEventSchema_2024_06_14 | undefined {
|
||||
if (!recurrence) return undefined;
|
||||
return {
|
||||
interval: recurrence.interval,
|
||||
count: recurrence.occurrences,
|
||||
freq: Frequency[recurrence.frequency as keyof typeof Frequency],
|
||||
} satisfies TransformRecurringEventSchema_2024_06_14;
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./api-request";
|
||||
export * from "./api-response";
|
||||
export * from "./api-to-internal";
|
||||
export * from "./internal-to-api";
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type {
|
||||
OutputBookingField_2024_06_14,
|
||||
DefaultFieldOutput_2024_06_14,
|
||||
CustomFieldOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformBookingFieldsInternalToApi(
|
||||
databaseBookingFields: (SystemField | CustomField)[]
|
||||
): OutputBookingField_2024_06_14[] {
|
||||
const defaultFields: SystemField[] = databaseBookingFields.filter(
|
||||
(field): field is SystemField => field.editable === "system" || field.editable === "system-but-optional"
|
||||
);
|
||||
|
||||
const customFields: CustomField[] = databaseBookingFields.filter(
|
||||
(field): field is CustomField => field.editable === "user"
|
||||
);
|
||||
|
||||
const responseDefaultFields: DefaultFieldOutput_2024_06_14[] = defaultFields.map((field) => {
|
||||
switch (field.name) {
|
||||
case "name":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
case "email":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
case "location":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
case "rescheduleReason":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
case "title":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
case "notes":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
case "guests":
|
||||
return {
|
||||
isDefault: true,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
};
|
||||
default:
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
throw new Error(`Unsupported default booking field '${field.name}'.`);
|
||||
}
|
||||
});
|
||||
|
||||
const responseCustomFields: CustomFieldOutput_2024_06_14[] = customFields.map((field) => {
|
||||
switch (field.type) {
|
||||
case "phone":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "address":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "text":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "textarea":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "multiemail":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
};
|
||||
case "boolean":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
options: field.options ? field.options.map((option) => option.value) : [],
|
||||
};
|
||||
case "multiselect":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "checkbox":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
case "radio":
|
||||
return {
|
||||
isDefault: false,
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported booking field type '${field.type}'.`);
|
||||
}
|
||||
});
|
||||
|
||||
return [...responseDefaultFields, ...responseCustomFields];
|
||||
}
|
||||
|
||||
const CustomFieldTypeEnum = z.enum([
|
||||
"number",
|
||||
"boolean",
|
||||
"address",
|
||||
"text",
|
||||
"textarea",
|
||||
"phone",
|
||||
"multiemail",
|
||||
"select",
|
||||
"multiselect",
|
||||
"checkbox",
|
||||
"radio",
|
||||
"radioInput",
|
||||
]);
|
||||
|
||||
const CustomFieldsSchema = z.object({
|
||||
name: z.string(),
|
||||
type: CustomFieldTypeEnum,
|
||||
label: z.string(),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("user"),
|
||||
type: z.literal("user"),
|
||||
label: z.literal("User"),
|
||||
fieldRequired: z.literal(true),
|
||||
})
|
||||
),
|
||||
editable: z.literal("user"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string().optional(),
|
||||
options: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const SystemFieldSchema = z.object({
|
||||
defaultLabel: z.string(),
|
||||
label: z.string().optional(),
|
||||
editable: z.enum(["system-but-optional", "system"]),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("default"),
|
||||
type: z.literal("default"),
|
||||
label: z.literal("Default"),
|
||||
})
|
||||
),
|
||||
views: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.enum(["reschedule"]),
|
||||
label: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
defaultPlaceholder: z.enum(["", "share_additional_notes", "email", "reschedule_placeholder"]).optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
required: z.boolean(),
|
||||
hideWhenJustOneOption: z.boolean().optional(),
|
||||
getOptionsAt: z.enum(["locations"]).optional(),
|
||||
optionsInputs: z
|
||||
.object({
|
||||
attendeeInPerson: z.object({
|
||||
type: z.literal("address"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string(),
|
||||
}),
|
||||
phone: z.object({
|
||||
type: z.literal("phone"),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string(),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const NameSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("name"),
|
||||
type: z.literal("name"),
|
||||
required: z.literal(true),
|
||||
});
|
||||
|
||||
const EmailSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("email"),
|
||||
type: z.literal("email"),
|
||||
required: z.literal(true),
|
||||
});
|
||||
|
||||
const RescheduleReasonSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("rescheduleReason"),
|
||||
type: z.literal("textarea"),
|
||||
required: z.literal(false),
|
||||
});
|
||||
|
||||
const LocationReasonSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("location"),
|
||||
type: z.literal("radioInput"),
|
||||
required: z.literal(false),
|
||||
});
|
||||
|
||||
const TitleSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("title"),
|
||||
type: z.literal("text"),
|
||||
required: z.literal(true),
|
||||
});
|
||||
|
||||
const NotesSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("notes"),
|
||||
type: z.literal("textarea"),
|
||||
required: z.literal(false),
|
||||
});
|
||||
|
||||
const GuestsSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("guests"),
|
||||
type: z.literal("multiemail"),
|
||||
required: z.literal(false),
|
||||
});
|
||||
|
||||
type NameSystemField = z.infer<typeof NameSystemFieldSchema>;
|
||||
type EmailSystemField = z.infer<typeof EmailSystemFieldSchema>;
|
||||
type RescheduleReasonSystemField = z.infer<typeof RescheduleReasonSystemFieldSchema>;
|
||||
type LocationReasonSystemField = z.infer<typeof LocationReasonSystemFieldSchema>;
|
||||
type TitleSystemField = z.infer<typeof TitleSystemFieldSchema>;
|
||||
type NotesSystemField = z.infer<typeof NotesSystemFieldSchema>;
|
||||
type GuestsSystemField = z.infer<typeof GuestsSystemFieldSchema>;
|
||||
|
||||
export type SystemField =
|
||||
| NameSystemField
|
||||
| EmailSystemField
|
||||
| RescheduleReasonSystemField
|
||||
| LocationReasonSystemField
|
||||
| TitleSystemField
|
||||
| NotesSystemField
|
||||
| GuestsSystemField;
|
||||
|
||||
export type CustomField = z.infer<typeof CustomFieldsSchema>;
|
||||
|
||||
const SystemFieldsSchema = z.union([
|
||||
NameSystemFieldSchema,
|
||||
EmailSystemFieldSchema,
|
||||
LocationReasonSystemFieldSchema,
|
||||
RescheduleReasonSystemFieldSchema,
|
||||
TitleSystemFieldSchema,
|
||||
NotesSystemFieldSchema,
|
||||
GuestsSystemFieldSchema,
|
||||
]);
|
||||
|
||||
export const BookingFieldsSchema = z.array(z.union([CustomFieldsSchema, SystemFieldsSchema]));
|
||||
|
||||
export const systemBeforeFieldName: NameSystemField = {
|
||||
type: "name",
|
||||
name: "name",
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
required: true,
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const systemBeforeFieldEmail: EmailSystemField = {
|
||||
defaultLabel: "email_address",
|
||||
type: "email",
|
||||
name: "email",
|
||||
required: true,
|
||||
editable: "system",
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const systemBeforeFieldLocation: LocationReasonSystemField = {
|
||||
defaultLabel: "location",
|
||||
type: "radioInput",
|
||||
name: "location",
|
||||
editable: "system",
|
||||
hideWhenJustOneOption: true,
|
||||
required: false,
|
||||
getOptionsAt: "locations",
|
||||
optionsInputs: {
|
||||
attendeeInPerson: {
|
||||
type: "address",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
phone: {
|
||||
type: "phone",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
},
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const systemAfterFieldRescheduleReason: RescheduleReasonSystemField = {
|
||||
defaultLabel: "reason_for_reschedule",
|
||||
type: "textarea",
|
||||
editable: "system-but-optional",
|
||||
name: "rescheduleReason",
|
||||
defaultPlaceholder: "reschedule_placeholder",
|
||||
required: false,
|
||||
views: [
|
||||
{
|
||||
id: "reschedule",
|
||||
label: "Reschedule View",
|
||||
},
|
||||
],
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookingWindowPeriodOutputTypeEnum_2024_06_14,
|
||||
} from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
BookingWindow_2024_06_14,
|
||||
RangeWindow_2024_06_14,
|
||||
CalendarDaysWindow_2024_06_14,
|
||||
BusinessDaysWindow_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformFutureBookingLimitsInternalToApi(
|
||||
transformedFutureBookingsLimitsFields: TransformFutureBookingsLimitSchema_2024_06_14
|
||||
): BookingWindow_2024_06_14 | undefined {
|
||||
switch (transformedFutureBookingsLimitsFields?.periodType) {
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.RANGE:
|
||||
return {
|
||||
type: BookingWindowPeriodInputTypeEnum_2024_06_14.range,
|
||||
value: [
|
||||
transformedFutureBookingsLimitsFields?.periodStartDate?.toISOString().split("T")[0],
|
||||
transformedFutureBookingsLimitsFields?.periodEndDate?.toISOString().split("T")[0],
|
||||
],
|
||||
} as RangeWindow_2024_06_14;
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING_WINDOW:
|
||||
return {
|
||||
type: transformedFutureBookingsLimitsFields.periodCountCalendarDays
|
||||
? BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays
|
||||
: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
|
||||
value: transformedFutureBookingsLimitsFields.periodDays,
|
||||
rolling: true,
|
||||
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
|
||||
case BookingWindowPeriodOutputTypeEnum_2024_06_14.ROLLING:
|
||||
return {
|
||||
type: transformedFutureBookingsLimitsFields.periodCountCalendarDays
|
||||
? BookingWindowPeriodInputTypeEnum_2024_06_14.calendarDays
|
||||
: BookingWindowPeriodInputTypeEnum_2024_06_14.businessDays,
|
||||
value: transformedFutureBookingsLimitsFields.periodDays,
|
||||
rolling: false,
|
||||
} as CalendarDaysWindow_2024_06_14 | BusinessDaysWindow_2024_06_14;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./booking-fields";
|
||||
export * from "./locations";
|
||||
export * from "./recurrence";
|
||||
export * from "./interval-limits";
|
||||
export * from "./future-booking-limits";
|
||||
+56
-115
@@ -10,16 +10,16 @@ import type {
|
||||
TransformRecurringEventSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { UserField } from "./api-request";
|
||||
import {
|
||||
getResponseEventTypeLocations,
|
||||
getResponseEventTypeBookingFields,
|
||||
getResponseEventTypeIntervalLimits,
|
||||
getResponseEventTypeFutureBookingLimits,
|
||||
getResponseEventTypeRecurrence,
|
||||
} from "./api-response";
|
||||
transformLocationsInternalToApi,
|
||||
transformBookingFieldsInternalToApi,
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
} from ".";
|
||||
import type { CustomField } from "./booking-fields";
|
||||
|
||||
describe("getResponseEventTypeLocations", () => {
|
||||
describe("transformLocationsInternalToApi", () => {
|
||||
it("should reverse transform address location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
@@ -37,7 +37,7 @@ describe("getResponseEventTypeLocations", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
const result = transformLocationsInternalToApi(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -59,7 +59,7 @@ describe("getResponseEventTypeLocations", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
const result = transformLocationsInternalToApi(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -81,7 +81,7 @@ describe("getResponseEventTypeLocations", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
const result = transformLocationsInternalToApi(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -100,85 +100,15 @@ describe("getResponseEventTypeLocations", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeLocations(transformedLocation);
|
||||
const result = transformLocationsInternalToApi(transformedLocation);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResponseEventTypeBookingFields", () => {
|
||||
it("should reverse transform name field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
{
|
||||
name: "your-name",
|
||||
type: "name",
|
||||
label: "Your name",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "alice",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "name",
|
||||
slug: "your-name",
|
||||
label: "Your name",
|
||||
required: true,
|
||||
placeholder: "alice",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform email field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
{
|
||||
name: "your-email",
|
||||
type: "email",
|
||||
label: "Your email",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "email",
|
||||
slug: "your-email",
|
||||
label: "Your email",
|
||||
required: true,
|
||||
placeholder: "example@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
describe("transformBookingFieldsInternalToApi", () => {
|
||||
it("should reverse transform phone field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-phone",
|
||||
type: "phone",
|
||||
@@ -199,6 +129,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "phone",
|
||||
slug: "your-phone",
|
||||
label: "Your phone number",
|
||||
@@ -207,13 +138,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform address field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-address",
|
||||
type: "address",
|
||||
@@ -234,6 +165,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "address",
|
||||
slug: "your-address",
|
||||
label: "Your address",
|
||||
@@ -242,13 +174,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform text field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-text",
|
||||
type: "text",
|
||||
@@ -269,6 +201,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "text",
|
||||
slug: "your-text",
|
||||
label: "Your text",
|
||||
@@ -277,13 +210,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform number field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-number",
|
||||
type: "number",
|
||||
@@ -304,6 +237,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "number",
|
||||
slug: "your-number",
|
||||
label: "Your number",
|
||||
@@ -312,13 +246,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform textarea field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-textarea",
|
||||
type: "textarea",
|
||||
@@ -339,6 +273,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "textarea",
|
||||
slug: "your-textarea",
|
||||
label: "Your detailed information",
|
||||
@@ -347,13 +282,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform select field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-select",
|
||||
type: "select",
|
||||
@@ -378,6 +313,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "select",
|
||||
slug: "your-select",
|
||||
label: "Your selection",
|
||||
@@ -387,13 +323,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform multiselect field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-multiselect",
|
||||
type: "multiselect",
|
||||
@@ -417,6 +353,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "multiselect",
|
||||
slug: "your-multiselect",
|
||||
label: "Your multiple selections",
|
||||
@@ -425,13 +362,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform multiemail field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-multiemail",
|
||||
type: "multiemail",
|
||||
@@ -452,6 +389,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "multiemail",
|
||||
slug: "your-multiemail",
|
||||
label: "Your multiple emails",
|
||||
@@ -460,13 +398,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform checkbox field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-checkbox",
|
||||
type: "checkbox",
|
||||
@@ -490,6 +428,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "checkbox",
|
||||
slug: "your-checkbox",
|
||||
label: "Your checkboxes",
|
||||
@@ -498,13 +437,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform radio field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "your-radio",
|
||||
type: "radio",
|
||||
@@ -528,6 +467,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "radio",
|
||||
slug: "your-radio",
|
||||
label: "Your radio buttons",
|
||||
@@ -536,13 +476,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform boolean field", () => {
|
||||
const transformedField: UserField[] = [
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
name: "agree-to-terms",
|
||||
type: "boolean",
|
||||
@@ -563,6 +503,7 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
isDefault: false,
|
||||
type: "boolean",
|
||||
slug: "agree-to-terms",
|
||||
label: "Agree to terms?",
|
||||
@@ -570,13 +511,13 @@ describe("getResponseEventTypeBookingFields", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = getResponseEventTypeBookingFields(transformedField);
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResponseEventTypeIntervalLimits", () => {
|
||||
describe("transformIntervalLimitsInternalToApi", () => {
|
||||
it("should reverse transform booking limits count or booking limits duration", () => {
|
||||
const transformedField: TransformBookingLimitsSchema_2024_06_14 = {
|
||||
PER_DAY: 2,
|
||||
@@ -591,13 +532,13 @@ describe("getResponseEventTypeIntervalLimits", () => {
|
||||
month: 22,
|
||||
year: 33,
|
||||
};
|
||||
const result = getResponseEventTypeIntervalLimits(transformedField);
|
||||
const result = transformIntervalLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResponseEventTypeFutureBookingLimits", () => {
|
||||
describe("transformFutureBookingLimitsInternalToApi", () => {
|
||||
it("should reverse transform range type", () => {
|
||||
const transformedField: TransformFutureBookingsLimitSchema_2024_06_14 = {
|
||||
periodType: "RANGE",
|
||||
@@ -609,7 +550,7 @@ describe("getResponseEventTypeFutureBookingLimits", () => {
|
||||
value: ["2024-08-06", "2024-08-28"],
|
||||
};
|
||||
|
||||
const result = getResponseEventTypeFutureBookingLimits(transformedField);
|
||||
const result = transformFutureBookingLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -625,7 +566,7 @@ describe("getResponseEventTypeFutureBookingLimits", () => {
|
||||
rolling: false,
|
||||
};
|
||||
|
||||
const result = getResponseEventTypeFutureBookingLimits(transformedField);
|
||||
const result = transformFutureBookingLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -642,7 +583,7 @@ describe("getResponseEventTypeFutureBookingLimits", () => {
|
||||
rolling: true,
|
||||
};
|
||||
|
||||
const result = getResponseEventTypeFutureBookingLimits(transformedField);
|
||||
const result = transformFutureBookingLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -659,7 +600,7 @@ describe("getResponseEventTypeFutureBookingLimits", () => {
|
||||
rolling: false,
|
||||
};
|
||||
|
||||
const result = getResponseEventTypeFutureBookingLimits(transformedField);
|
||||
const result = transformFutureBookingLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -676,13 +617,13 @@ describe("getResponseEventTypeFutureBookingLimits", () => {
|
||||
rolling: true,
|
||||
};
|
||||
|
||||
const result = getResponseEventTypeFutureBookingLimits(transformedField);
|
||||
const result = transformFutureBookingLimitsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResponseEventTypeRecurrence", () => {
|
||||
describe("transformRecurrenceInternalToApi", () => {
|
||||
it("should reverse transform recurringEvent", () => {
|
||||
const transformedField: TransformRecurringEventSchema_2024_06_14 = {
|
||||
interval: 2,
|
||||
@@ -695,7 +636,7 @@ describe("getResponseEventTypeRecurrence", () => {
|
||||
interval: 2,
|
||||
occurrences: 10,
|
||||
};
|
||||
const result = getResponseEventTypeRecurrence(transformedField);
|
||||
const result = transformRecurrenceInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BookingLimitsEnum_2024_06_14 } from "@calcom/platform-enums/monorepo";
|
||||
import type {
|
||||
BookingLimitsKeysInputType,
|
||||
TransformBookingLimitsSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
export function transformIntervalLimitsInternalToApi(
|
||||
transformedBookingFields: TransformBookingLimitsSchema_2024_06_14 | null
|
||||
) {
|
||||
if (!transformedBookingFields) {
|
||||
return undefined;
|
||||
}
|
||||
const res: { [K in BookingLimitsKeysInputType]?: number } = {};
|
||||
transformedBookingFields &&
|
||||
Object.entries(transformedBookingFields).map(([key, value]) => {
|
||||
const outputKey: BookingLimitsKeysInputType | undefined = Object.keys(
|
||||
BookingLimitsEnum_2024_06_14
|
||||
).find(
|
||||
(item) => BookingLimitsEnum_2024_06_14[item as keyof typeof BookingLimitsEnum_2024_06_14] === key
|
||||
) as BookingLimitsKeysInputType;
|
||||
|
||||
if (outputKey) {
|
||||
res[outputKey] = value as number;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import type {
|
||||
AddressLocation_2024_06_14,
|
||||
IntegrationLocation_2024_06_14,
|
||||
LinkLocation_2024_06_14,
|
||||
PhoneLocation_2024_06_14,
|
||||
Integration_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { transformLocationsApiToInternal } from "../api-to-internal";
|
||||
|
||||
const reverseIntegrationsMapping: Record<string, Integration_2024_06_14> = {
|
||||
"integrations:daily": "cal-video",
|
||||
};
|
||||
|
||||
export function transformLocationsInternalToApi(
|
||||
transformedLocations: ReturnType<typeof transformLocationsApiToInternal>
|
||||
) {
|
||||
if (!transformedLocations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return transformedLocations.map((location) => {
|
||||
switch (location.type) {
|
||||
case "inPerson": {
|
||||
if (!location.address) {
|
||||
throw new Error("Address location must have an address");
|
||||
}
|
||||
const addressLocation: AddressLocation_2024_06_14 = {
|
||||
type: "address",
|
||||
address: location.address,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return addressLocation;
|
||||
}
|
||||
case "link": {
|
||||
if (!location.link) {
|
||||
throw new Error("Link location must have a link");
|
||||
}
|
||||
const linkLocation: LinkLocation_2024_06_14 = {
|
||||
type: "link",
|
||||
link: location.link,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return linkLocation;
|
||||
}
|
||||
case "userPhone": {
|
||||
if (!location.hostPhoneNumber) {
|
||||
throw new Error("Phone location must have a phone number");
|
||||
}
|
||||
const phoneLocation: PhoneLocation_2024_06_14 = {
|
||||
type: "phone",
|
||||
phone: location.hostPhoneNumber,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return phoneLocation;
|
||||
}
|
||||
default: {
|
||||
const integrationType = reverseIntegrationsMapping[location.type];
|
||||
if (!integrationType) {
|
||||
throw new Error(`Unsupported integration type '${location.type}'.`);
|
||||
}
|
||||
const integration: IntegrationLocation_2024_06_14 = {
|
||||
type: "integration",
|
||||
integration: integrationType,
|
||||
};
|
||||
return integration;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Frequency, FrequencyInput } from "@calcom/platform-enums/monorepo";
|
||||
import type { Recurrence_2024_06_14, TransformRecurringEventSchema_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
export function transformRecurrenceInternalToApi(
|
||||
transformRecurringEvent: TransformRecurringEventSchema_2024_06_14
|
||||
): Recurrence_2024_06_14 {
|
||||
return {
|
||||
interval: transformRecurringEvent.interval,
|
||||
occurrences: transformRecurringEvent.count,
|
||||
frequency: FrequencyInput[Frequency[transformRecurringEvent.freq] as keyof typeof FrequencyInput],
|
||||
} satisfies Recurrence_2024_06_14;
|
||||
}
|
||||
+32
-129
@@ -1,13 +1,20 @@
|
||||
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
||||
import { getFieldIdentifier } from "@calcom/features/form-builder/utils/getFieldIdentifier";
|
||||
import { defaultEvents } from "@calcom/lib/defaultEvents";
|
||||
import type { UserField, SystemField } from "@calcom/lib/event-types/transformers";
|
||||
import type { CustomField, SystemField } from "@calcom/lib/event-types/transformers";
|
||||
import {
|
||||
transformApiEventTypeLocations,
|
||||
transformApiEventTypeBookingFields,
|
||||
transformLocationsApiToInternal,
|
||||
transformBookingFieldsApiToInternal,
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
systemAfterFieldRescheduleReason,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
import type { EventTypeOutput_2024_06_14, TeamEventTypeOutput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type {
|
||||
CustomFieldOutput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import {
|
||||
bookerLayoutOptions,
|
||||
BookerLayouts,
|
||||
@@ -184,7 +191,7 @@ function isDefaultEvent(eventSlug: string) {
|
||||
}
|
||||
|
||||
function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
|
||||
const transformed = transformApiEventTypeLocations(locations);
|
||||
const transformed = transformLocationsApiToInternal(locations);
|
||||
|
||||
const withPrivateHidden = transformed.map((location) => {
|
||||
const { displayLocationPublicly, type } = location;
|
||||
@@ -210,133 +217,29 @@ function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
|
||||
}
|
||||
|
||||
function getBookingFields(bookingFields: EventTypeOutput_2024_06_14["bookingFields"]) {
|
||||
const transformedBookingFields: (SystemField | UserField)[] =
|
||||
transformApiEventTypeBookingFields(bookingFields);
|
||||
|
||||
// These fields should be added before other user fields
|
||||
const systemBeforeFields: SystemField[] = [
|
||||
{
|
||||
type: "name",
|
||||
// This is the `name` of the main field
|
||||
name: "name",
|
||||
editable: "system",
|
||||
// This Label is used in Email only as of now.
|
||||
defaultLabel: "your_name",
|
||||
required: true,
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultLabel: "email_address",
|
||||
type: "email",
|
||||
name: "email",
|
||||
required: true,
|
||||
editable: "system",
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultLabel: "location",
|
||||
type: "radioInput",
|
||||
name: "location",
|
||||
editable: "system",
|
||||
hideWhenJustOneOption: true,
|
||||
required: false,
|
||||
getOptionsAt: "locations",
|
||||
optionsInputs: {
|
||||
attendeeInPerson: {
|
||||
type: "address",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
phone: {
|
||||
type: "phone",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
},
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
];
|
||||
|
||||
// These fields should be added after other user fields
|
||||
const systemAfterFields: SystemField[] = [
|
||||
{
|
||||
defaultLabel: "reason_for_reschedule",
|
||||
type: "textarea",
|
||||
editable: "system-but-optional",
|
||||
name: "rescheduleReason",
|
||||
defaultPlaceholder: "reschedule_placeholder",
|
||||
required: false,
|
||||
views: [
|
||||
{
|
||||
id: "reschedule",
|
||||
label: "Reschedule View",
|
||||
},
|
||||
],
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
},
|
||||
const transformedCustomFields: CustomField[] = transformBookingFieldsApiToInternal(
|
||||
bookingFields.filter((field) => isCustomField(field))
|
||||
);
|
||||
|
||||
const systemAfterFields: SystemField[] = [systemAfterFieldRescheduleReason];
|
||||
|
||||
const transformedBookingFields: (SystemField | CustomField)[] = [
|
||||
...systemBeforeFields,
|
||||
...transformedCustomFields,
|
||||
...systemAfterFields,
|
||||
];
|
||||
|
||||
const missingSystemBeforeFields: SystemField[] = [];
|
||||
|
||||
for (const field of systemBeforeFields) {
|
||||
const existingBookingFieldIndex = transformedBookingFields.findIndex(
|
||||
(f) => getFieldIdentifier(f.name) === getFieldIdentifier(field.name)
|
||||
);
|
||||
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
||||
if (existingBookingFieldIndex === -1) {
|
||||
missingSystemBeforeFields.push(field);
|
||||
} else {
|
||||
// Adding the fields from Code first and then fields from DB. Allows, the code to push new properties to the field
|
||||
transformedBookingFields[existingBookingFieldIndex] = {
|
||||
...field,
|
||||
...transformedBookingFields[existingBookingFieldIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
transformedBookingFields.push(...missingSystemBeforeFields);
|
||||
|
||||
const missingSystemAfterFields: SystemField[] = [];
|
||||
for (const field of systemAfterFields) {
|
||||
const existingBookingFieldIndex = transformedBookingFields.findIndex(
|
||||
(f) => getFieldIdentifier(f.name) === getFieldIdentifier(field.name)
|
||||
);
|
||||
// Only do a push, we must not update existing system fields as user could have modified any property in it,
|
||||
if (existingBookingFieldIndex === -1) {
|
||||
missingSystemAfterFields.push(field);
|
||||
} else {
|
||||
transformedBookingFields[existingBookingFieldIndex] = {
|
||||
// Adding the fields from Code first and then fields from DB. Allows, the code to push new properties to the field
|
||||
...field,
|
||||
...transformedBookingFields[existingBookingFieldIndex],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
transformedBookingFields.push(...missingSystemAfterFields);
|
||||
return eventTypeBookingFields.brand<"HAS_SYSTEM_FIELDS">().parse(transformedBookingFields);
|
||||
}
|
||||
|
||||
function isCustomField(
|
||||
field: EventTypeOutput_2024_06_14["bookingFields"][number]
|
||||
): field is CustomFieldOutput_2024_06_14 {
|
||||
return !field.isDefault;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
## 0.0.36
|
||||
Released to support PR https://github.com/calcom/cal.com/pull/16685
|
||||
|
||||
## 0.0.31
|
||||
PR https://github.com/calcom/cal.com/pull/16414 fixed issue of deleting and rescheduling recurring events.
|
||||
|
||||
|
||||
@@ -94,22 +94,31 @@ export { eventTypeBookingFields, eventTypeLocations } from "@calcom/prisma/zod-u
|
||||
export { EventTypeMetaDataSchema, userMetadata } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export {
|
||||
transformApiEventTypeBookingFields,
|
||||
transformApiEventTypeIntervalLimits,
|
||||
transformApiEventTypeLocations,
|
||||
getResponseEventTypeLocations,
|
||||
getResponseEventTypeBookingFields,
|
||||
// note(Lauris): Api to internal
|
||||
transformBookingFieldsApiToInternal,
|
||||
transformLocationsApiToInternal,
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
// note(Lauris): Internal to api
|
||||
transformBookingFieldsInternalToApi,
|
||||
transformLocationsInternalToApi,
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
// note(Lauris): schemas
|
||||
TransformedLocationsSchema,
|
||||
BookingFieldsSchema,
|
||||
getResponseEventTypeIntervalLimits,
|
||||
getResponseEventTypeFutureBookingLimits,
|
||||
transformApiEventTypeFutureBookingLimits,
|
||||
transformApiEventTypeRecurrence,
|
||||
getResponseEventTypeRecurrence,
|
||||
// note(Lauris): constants
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
systemAfterFieldRescheduleReason,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
|
||||
export type { SystemField, CustomField } from "@calcom/lib/event-types/transformers";
|
||||
|
||||
export { parseBookingLimit } from "@calcom/lib";
|
||||
export type { SystemField, UserField } from "@calcom/lib/event-types/transformers";
|
||||
|
||||
export { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
export { dynamicEvent } from "@calcom/lib/defaultEvents";
|
||||
|
||||
+61
-102
@@ -5,9 +5,7 @@ import { IsString, IsBoolean, IsArray, IsIn, IsOptional } from "class-validator"
|
||||
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
|
||||
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
|
||||
|
||||
const bookingFields = [
|
||||
"name",
|
||||
"email",
|
||||
const inputBookingFieldTypes = [
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -21,53 +19,8 @@ const bookingFields = [
|
||||
"boolean",
|
||||
] as const;
|
||||
|
||||
export class NameField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
@DocsProperty()
|
||||
type!: "name";
|
||||
|
||||
@IsString()
|
||||
@DocsProperty()
|
||||
slug!: string;
|
||||
|
||||
@IsString()
|
||||
@DocsProperty()
|
||||
label!: string;
|
||||
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
required!: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
}
|
||||
export class EmailField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
@DocsProperty()
|
||||
type!: "email";
|
||||
|
||||
@IsString()
|
||||
@DocsProperty()
|
||||
slug!: string;
|
||||
|
||||
@IsString()
|
||||
@DocsProperty()
|
||||
label!: string;
|
||||
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
required!: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class PhoneField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class PhoneFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "phone";
|
||||
|
||||
@@ -89,8 +42,8 @@ export class PhoneField_2024_06_14 {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class AddressField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class AddressFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "address";
|
||||
|
||||
@@ -113,8 +66,8 @@ export class AddressField_2024_06_14 {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class TextField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class TextFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "text";
|
||||
|
||||
@@ -137,8 +90,8 @@ export class TextField_2024_06_14 {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class NumberField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class NumberFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "number";
|
||||
|
||||
@@ -161,8 +114,8 @@ export class NumberField_2024_06_14 {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class TextAreaField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class TextAreaFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "textarea";
|
||||
|
||||
@@ -185,8 +138,8 @@ export class TextAreaField_2024_06_14 {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class SelectField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class SelectFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "select";
|
||||
|
||||
@@ -213,8 +166,8 @@ export class SelectField_2024_06_14 {
|
||||
options!: string[];
|
||||
}
|
||||
|
||||
export class MultiSelectField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class MultiSelectFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "multiselect";
|
||||
|
||||
@@ -235,8 +188,8 @@ export class MultiSelectField_2024_06_14 {
|
||||
options!: string[];
|
||||
}
|
||||
|
||||
export class MultiEmailField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class MultiEmailFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "multiemail";
|
||||
|
||||
@@ -259,8 +212,8 @@ export class MultiEmailField_2024_06_14 {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export class CheckboxGroupField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class CheckboxGroupFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "checkbox";
|
||||
|
||||
@@ -281,8 +234,8 @@ export class CheckboxGroupField_2024_06_14 {
|
||||
options!: string[];
|
||||
}
|
||||
|
||||
export class RadioGroupField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class RadioGroupFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "radio";
|
||||
|
||||
@@ -303,8 +256,8 @@ export class RadioGroupField_2024_06_14 {
|
||||
options!: string[];
|
||||
}
|
||||
|
||||
export class BooleanField_2024_06_14 {
|
||||
@IsIn(bookingFields)
|
||||
export class BooleanFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty()
|
||||
type!: "boolean";
|
||||
|
||||
@@ -321,39 +274,37 @@ export class BooleanField_2024_06_14 {
|
||||
required!: boolean;
|
||||
}
|
||||
|
||||
export type BookingField_2024_06_14 =
|
||||
| NameField_2024_06_14
|
||||
| EmailField_2024_06_14
|
||||
| PhoneField_2024_06_14
|
||||
| AddressField_2024_06_14
|
||||
| TextField_2024_06_14
|
||||
| NumberField_2024_06_14
|
||||
| TextAreaField_2024_06_14
|
||||
| SelectField_2024_06_14
|
||||
| MultiSelectField_2024_06_14
|
||||
| MultiEmailField_2024_06_14
|
||||
| CheckboxGroupField_2024_06_14
|
||||
| RadioGroupField_2024_06_14
|
||||
| BooleanField_2024_06_14;
|
||||
export type InputBookingField_2024_06_14 =
|
||||
| PhoneFieldInput_2024_06_14
|
||||
| AddressFieldInput_2024_06_14
|
||||
| TextFieldInput_2024_06_14
|
||||
| NumberFieldInput_2024_06_14
|
||||
| TextAreaFieldInput_2024_06_14
|
||||
| SelectFieldInput_2024_06_14
|
||||
| MultiSelectFieldInput_2024_06_14
|
||||
| MultiEmailFieldInput_2024_06_14
|
||||
| CheckboxGroupFieldInput_2024_06_14
|
||||
| RadioGroupFieldInput_2024_06_14
|
||||
| BooleanFieldInput_2024_06_14;
|
||||
|
||||
@ValidatorConstraint({ async: true })
|
||||
class BookingFieldValidator_2024_06_14 implements ValidatorConstraintInterface {
|
||||
private classTypeMap: { [key: string]: new () => BookingField_2024_06_14 } = {
|
||||
name: NameField_2024_06_14,
|
||||
email: EmailField_2024_06_14,
|
||||
phone: PhoneField_2024_06_14,
|
||||
address: AddressField_2024_06_14,
|
||||
text: TextField_2024_06_14,
|
||||
number: NumberField_2024_06_14,
|
||||
textarea: TextAreaField_2024_06_14,
|
||||
select: SelectField_2024_06_14,
|
||||
multiselect: MultiSelectField_2024_06_14,
|
||||
multiemail: MultiEmailField_2024_06_14,
|
||||
checkbox: CheckboxGroupField_2024_06_14,
|
||||
radio: RadioGroupField_2024_06_14,
|
||||
boolean: BooleanField_2024_06_14,
|
||||
class InputBookingFieldValidator_2024_06_14 implements ValidatorConstraintInterface {
|
||||
private classTypeMap: { [key: string]: new () => InputBookingField_2024_06_14 } = {
|
||||
phone: PhoneFieldInput_2024_06_14,
|
||||
address: AddressFieldInput_2024_06_14,
|
||||
text: TextFieldInput_2024_06_14,
|
||||
number: NumberFieldInput_2024_06_14,
|
||||
textarea: TextAreaFieldInput_2024_06_14,
|
||||
select: SelectFieldInput_2024_06_14,
|
||||
multiselect: MultiSelectFieldInput_2024_06_14,
|
||||
multiemail: MultiEmailFieldInput_2024_06_14,
|
||||
checkbox: CheckboxGroupFieldInput_2024_06_14,
|
||||
radio: RadioGroupFieldInput_2024_06_14,
|
||||
boolean: BooleanFieldInput_2024_06_14,
|
||||
};
|
||||
|
||||
private reservedSystemSlugs = ["name", "email", "location", "rescheduleReason"];
|
||||
|
||||
async validate(bookingFields: { type: string; slug: string }[]) {
|
||||
if (!Array.isArray(bookingFields)) {
|
||||
throw new BadRequestException(`'bookingFields' must be an array.`);
|
||||
@@ -374,6 +325,14 @@ class BookingFieldValidator_2024_06_14 implements ValidatorConstraintInterface {
|
||||
throw new BadRequestException(`Each booking field must have a 'slug' property.`);
|
||||
}
|
||||
|
||||
if (this.reservedSystemSlugs.includes(slug)) {
|
||||
throw new BadRequestException(
|
||||
`The slug '${slug}' is reserved and cannot be used, because it is a slug of a default booking field. Reserved slugs are: ${this.reservedSystemSlugs.join(
|
||||
", "
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (slugs.includes(slug)) {
|
||||
throw new BadRequestException(
|
||||
`Duplicate bookingFields slug '${slug}' found. All bookingFields slugs must be unique.`
|
||||
@@ -402,15 +361,15 @@ class BookingFieldValidator_2024_06_14 implements ValidatorConstraintInterface {
|
||||
}
|
||||
}
|
||||
|
||||
export function ValidateBookingFields_2024_06_14(validationOptions?: ValidationOptions) {
|
||||
export function ValidateInputBookingFields_2024_06_14(validationOptions?: ValidationOptions) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return function (object: any, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: "ValidateBookingFields",
|
||||
name: "ValidateInputBookingFields",
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: new BookingFieldValidator_2024_06_14(),
|
||||
validator: new InputBookingFieldValidator_2024_06_14(),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
+4
-4
@@ -13,8 +13,8 @@ import {
|
||||
|
||||
import { SchedulingType } from "@calcom/platform-enums";
|
||||
|
||||
import type { BookingField_2024_06_14 } from "./booking-fields.input";
|
||||
import { ValidateBookingFields_2024_06_14 } from "./booking-fields.input";
|
||||
import type { InputBookingField_2024_06_14 } from "./booking-fields.input";
|
||||
import { ValidateInputBookingFields_2024_06_14 } from "./booking-fields.input";
|
||||
import { BookingLimitsCount_2024_06_14, ValidateBookingLimistsCount } from "./booking-limits-count.input";
|
||||
import {
|
||||
BookingLimitsDuration_2024_06_14,
|
||||
@@ -54,8 +54,8 @@ export class CreateEventTypeInput_2024_06_14 {
|
||||
locations?: Location_2024_06_14[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateBookingFields_2024_06_14()
|
||||
bookingFields?: BookingField_2024_06_14[];
|
||||
@ValidateInputBookingFields_2024_06_14()
|
||||
bookingFields?: InputBookingField_2024_06_14[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
|
||||
+6
-6
@@ -2,8 +2,8 @@ import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsString, IsInt, IsBoolean, IsOptional, Min, ValidateNested, IsArray } from "class-validator";
|
||||
|
||||
import type { BookingField_2024_06_14 } from "./booking-fields.input";
|
||||
import { ValidateBookingFields_2024_06_14 } from "./booking-fields.input";
|
||||
import type { InputBookingField_2024_06_14 } from "./booking-fields.input";
|
||||
import { ValidateInputBookingFields_2024_06_14 } from "./booking-fields.input";
|
||||
import { BookingLimitsCount_2024_06_14, ValidateBookingLimistsCount } from "./booking-limits-count.input";
|
||||
import {
|
||||
BookingLimitsDuration_2024_06_14,
|
||||
@@ -38,8 +38,8 @@ export class UpdateEventTypeInput_2024_06_14 {
|
||||
locations?: Location_2024_06_14[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateBookingFields_2024_06_14()
|
||||
bookingFields?: BookingField_2024_06_14[];
|
||||
@ValidateInputBookingFields_2024_06_14()
|
||||
bookingFields?: InputBookingField_2024_06_14[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@@ -122,9 +122,9 @@ export class UpdateTeamEventTypeInput_2024_06_14 {
|
||||
locations?: Location_2024_06_14[];
|
||||
|
||||
@IsOptional()
|
||||
@ValidateBookingFields_2024_06_14()
|
||||
@ValidateInputBookingFields_2024_06_14()
|
||||
@DocsProperty()
|
||||
bookingFields?: BookingField_2024_06_14[];
|
||||
bookingFields?: InputBookingField_2024_06_14[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
import { IsBoolean, IsString } from "class-validator";
|
||||
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
|
||||
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
|
||||
|
||||
import {
|
||||
PhoneFieldInput_2024_06_14,
|
||||
AddressFieldInput_2024_06_14,
|
||||
TextFieldInput_2024_06_14,
|
||||
NumberFieldInput_2024_06_14,
|
||||
TextAreaFieldInput_2024_06_14,
|
||||
SelectFieldInput_2024_06_14,
|
||||
MultiSelectFieldInput_2024_06_14,
|
||||
MultiEmailFieldInput_2024_06_14,
|
||||
CheckboxGroupFieldInput_2024_06_14,
|
||||
RadioGroupFieldInput_2024_06_14,
|
||||
BooleanFieldInput_2024_06_14,
|
||||
} from "../inputs";
|
||||
|
||||
class NameDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "name";
|
||||
|
||||
@IsString()
|
||||
type!: "name";
|
||||
|
||||
@IsBoolean()
|
||||
required!: true;
|
||||
}
|
||||
|
||||
class EmailDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "email";
|
||||
|
||||
@IsString()
|
||||
type!: "email";
|
||||
|
||||
@IsBoolean()
|
||||
required!: true;
|
||||
}
|
||||
|
||||
class LocationDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "location";
|
||||
|
||||
@IsString()
|
||||
type!: "radioInput";
|
||||
|
||||
@IsBoolean()
|
||||
required!: false;
|
||||
}
|
||||
|
||||
class RescheduleReasonDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "rescheduleReason";
|
||||
|
||||
@IsString()
|
||||
type!: "textarea";
|
||||
|
||||
@IsBoolean()
|
||||
required!: false;
|
||||
}
|
||||
|
||||
class TitleDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "title";
|
||||
|
||||
@IsString()
|
||||
type!: "text";
|
||||
|
||||
@IsBoolean()
|
||||
required!: true;
|
||||
}
|
||||
|
||||
class NotesDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "notes";
|
||||
|
||||
@IsString()
|
||||
type!: "textarea";
|
||||
|
||||
@IsBoolean()
|
||||
required!: false;
|
||||
}
|
||||
|
||||
class GuestsDefaultFieldOutput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = true;
|
||||
|
||||
@IsString()
|
||||
slug!: "guests";
|
||||
|
||||
@IsString()
|
||||
type!: "multiemail";
|
||||
|
||||
@IsBoolean()
|
||||
required!: false;
|
||||
}
|
||||
|
||||
class PhoneFieldOutput_2024_06_14 extends PhoneFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class AddressFieldOutput_2024_06_14 extends AddressFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class TextFieldOutput_2024_06_14 extends TextFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class NumberFieldOutput_2024_06_14 extends NumberFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class TextAreaFieldOutput_2024_06_14 extends TextAreaFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class SelectFieldOutput_2024_06_14 extends SelectFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class MultiSelectFieldOutput_2024_06_14 extends MultiSelectFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class MultiEmailFieldOutput_2024_06_14 extends MultiEmailFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class CheckboxGroupFieldOutput_2024_06_14 extends CheckboxGroupFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class RadioGroupFieldOutput_2024_06_14 extends RadioGroupFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
class BooleanFieldOutput_2024_06_14 extends BooleanFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
isDefault = false;
|
||||
}
|
||||
|
||||
export type DefaultFieldOutput_2024_06_14 =
|
||||
| NameDefaultFieldOutput_2024_06_14
|
||||
| EmailDefaultFieldOutput_2024_06_14
|
||||
| LocationDefaultFieldOutput_2024_06_14
|
||||
| RescheduleReasonDefaultFieldOutput_2024_06_14
|
||||
| TitleDefaultFieldOutput_2024_06_14
|
||||
| NotesDefaultFieldOutput_2024_06_14
|
||||
| GuestsDefaultFieldOutput_2024_06_14;
|
||||
|
||||
export type CustomFieldOutput_2024_06_14 =
|
||||
| PhoneFieldOutput_2024_06_14
|
||||
| AddressFieldOutput_2024_06_14
|
||||
| TextFieldOutput_2024_06_14
|
||||
| NumberFieldOutput_2024_06_14
|
||||
| TextAreaFieldOutput_2024_06_14
|
||||
| SelectFieldOutput_2024_06_14
|
||||
| MultiSelectFieldOutput_2024_06_14
|
||||
| MultiEmailFieldOutput_2024_06_14
|
||||
| CheckboxGroupFieldOutput_2024_06_14
|
||||
| RadioGroupFieldOutput_2024_06_14
|
||||
| BooleanFieldOutput_2024_06_14;
|
||||
|
||||
export type OutputBookingField_2024_06_14 = DefaultFieldOutput_2024_06_14 | CustomFieldOutput_2024_06_14;
|
||||
|
||||
@ValidatorConstraint({ async: true })
|
||||
class OutputBookingFieldValidator_2024_06_14 implements ValidatorConstraintInterface {
|
||||
private defaultOutputNameMap: { [key: string]: new () => DefaultFieldOutput_2024_06_14 } = {
|
||||
name: NameDefaultFieldOutput_2024_06_14,
|
||||
email: EmailDefaultFieldOutput_2024_06_14,
|
||||
location: LocationDefaultFieldOutput_2024_06_14,
|
||||
rescheduleReason: RescheduleReasonDefaultFieldOutput_2024_06_14,
|
||||
title: TitleDefaultFieldOutput_2024_06_14,
|
||||
notes: NotesDefaultFieldOutput_2024_06_14,
|
||||
guests: GuestsDefaultFieldOutput_2024_06_14,
|
||||
};
|
||||
|
||||
private customOutputTypeMap: { [key: string]: new () => CustomFieldOutput_2024_06_14 } = {
|
||||
phone: PhoneFieldOutput_2024_06_14,
|
||||
address: AddressFieldOutput_2024_06_14,
|
||||
text: TextFieldOutput_2024_06_14,
|
||||
number: NumberFieldOutput_2024_06_14,
|
||||
textarea: TextAreaFieldOutput_2024_06_14,
|
||||
select: SelectFieldOutput_2024_06_14,
|
||||
multiselect: MultiSelectFieldOutput_2024_06_14,
|
||||
multiemail: MultiEmailFieldOutput_2024_06_14,
|
||||
checkbox: CheckboxGroupFieldOutput_2024_06_14,
|
||||
radio: RadioGroupFieldOutput_2024_06_14,
|
||||
boolean: BooleanFieldOutput_2024_06_14,
|
||||
};
|
||||
|
||||
async validate(bookingFields: OutputBookingField_2024_06_14[]) {
|
||||
if (!Array.isArray(bookingFields)) {
|
||||
throw new BadRequestException(`'bookingFields' must be an array.`);
|
||||
}
|
||||
|
||||
if (!bookingFields.length) {
|
||||
throw new BadRequestException(`'bookingFields' must contain at least 1 booking field.`);
|
||||
}
|
||||
|
||||
const slugs: string[] = [];
|
||||
for (const field of bookingFields) {
|
||||
const { type, slug } = field;
|
||||
if (!type) {
|
||||
throw new BadRequestException(`Each booking field must have a 'type' property.`);
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
throw new BadRequestException(`Each booking field must have a 'slug' property.`);
|
||||
}
|
||||
|
||||
if (slugs.includes(slug)) {
|
||||
throw new BadRequestException(
|
||||
`Duplicate bookingFields slug '${slug}' found. All bookingFields slugs must be unique.`
|
||||
);
|
||||
}
|
||||
slugs.push(slug);
|
||||
|
||||
if (this.isDefaultField(field)) {
|
||||
await this.validateDefaultField(field);
|
||||
} else {
|
||||
await this.validateCustomField(field);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
isDefaultField(field: OutputBookingField_2024_06_14): field is DefaultFieldOutput_2024_06_14 {
|
||||
return field.isDefault === true;
|
||||
}
|
||||
|
||||
async validateDefaultField(field: DefaultFieldOutput_2024_06_14) {
|
||||
const ClassType = this.defaultOutputNameMap[field.slug];
|
||||
if (!ClassType) {
|
||||
throw new BadRequestException(`Unsupported booking field slgu '${field.slug}'.`);
|
||||
}
|
||||
|
||||
const instance = plainToInstance(ClassType, field);
|
||||
const errors = await validate(instance);
|
||||
if (errors.length > 0) {
|
||||
const message = errors.flatMap((error) => Object.values(error.constraints || {})).join(", ");
|
||||
throw new BadRequestException(`Validation failed for ${field.slug} booking field: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async validateCustomField(field: CustomFieldOutput_2024_06_14) {
|
||||
const ClassType = this.customOutputTypeMap[field.type];
|
||||
if (!ClassType) {
|
||||
throw new BadRequestException(`Unsupported booking field type '${field.type}'.`);
|
||||
}
|
||||
|
||||
const instance = plainToInstance(ClassType, field);
|
||||
const errors = await validate(instance);
|
||||
if (errors.length > 0) {
|
||||
const message = errors.flatMap((error) => Object.values(error.constraints || {})).join(", ");
|
||||
throw new BadRequestException(`Validation failed for ${field.type} booking field: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
defaultMessage() {
|
||||
return `Validation failed for one or more booking fields.`;
|
||||
}
|
||||
}
|
||||
|
||||
export function ValidateOutputBookingFields_2024_06_14(validationOptions?: ValidationOptions) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return function (object: any, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: "ValidateOutputBookingFields",
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
validator: new OutputBookingFieldValidator_2024_06_14(),
|
||||
});
|
||||
};
|
||||
}
|
||||
+7
-6
@@ -11,12 +11,13 @@ import {
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
|
||||
import type { Location_2024_06_14, BookingField_2024_06_14, BookingWindow_2024_06_14 } from "../inputs";
|
||||
import type { Location_2024_06_14, BookingWindow_2024_06_14 } from "../inputs";
|
||||
import { Host as TeamEventTypeHostInput, BookingLimitsDuration_2024_06_14 } from "../inputs";
|
||||
import { Recurrence_2024_06_14 } from "../inputs";
|
||||
import { ValidateBookingFields_2024_06_14 } from "../inputs/booking-fields.input";
|
||||
import type { BookingLimitsCount_2024_06_14 } from "../inputs/booking-limits-count.input";
|
||||
import { ValidateLocations_2024_06_14 } from "../inputs/locations.input";
|
||||
import type { OutputBookingField_2024_06_14 } from "./booking-fields.output";
|
||||
import { ValidateOutputBookingFields_2024_06_14 } from "./booking-fields.output";
|
||||
|
||||
enum SchedulingTypeEnum {
|
||||
ROUND_ROBIN = "ROUND_ROBIN",
|
||||
@@ -75,8 +76,8 @@ export class EventTypeOutput_2024_06_14 {
|
||||
@ValidateLocations_2024_06_14()
|
||||
locations!: Location_2024_06_14[];
|
||||
|
||||
@ValidateBookingFields_2024_06_14()
|
||||
bookingFields!: BookingField_2024_06_14[];
|
||||
@ValidateOutputBookingFields_2024_06_14()
|
||||
bookingFields!: OutputBookingField_2024_06_14[];
|
||||
|
||||
@IsBoolean()
|
||||
disableGuests!: boolean;
|
||||
@@ -185,8 +186,8 @@ export class TeamEventTypeOutput_2024_06_14 {
|
||||
@ValidateLocations_2024_06_14()
|
||||
locations!: Location_2024_06_14[];
|
||||
|
||||
@ValidateBookingFields_2024_06_14()
|
||||
bookingFields!: BookingField_2024_06_14[];
|
||||
@ValidateOutputBookingFields_2024_06_14()
|
||||
bookingFields!: OutputBookingField_2024_06_14[];
|
||||
|
||||
@IsBoolean()
|
||||
disableGuests!: boolean;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./event-type.output";
|
||||
export * from "./booking-fields.output";
|
||||
|
||||
Reference in New Issue
Block a user