refactor: v2 and booker atom booking fields (#17168)
* handle storing disableOnPrefill * feat: allow overriding properties of default name and email fields * feat: allow overriding properties of default name and email fields * feat: output booking fields * fix: event type test * refactor: booker atom booking fields handling * tests * fix: boolean booking field * update platform libraries * fix: unit tests
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.46",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.47",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
-1
@@ -662,7 +662,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.data).toBeDefined();
|
||||
const data: (BookingOutput_2024_08_13 | RecurringBookingOutput_2024_08_13)[] = responseBody.data;
|
||||
expect(data.length).toEqual(2);
|
||||
console.log("asap data", JSON.stringify(data, null, 2));
|
||||
expect(data[0].start).toEqual(createdBooking.start);
|
||||
expect(data[1].start).toEqual(bookingInThePast.startTime.toISOString());
|
||||
});
|
||||
|
||||
+48
-4
@@ -31,6 +31,7 @@ import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
NameFieldInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
@@ -199,6 +200,13 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create an event type", async () => {
|
||||
const nameBookingField: NameFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name sir / madam",
|
||||
placeholder: "john doe",
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const body: CreateEventTypeInput_2024_06_14 = {
|
||||
title: "Coding class",
|
||||
slug: "coding-class",
|
||||
@@ -211,6 +219,7 @@ describe("Event types Endpoints", () => {
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
nameBookingField,
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
@@ -218,6 +227,7 @@ describe("Event types Endpoints", () => {
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: true,
|
||||
},
|
||||
],
|
||||
scheduleId: firstSchedule.id,
|
||||
@@ -304,12 +314,14 @@ describe("Event types Endpoints", () => {
|
||||
);
|
||||
expect(createdEventType.color).toEqual(body.color);
|
||||
|
||||
const responseBookingFields = body.bookingFields || [];
|
||||
const requestBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", type: "name" },
|
||||
{ isDefault: true, required: true, slug: "name", ...nameBookingField },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
...responseBookingFields.map((field) => ({ isDefault: false, ...field })),
|
||||
...requestBookingFields
|
||||
.filter((field) => field.type !== "name" && field.type !== "email")
|
||||
.map((field) => ({ isDefault: false, ...field })),
|
||||
];
|
||||
|
||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -630,11 +642,30 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should update event type", async () => {
|
||||
const nameBookingField: NameFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name sir / madam",
|
||||
placeholder: "john doe",
|
||||
disableOnPrefill: true,
|
||||
};
|
||||
|
||||
const newTitle = "Coding class in Italian!";
|
||||
|
||||
const body: UpdateEventTypeInput_2024_06_14 = {
|
||||
title: newTitle,
|
||||
scheduleId: secondSchedule.id,
|
||||
bookingFields: [
|
||||
nameBookingField,
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
],
|
||||
bookingLimitsCount: {
|
||||
day: 4,
|
||||
week: 10,
|
||||
@@ -694,7 +725,19 @@ describe("Event types Endpoints", () => {
|
||||
expect(updatedEventType.description).toEqual(eventType.description);
|
||||
expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(updatedEventType.locations).toEqual(eventType.locations);
|
||||
expect(updatedEventType.bookingFields).toEqual(eventType.bookingFields);
|
||||
|
||||
const requestBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", ...nameBookingField },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
...requestBookingFields
|
||||
.filter((field) => field.type !== "name" && field.type !== "email")
|
||||
.map((field) => ({ isDefault: false, ...field })),
|
||||
];
|
||||
|
||||
expect(updatedEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
|
||||
expect(updatedEventType.ownerId).toEqual(user.id);
|
||||
expect(updatedEventType.scheduleId).toEqual(secondSchedule.id);
|
||||
expect(updatedEventType.bookingLimitsCount).toEqual(body.bookingLimitsCount);
|
||||
@@ -732,6 +775,7 @@ describe("Event types Endpoints", () => {
|
||||
eventType.hideCalendarEventDetails = updatedEventType.hideCalendarEventDetails;
|
||||
eventType.lockTimeZoneToggleOnBookingPage = updatedEventType.lockTimeZoneToggleOnBookingPage;
|
||||
eventType.color = updatedEventType.color;
|
||||
eventType.bookingFields = updatedEventType.bookingFields;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+16
-3
@@ -20,6 +20,8 @@ import {
|
||||
transformEventColorsApiToInternal,
|
||||
validateCustomEventName,
|
||||
transformSeatsApiToInternal,
|
||||
SystemField,
|
||||
CustomField,
|
||||
} from "@calcom/platform-libraries";
|
||||
import {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
@@ -211,16 +213,27 @@ export class InputEventTypesService_2024_06_14 {
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
|
||||
hasMultipleLocations: boolean
|
||||
) {
|
||||
const defaultFieldsBefore = [systemBeforeFieldName, systemBeforeFieldEmail];
|
||||
const customFields: (SystemField | CustomField)[] = inputBookingFields
|
||||
? transformBookingFieldsApiToInternal(inputBookingFields)
|
||||
: [];
|
||||
const customFieldsWithoutNameEmail = customFields.filter(
|
||||
(field) => field.type !== "name" && field.type !== "email"
|
||||
);
|
||||
const customNameField = customFields?.find((field) => field.type === "name");
|
||||
const customEmailField = customFields?.find((field) => field.type === "email");
|
||||
|
||||
const defaultFieldsBefore: (SystemField | CustomField)[] = [
|
||||
customNameField || systemBeforeFieldName,
|
||||
customEmailField || 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];
|
||||
return [...defaultFieldsBefore, ...customFieldsWithoutNameEmail, ...defaultFieldsAfter];
|
||||
}
|
||||
|
||||
transformInputIntervalLimits(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]) {
|
||||
|
||||
@@ -5977,6 +5977,10 @@
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `phone` and the URL contains query parameter `&phone=1234567890`, the phone field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6010,6 +6014,10 @@
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"example": "e.g., 1234 Main St"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `address` and the URL contains query parameter `&address=1234 Main St, London`, the address field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6043,6 +6051,10 @@
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"example": "e.g., Enter text here"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `who-referred-you` and the URL contains query parameter `&who-referred-you=bob`, the text field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6076,6 +6088,10 @@
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"example": "e.g., 100"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `calories-per-day` and the URL contains query parameter `&calories-per-day=3000`, the number field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6109,6 +6125,10 @@
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"example": "e.g., Detailed description here..."
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `dear-diary` and the URL contains query parameter `&dear-diary=Today I shipped a feature`, the text area will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6152,6 +6172,10 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' will be selected and the select field will be disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6192,6 +6216,10 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `consultants` and the URL contains query parameter `&consultants=en&language=it`, the 'en' and 'it' will be selected and the select field will be disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6225,6 +6253,10 @@
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"example": "e.g., example@example.com"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `consultants` and the URL contains query parameter `&consultants=alice@gmail.com&consultants=bob@gmail.com`, the these emails will be added and none more can be added."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6264,6 +6296,10 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `notify-me` and the URL contains query parameter `¬ify-me=true`, the checkbox will be selected and the checkbox field will be disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6303,6 +6339,10 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' radio buttom will be selected and the select field will be disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6332,6 +6372,9 @@
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6875,6 +6918,37 @@
|
||||
"EmailDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
"number",
|
||||
"textarea",
|
||||
"select",
|
||||
"multiselect",
|
||||
"multiemail",
|
||||
"checkbox",
|
||||
"radio",
|
||||
"boolean"
|
||||
],
|
||||
"example": "email",
|
||||
"description": "only allowed value for type is `email`",
|
||||
"default": "email"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&email=bob@gmail.com`, the email field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
@@ -6885,24 +6959,51 @@
|
||||
"type": "string",
|
||||
"default": "email"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "email"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"isDefault",
|
||||
"slug",
|
||||
"type",
|
||||
"required"
|
||||
]
|
||||
},
|
||||
"NameDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
"number",
|
||||
"textarea",
|
||||
"select",
|
||||
"multiselect",
|
||||
"multiemail",
|
||||
"checkbox",
|
||||
"radio",
|
||||
"boolean"
|
||||
],
|
||||
"example": "name",
|
||||
"description": "only allowed value for type is `name`",
|
||||
"default": "name"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&name=bob`, the name field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
@@ -6913,18 +7014,14 @@
|
||||
"type": "string",
|
||||
"default": "name"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "name"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"isDefault",
|
||||
"slug",
|
||||
"type",
|
||||
"required"
|
||||
]
|
||||
},
|
||||
@@ -7074,6 +7171,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7105,6 +7204,10 @@
|
||||
"type": "string",
|
||||
"example": "e.g., 1234 Main St"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `address` and the URL contains query parameter `&address=1234 Main St, London`, the address field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7126,6 +7229,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7153,6 +7258,9 @@
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7174,6 +7282,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7211,6 +7321,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `notify-me` and the URL contains query parameter `¬ify-me=true`, the checkbox will be selected and the checkbox field will be disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7233,6 +7347,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7264,6 +7380,10 @@
|
||||
"type": "string",
|
||||
"example": "e.g., example@example.com"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `consultants` and the URL contains query parameter `&consultants=alice@gmail.com&consultants=bob@gmail.com`, the these emails will be added and none more can be added."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7285,6 +7405,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7322,6 +7444,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `consultants` and the URL contains query parameter `&consultants=en&language=it`, the 'en' and 'it' will be selected and the select field will be disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7344,6 +7470,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7375,6 +7503,10 @@
|
||||
"type": "string",
|
||||
"example": "e.g., 100"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `calories-per-day` and the URL contains query parameter `&calories-per-day=3000`, the number field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7396,6 +7528,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7425,6 +7559,10 @@
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `phone` and the URL contains query parameter `&phone=1234567890`, the phone field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7446,6 +7584,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7483,6 +7623,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' radio buttom will be selected and the select field will be disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7505,6 +7649,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7546,6 +7692,10 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' will be selected and the select field will be disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7568,6 +7718,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7599,6 +7751,10 @@
|
||||
"type": "string",
|
||||
"example": "e.g., Detailed description here..."
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `dear-diary` and the URL contains query parameter `&dear-diary=Today I shipped a feature`, the text area will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
@@ -7620,6 +7776,8 @@
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -7651,6 +7809,10 @@
|
||||
"type": "string",
|
||||
"example": "e.g., Enter text here"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `who-referred-you` and the URL contains query parameter `&who-referred-you=bob`, the text field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": false,
|
||||
|
||||
@@ -21,7 +21,12 @@ import type {
|
||||
SeatOptionsDisabledSchema,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { CustomField } from "../internal-to-api/booking-fields";
|
||||
import {
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldName,
|
||||
type CustomField,
|
||||
type SystemField,
|
||||
} from "../internal-to-api/booking-fields";
|
||||
import {
|
||||
transformLocationsApiToInternal,
|
||||
transformBookingFieldsApiToInternal,
|
||||
@@ -105,6 +110,59 @@ describe("transformLocationsApiToInternal", () => {
|
||||
});
|
||||
|
||||
describe("transformBookingFieldsApiToInternal", () => {
|
||||
it("should transform name field", () => {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name number",
|
||||
placeholder: "123456789",
|
||||
disableOnPrefill: true,
|
||||
};
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedField = {
|
||||
...systemBeforeFieldName,
|
||||
label: "Your name number",
|
||||
placeholder: "123456789",
|
||||
disableOnPrefill: true,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expectedField.variantsConfig.variants.fullName.fields[0].label = "Your name number";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expectedField.variantsConfig.variants.fullName.fields[0].placeholder = "123456789";
|
||||
|
||||
const expectedOutput: SystemField[] = [expectedField];
|
||||
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform email field", () => {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "email",
|
||||
label: "Your email",
|
||||
placeholder: "bob@gmail.com",
|
||||
disableOnPrefill: true,
|
||||
};
|
||||
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: SystemField[] = [
|
||||
{
|
||||
...systemBeforeFieldEmail,
|
||||
label: "Your email",
|
||||
placeholder: "bob@gmail.com",
|
||||
disableOnPrefill: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should transform phone field", () => {
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "phone",
|
||||
@@ -470,6 +528,44 @@ describe("transformBookingFieldsApiToInternal", () => {
|
||||
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
type: bookingField.type,
|
||||
label: bookingField.label,
|
||||
labelAsSafeHtml: `<p>${bookingField.label}</p>\n`,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFieldsApiToInternal(input);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should keep disableOnPrefill property", () => {
|
||||
const disableOnPrefill = true;
|
||||
|
||||
const bookingField: InputBookingField_2024_06_14 = {
|
||||
type: "radio",
|
||||
slug: "radio",
|
||||
label: "Your radio buttons",
|
||||
required: true,
|
||||
options: ["Radio 1", "Radio 2"],
|
||||
disableOnPrefill,
|
||||
};
|
||||
|
||||
const input: InputBookingField_2024_06_14[] = [bookingField];
|
||||
|
||||
const expectedOutput: CustomField[] = [
|
||||
{
|
||||
name: bookingField.slug,
|
||||
@@ -486,6 +582,8 @@ describe("transformBookingFieldsApiToInternal", () => {
|
||||
editable: "user",
|
||||
required: bookingField.required,
|
||||
placeholder: "",
|
||||
options: transformSelectOptionsApiToInternal(bookingField.options),
|
||||
disableOnPrefill,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,19 +1,70 @@
|
||||
import { type CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
|
||||
import type { InputBookingField_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
import type { CustomField } from "../internal-to-api";
|
||||
import {
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldName,
|
||||
type CustomField,
|
||||
type SystemField,
|
||||
} from "../internal-to-api";
|
||||
|
||||
export function transformBookingFieldsApiToInternal(
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"]
|
||||
) {
|
||||
if (!inputBookingFields) {
|
||||
return [];
|
||||
inputBookingFields: InputBookingField_2024_06_14[]
|
||||
): (SystemField | CustomField)[] {
|
||||
const customBookingFields = inputBookingFields.map((field) => {
|
||||
const baseProperties = getBaseProperties(field);
|
||||
|
||||
const options =
|
||||
"options" in field && field.options ? transformSelectOptionsApiToInternal(field.options) : undefined;
|
||||
|
||||
if (!options) {
|
||||
return baseProperties;
|
||||
}
|
||||
|
||||
return {
|
||||
...baseProperties,
|
||||
options,
|
||||
};
|
||||
});
|
||||
|
||||
return customBookingFields;
|
||||
}
|
||||
|
||||
function getBaseProperties(field: InputBookingField_2024_06_14): SystemField | CustomField {
|
||||
if (field.type === "name") {
|
||||
const systemName = { ...systemBeforeFieldName };
|
||||
if (systemName.variantsConfig?.variants?.fullName?.fields?.[0]) {
|
||||
systemName.variantsConfig.variants.fullName.fields[0].label = field.label;
|
||||
}
|
||||
|
||||
if (systemName.variantsConfig?.variants?.fullName?.fields?.[0]) {
|
||||
systemName.variantsConfig.variants.fullName.fields[0].placeholder = field.placeholder;
|
||||
}
|
||||
// note(Lauris): we attach top level label and placeholder for easier access when converting database event type
|
||||
// to v2 response event type even though form builder uses label and placeholder from variantsConfig.
|
||||
systemName.label = field.label;
|
||||
systemName.placeholder = field.placeholder;
|
||||
|
||||
return {
|
||||
...systemName,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
}
|
||||
|
||||
const customBookingFields = inputBookingFields.map((field) => {
|
||||
const commonFields: CustomField = {
|
||||
if (field.type === "email") {
|
||||
return {
|
||||
...systemBeforeFieldEmail,
|
||||
label: field.label,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.type === "boolean") {
|
||||
return {
|
||||
name: field.slug,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
labelAsSafeHtml: `<p>${field.label}</p>\n`,
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
@@ -24,23 +75,27 @@ export function transformBookingFieldsApiToInternal(
|
||||
],
|
||||
editable: "user",
|
||||
required: field.required,
|
||||
placeholder: "placeholder" in field && field.placeholder ? field.placeholder : "",
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
}
|
||||
|
||||
const options =
|
||||
"options" in field && field.options ? transformSelectOptionsApiToInternal(field.options) : undefined;
|
||||
|
||||
if (!options) {
|
||||
return commonFields;
|
||||
}
|
||||
|
||||
return {
|
||||
...commonFields,
|
||||
options,
|
||||
};
|
||||
});
|
||||
|
||||
return customBookingFields;
|
||||
return {
|
||||
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 : "",
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformSelectOptionsApiToInternal(options: string[]) {
|
||||
|
||||
@@ -25,6 +25,9 @@ export function transformBookingFieldsInternalToApi(
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
label: field.label,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "email":
|
||||
return {
|
||||
@@ -32,6 +35,9 @@ export function transformBookingFieldsInternalToApi(
|
||||
type: field.type,
|
||||
slug: field.name,
|
||||
required: field.required,
|
||||
label: field.label,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "location":
|
||||
return {
|
||||
@@ -85,6 +91,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "address":
|
||||
return {
|
||||
@@ -94,6 +101,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "text":
|
||||
return {
|
||||
@@ -103,6 +111,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
@@ -112,6 +121,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "textarea":
|
||||
return {
|
||||
@@ -121,6 +131,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "multiemail":
|
||||
return {
|
||||
@@ -130,6 +141,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "boolean":
|
||||
return {
|
||||
@@ -138,6 +150,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
slug: field.name,
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
@@ -148,6 +161,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
required: field.required,
|
||||
placeholder: field.placeholder,
|
||||
options: field.options ? field.options.map((option) => option.value) : [],
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "multiselect":
|
||||
return {
|
||||
@@ -157,6 +171,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "checkbox":
|
||||
return {
|
||||
@@ -166,6 +181,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
case "radio":
|
||||
return {
|
||||
@@ -175,6 +191,7 @@ export function transformBookingFieldsInternalToApi(
|
||||
label: field.label,
|
||||
required: field.required,
|
||||
options: field.options ? field.options?.map((option) => option.value) : [],
|
||||
disableOnPrefill: field.disableOnPrefill,
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unsupported booking field type '${field.type}'.`);
|
||||
@@ -203,6 +220,7 @@ const CustomFieldsSchema = z.object({
|
||||
name: z.string(),
|
||||
type: CustomFieldTypeEnum,
|
||||
label: z.string(),
|
||||
labelAsSafeHtml: z.string().optional(),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
id: z.literal("user"),
|
||||
@@ -211,7 +229,7 @@ const CustomFieldsSchema = z.object({
|
||||
fieldRequired: z.literal(true),
|
||||
})
|
||||
),
|
||||
editable: z.literal("user"),
|
||||
editable: z.enum(["user", "user-readonly"]),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string().optional(),
|
||||
options: z
|
||||
@@ -222,6 +240,7 @@ const CustomFieldsSchema = z.object({
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
disableOnPrefill: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const SystemFieldSchema = z.object({
|
||||
@@ -244,6 +263,7 @@ const SystemFieldSchema = z.object({
|
||||
)
|
||||
.optional(),
|
||||
defaultPlaceholder: z.enum(["", "share_additional_notes", "email", "reschedule_placeholder"]).optional(),
|
||||
placeholder: z.string().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
required: z.boolean(),
|
||||
hideWhenJustOneOption: z.boolean().optional(),
|
||||
@@ -262,12 +282,31 @@ const SystemFieldSchema = z.object({
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
disableOnPrefill: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const NameSystemFieldSchema = SystemFieldSchema.extend({
|
||||
name: z.literal("name"),
|
||||
type: z.literal("name"),
|
||||
required: z.literal(true),
|
||||
variant: z.literal("fullName").optional(),
|
||||
variantsConfig: z
|
||||
.object({
|
||||
variants: z.object({
|
||||
fullName: z.object({
|
||||
fields: z.array(
|
||||
z.object({
|
||||
name: z.literal("fullName"),
|
||||
type: z.literal("text"),
|
||||
label: z.string().optional(),
|
||||
required: z.literal(true),
|
||||
placeholder: z.string().optional(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const EmailSystemFieldSchema = SystemFieldSchema.extend({
|
||||
@@ -306,8 +345,8 @@ const GuestsSystemFieldSchema = SystemFieldSchema.extend({
|
||||
required: z.literal(false),
|
||||
});
|
||||
|
||||
type NameSystemField = z.infer<typeof NameSystemFieldSchema>;
|
||||
type EmailSystemField = z.infer<typeof EmailSystemFieldSchema>;
|
||||
export type NameSystemField = z.infer<typeof NameSystemFieldSchema>;
|
||||
export type EmailSystemField = z.infer<typeof EmailSystemFieldSchema>;
|
||||
type RescheduleReasonSystemField = z.infer<typeof RescheduleReasonSystemFieldSchema>;
|
||||
type LocationReasonSystemField = z.infer<typeof LocationReasonSystemFieldSchema>;
|
||||
type TitleSystemField = z.infer<typeof TitleSystemFieldSchema>;
|
||||
@@ -343,6 +382,7 @@ export const systemBeforeFieldName: NameSystemField = {
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
required: true,
|
||||
variant: "fullName",
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
@@ -350,21 +390,19 @@ export const systemBeforeFieldName: NameSystemField = {
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const systemBeforeFieldNameReadOnly: NameSystemField = {
|
||||
type: "name",
|
||||
name: "name",
|
||||
editable: "user-readonly",
|
||||
defaultLabel: "your_name",
|
||||
required: true,
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
variantsConfig: {
|
||||
variants: {
|
||||
fullName: {
|
||||
fields: [
|
||||
{
|
||||
name: "fullName",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const systemBeforeFieldEmail: EmailSystemField = {
|
||||
@@ -382,21 +420,6 @@ export const systemBeforeFieldEmail: EmailSystemField = {
|
||||
],
|
||||
};
|
||||
|
||||
export const systemBeforeFieldEmailReadOnly: EmailSystemField = {
|
||||
defaultLabel: "email_address",
|
||||
type: "email",
|
||||
name: "email",
|
||||
required: true,
|
||||
editable: "user-readonly",
|
||||
sources: [
|
||||
{
|
||||
label: "Default",
|
||||
id: "default",
|
||||
type: "default",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const systemBeforeFieldLocation: LocationReasonSystemField = {
|
||||
defaultLabel: "location",
|
||||
type: "radioInput",
|
||||
|
||||
@@ -31,13 +31,18 @@ import {
|
||||
transformEventTypeColorsInternalToApi,
|
||||
transformSeatsInternalToApi,
|
||||
} from ".";
|
||||
import type { CustomField } from "./booking-fields";
|
||||
import {
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldName,
|
||||
type CustomField,
|
||||
type SystemField,
|
||||
} from "./booking-fields";
|
||||
|
||||
describe("transformLocationsInternalToApi", () => {
|
||||
it("should reverse transform address location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "inPerson",
|
||||
type: "inPerson" as const,
|
||||
address: "1234 Main St",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
@@ -59,7 +64,7 @@ describe("transformLocationsInternalToApi", () => {
|
||||
it("should reverse transform link location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "link",
|
||||
type: "link" as const,
|
||||
link: "https://example.com",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
@@ -81,7 +86,7 @@ describe("transformLocationsInternalToApi", () => {
|
||||
it("should reverse transform phone location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "userPhone",
|
||||
type: "userPhone" as const,
|
||||
hostPhoneNumber: "123456789",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
@@ -103,7 +108,7 @@ describe("transformLocationsInternalToApi", () => {
|
||||
it("should reverse transform integration location", () => {
|
||||
const transformedLocation = [
|
||||
{
|
||||
type: "integrations:daily",
|
||||
type: "integrations:daily" as const,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -121,6 +126,109 @@ describe("transformLocationsInternalToApi", () => {
|
||||
});
|
||||
|
||||
describe("transformBookingFieldsInternalToApi", () => {
|
||||
it("should reverse transform not modified name default field", () => {
|
||||
const transformedField: SystemField[] = [systemBeforeFieldName];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "name",
|
||||
slug: "name",
|
||||
isDefault: true,
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform modified name default field", () => {
|
||||
const nameField = {
|
||||
...systemBeforeFieldName,
|
||||
placeholder: "custom placeholder",
|
||||
disableOnPrefill: true,
|
||||
label: "custom label",
|
||||
variantsConfig: {
|
||||
variants: {
|
||||
fullName: {
|
||||
fields: [
|
||||
{
|
||||
name: "fullName",
|
||||
label: "custom label",
|
||||
placeholder: "custom placeholder",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const transformedField: SystemField[] = [nameField];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "name",
|
||||
slug: "name",
|
||||
isDefault: true,
|
||||
required: true,
|
||||
placeholder: "custom placeholder",
|
||||
disableOnPrefill: true,
|
||||
label: "custom label",
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform not modified email default field", () => {
|
||||
const transformedField: SystemField[] = [systemBeforeFieldEmail];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "email",
|
||||
slug: "email",
|
||||
isDefault: true,
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform modified email default field", () => {
|
||||
const transformedField: SystemField[] = [
|
||||
{
|
||||
...systemBeforeFieldEmail,
|
||||
placeholder: "custom placeholder",
|
||||
disableOnPrefill: true,
|
||||
label: "custom label",
|
||||
},
|
||||
];
|
||||
|
||||
const expectedOutput = [
|
||||
{
|
||||
type: "email",
|
||||
slug: "email",
|
||||
isDefault: true,
|
||||
required: true,
|
||||
placeholder: "custom placeholder",
|
||||
disableOnPrefill: true,
|
||||
label: "custom label",
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFieldsInternalToApi(transformedField);
|
||||
|
||||
expect(result).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it("should reverse transform phone field", () => {
|
||||
const transformedField: CustomField[] = [
|
||||
{
|
||||
@@ -511,7 +619,6 @@ describe("transformBookingFieldsInternalToApi", () => {
|
||||
],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -733,7 +840,7 @@ describe("transformRequiresConfirmationInternalToApi", () => {
|
||||
};
|
||||
const result = transformRequiresConfirmationInternalToApi(
|
||||
transformedField.requiresConfirmation,
|
||||
!!undefined,
|
||||
false,
|
||||
undefined
|
||||
);
|
||||
|
||||
|
||||
@@ -62,10 +62,6 @@ export type BookerPlatformWrapperAtomProps = Omit<
|
||||
notes?: string;
|
||||
rescheduleReason?: string;
|
||||
} & Record<string, string | string[]>;
|
||||
readOnlyFormValues?: {
|
||||
name?: boolean;
|
||||
email?: boolean;
|
||||
};
|
||||
handleCreateBooking?: (input: UseCreateBookingInput) => void;
|
||||
onCreateBookingSuccess?: (data: ApiSuccessResponse<BookingResponse>) => void;
|
||||
onCreateBookingError?: (data: ApiErrorResponse | Error) => void;
|
||||
@@ -153,7 +149,7 @@ export const BookerPlatformWrapper = (
|
||||
isPending: isTeamPending,
|
||||
data:
|
||||
teamEventTypeData && teamEventTypeData.length > 0
|
||||
? transformApiTeamEventTypeForAtom(teamEventTypeData[0], props.entity, props.readOnlyFormValues)
|
||||
? transformApiTeamEventTypeForAtom(teamEventTypeData[0], props.entity, props.defaultFormValues)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
@@ -164,7 +160,7 @@ export const BookerPlatformWrapper = (
|
||||
isPending,
|
||||
data:
|
||||
data && data.length > 0
|
||||
? transformApiEventTypeForAtom(data[0], props.entity, props.readOnlyFormValues)
|
||||
? transformApiEventTypeForAtom(data[0], props.entity, props.defaultFormValues)
|
||||
: undefined,
|
||||
};
|
||||
}, [
|
||||
|
||||
@@ -113,7 +113,7 @@ export function BaseCalProvider({
|
||||
let translation =
|
||||
labels?.[resolvedKey as keyof typeof labels] ?? String(getTranslation(resolvedKey, language) ?? "");
|
||||
if (!translation) {
|
||||
return "";
|
||||
return key;
|
||||
}
|
||||
if (values) {
|
||||
const valueKeys = Object.keys(values) as (keyof typeof values)[];
|
||||
|
||||
+42
-14
@@ -8,13 +8,13 @@ import {
|
||||
systemBeforeFieldLocation,
|
||||
systemAfterFieldRescheduleReason,
|
||||
transformRecurrenceApiToInternal,
|
||||
systemBeforeFieldNameReadOnly,
|
||||
systemBeforeFieldEmailReadOnly,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
import type {
|
||||
CustomFieldOutput_2024_06_14,
|
||||
EmailDefaultFieldOutput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
NameDefaultFieldOutput_2024_06_14,
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import {
|
||||
@@ -30,7 +30,7 @@ import type { BookerPlatformWrapperAtomProps } from "../../booker/BookerPlatform
|
||||
export function transformApiEventTypeForAtom(
|
||||
eventType: Omit<EventTypeOutput_2024_06_14, "ownerId">,
|
||||
entity: BookerPlatformWrapperAtomProps["entity"] | undefined,
|
||||
readOnlyFormValues: BookerPlatformWrapperAtomProps["readOnlyFormValues"] | undefined
|
||||
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined
|
||||
) {
|
||||
const { lengthInMinutes, locations, bookingFields, users, recurrence, ...rest } = eventType;
|
||||
|
||||
@@ -50,7 +50,7 @@ export function transformApiEventTypeForAtom(
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: getLocations(locations),
|
||||
bookingFields: getBookingFields(bookingFields, readOnlyFormValues),
|
||||
bookingFields: getBookingFields(bookingFields, defaultFormValues),
|
||||
isDefault,
|
||||
isDynamic: false,
|
||||
profile: {
|
||||
@@ -107,7 +107,7 @@ export function transformApiEventTypeForAtom(
|
||||
export function transformApiTeamEventTypeForAtom(
|
||||
eventType: TeamEventTypeOutput_2024_06_14,
|
||||
entity: BookerPlatformWrapperAtomProps["entity"] | undefined,
|
||||
readOnlyFormValues: BookerPlatformWrapperAtomProps["readOnlyFormValues"] | undefined
|
||||
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined
|
||||
) {
|
||||
const { lengthInMinutes, locations, hosts, bookingFields, recurrence, ...rest } = eventType;
|
||||
|
||||
@@ -126,7 +126,7 @@ export function transformApiTeamEventTypeForAtom(
|
||||
...rest,
|
||||
length: lengthInMinutes,
|
||||
locations: getLocations(locations),
|
||||
bookingFields: getBookingFields(bookingFields, readOnlyFormValues),
|
||||
bookingFields: getBookingFields(bookingFields, defaultFormValues),
|
||||
isDefault,
|
||||
isDynamic: false,
|
||||
profile: {
|
||||
@@ -226,26 +226,48 @@ function getLocations(locations: EventTypeOutput_2024_06_14["locations"]) {
|
||||
|
||||
function getBookingFields(
|
||||
bookingFields: EventTypeOutput_2024_06_14["bookingFields"],
|
||||
readOnlyFormValues: BookerPlatformWrapperAtomProps["readOnlyFormValues"] | undefined
|
||||
defaultFormValues: BookerPlatformWrapperAtomProps["defaultFormValues"] | undefined
|
||||
) {
|
||||
// note(Lauris): the peculiar thing about returning atom booking fields using v2 event type is that v2 event type has more possible
|
||||
// booking field outputs than inputs due to default system fields that cant be passed as inputs, which is why we take v2 from response
|
||||
// only the custom fields and default editable fields aka fields that can be passed as inputs for event type booking fields.
|
||||
const customFields: (SystemField | CustomField)[] = bookingFields
|
||||
? transformBookingFieldsApiToInternal(
|
||||
bookingFields.filter((field) => isCustomField(field) || isDefaultEditableField(field))
|
||||
)
|
||||
: [];
|
||||
|
||||
const customFieldsWithoutNameEmail = customFields.filter(
|
||||
(field) => field.type !== "name" && field.type !== "email"
|
||||
);
|
||||
const customNameField = customFields?.find((field) => field.type === "name");
|
||||
const customEmailField = customFields?.find((field) => field.type === "email");
|
||||
|
||||
const systemBeforeFields: SystemField[] = [
|
||||
readOnlyFormValues?.name ? systemBeforeFieldNameReadOnly : systemBeforeFieldName,
|
||||
readOnlyFormValues?.email ? systemBeforeFieldEmailReadOnly : systemBeforeFieldEmail,
|
||||
customNameField || systemBeforeFieldName,
|
||||
customEmailField || systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
];
|
||||
|
||||
const transformedCustomFields: CustomField[] = transformBookingFieldsApiToInternal(
|
||||
bookingFields.filter((field) => isCustomField(field))
|
||||
);
|
||||
|
||||
const systemAfterFields: SystemField[] = [systemAfterFieldRescheduleReason];
|
||||
|
||||
const transformedBookingFields: (SystemField | CustomField)[] = [
|
||||
...systemBeforeFields,
|
||||
...transformedCustomFields,
|
||||
...customFieldsWithoutNameEmail,
|
||||
...systemAfterFields,
|
||||
];
|
||||
|
||||
// note(Lauris): in web app booking form values can be passed as url query params, but booker atom does not accept booking field values via url,
|
||||
// so defaultFormValues act as a way to prefill booking form fields, and if the field in database has disableOnPrefill=true and value passed then its read only.
|
||||
const defaultFormValuesKeys = defaultFormValues ? Object.keys(defaultFormValues) : [];
|
||||
if (defaultFormValuesKeys.length) {
|
||||
for (const field of transformedBookingFields) {
|
||||
if (defaultFormValuesKeys.includes(field.name) && field.disableOnPrefill) {
|
||||
field.editable = "user-readonly";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return eventTypeBookingFields.brand<"HAS_SYSTEM_FIELDS">().parse(transformedBookingFields);
|
||||
}
|
||||
|
||||
@@ -254,3 +276,9 @@ function isCustomField(
|
||||
): field is CustomFieldOutput_2024_06_14 {
|
||||
return !field.isDefault;
|
||||
}
|
||||
|
||||
function isDefaultEditableField(
|
||||
field: EventTypeOutput_2024_06_14["bookingFields"][number]
|
||||
): field is NameDefaultFieldOutput_2024_06_14 | EmailDefaultFieldOutput_2024_06_14 {
|
||||
return field.type === "name" || field.type === "email";
|
||||
}
|
||||
|
||||
@@ -125,7 +125,12 @@ export {
|
||||
systemAfterFieldRescheduleReason,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
|
||||
export type { SystemField, CustomField } from "@calcom/lib/event-types/transformers";
|
||||
export type {
|
||||
SystemField,
|
||||
CustomField,
|
||||
NameSystemField,
|
||||
EmailSystemField,
|
||||
} from "@calcom/lib/event-types/transformers";
|
||||
|
||||
export { parseBookingLimit, parseEventTypeColor } from "@calcom/lib";
|
||||
|
||||
|
||||
+181
-4
@@ -1,11 +1,13 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { ApiProperty as DocsProperty, ApiPropertyOptional as DocsPropertyOptional } from "@nestjs/swagger";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
import { IsString, IsBoolean, IsArray, IsIn, IsOptional } from "class-validator";
|
||||
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
|
||||
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
|
||||
|
||||
const inputBookingFieldTypes = [
|
||||
"name",
|
||||
"email",
|
||||
"phone",
|
||||
"address",
|
||||
"text",
|
||||
@@ -19,6 +21,59 @@ const inputBookingFieldTypes = [
|
||||
"boolean",
|
||||
] as const;
|
||||
|
||||
export class NameFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty({ example: "name", description: "only allowed value for type is `name`" })
|
||||
type!: "name";
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
label?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if URL contains query parameter `&name=bob`,\
|
||||
the name field will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class EmailFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty({ example: "email", description: "only allowed value for type is `email`" })
|
||||
type!: "email";
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
label?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if URL contains query parameter `&email=bob@gmail.com`,\
|
||||
the email field will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
export class PhoneFieldInput_2024_06_14 {
|
||||
@IsIn(inputBookingFieldTypes)
|
||||
@DocsProperty({ example: "phone", description: "only allowed value for type is `phone`" })
|
||||
@@ -44,6 +99,17 @@ export class PhoneFieldInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `phone` and the URL contains query parameter `&phone=1234567890`,\
|
||||
the phone field will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class AddressFieldInput_2024_06_14 {
|
||||
@@ -72,6 +138,17 @@ export class AddressFieldInput_2024_06_14 {
|
||||
@DocsProperty()
|
||||
@DocsProperty({ example: "e.g., 1234 Main St" })
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `address` and the URL contains query parameter `&address=1234 Main St, London`,\
|
||||
the address field will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class TextFieldInput_2024_06_14 {
|
||||
@@ -100,6 +177,17 @@ export class TextFieldInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `who-referred-you` and the URL contains query parameter `&who-referred-you=bob`,\
|
||||
the text field will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class NumberFieldInput_2024_06_14 {
|
||||
@@ -128,6 +216,17 @@ export class NumberFieldInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `calories-per-day` and the URL contains query parameter `&calories-per-day=3000`,\
|
||||
the number field will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class TextAreaFieldInput_2024_06_14 {
|
||||
@@ -156,6 +255,17 @@ export class TextAreaFieldInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `dear-diary` and the URL contains query parameter `&dear-diary=Today I shipped a feature`,\
|
||||
the text area will be prefilled with this value and disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class SelectFieldInput_2024_06_14 {
|
||||
@@ -188,6 +298,17 @@ export class SelectFieldInput_2024_06_14 {
|
||||
@IsArray()
|
||||
@DocsProperty({ type: [String], example: ["Option 1", "Option 2"] })
|
||||
options!: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`,\
|
||||
the 'italian' will be selected and the select field will be disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class MultiSelectFieldInput_2024_06_14 {
|
||||
@@ -214,6 +335,17 @@ export class MultiSelectFieldInput_2024_06_14 {
|
||||
@IsArray()
|
||||
@DocsProperty({ type: [String], example: ["Option 1", "Option 2"] })
|
||||
options!: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `consultants` and the URL contains query parameter `&consultants=en&language=it`,\
|
||||
the 'en' and 'it' will be selected and the select field will be disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class MultiEmailFieldInput_2024_06_14 {
|
||||
@@ -242,6 +374,17 @@ export class MultiEmailFieldInput_2024_06_14 {
|
||||
@IsOptional()
|
||||
@DocsProperty()
|
||||
placeholder?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `consultants` and the URL contains query parameter `&consultants=alice@gmail.com&consultants=bob@gmail.com`,\
|
||||
the these emails will be added and none more can be added.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class CheckboxGroupFieldInput_2024_06_14 {
|
||||
@@ -268,6 +411,17 @@ export class CheckboxGroupFieldInput_2024_06_14 {
|
||||
@IsArray()
|
||||
@DocsProperty({ type: [String], example: ["Checkbox 1", "Checkbox 2"] })
|
||||
options!: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `notify-me` and the URL contains query parameter `¬ify-me=true`,\
|
||||
the checkbox will be selected and the checkbox field will be disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class RadioGroupFieldInput_2024_06_14 {
|
||||
@@ -294,6 +448,17 @@ export class RadioGroupFieldInput_2024_06_14 {
|
||||
@IsArray()
|
||||
@DocsProperty({ type: [String], example: ["Radio 1", "Radio 2"] })
|
||||
options!: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({
|
||||
type: Boolean,
|
||||
description:
|
||||
"Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value.\
|
||||
For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`,\
|
||||
the 'italian' radio buttom will be selected and the select field will be disabled.",
|
||||
})
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export class BooleanFieldInput_2024_06_14 {
|
||||
@@ -316,9 +481,16 @@ export class BooleanFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
@DocsProperty()
|
||||
required!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@DocsPropertyOptional({ type: Boolean })
|
||||
disableOnPrefill?: boolean;
|
||||
}
|
||||
|
||||
export type InputBookingField_2024_06_14 =
|
||||
| NameFieldInput_2024_06_14
|
||||
| EmailFieldInput_2024_06_14
|
||||
| PhoneFieldInput_2024_06_14
|
||||
| AddressFieldInput_2024_06_14
|
||||
| TextFieldInput_2024_06_14
|
||||
@@ -334,6 +506,8 @@ export type InputBookingField_2024_06_14 =
|
||||
@ValidatorConstraint({ async: true })
|
||||
class InputBookingFieldValidator_2024_06_14 implements ValidatorConstraintInterface {
|
||||
private classTypeMap: { [key: string]: new () => InputBookingField_2024_06_14 } = {
|
||||
name: NameFieldInput_2024_06_14,
|
||||
email: EmailFieldInput_2024_06_14,
|
||||
phone: PhoneFieldInput_2024_06_14,
|
||||
address: AddressFieldInput_2024_06_14,
|
||||
text: TextFieldInput_2024_06_14,
|
||||
@@ -347,7 +521,7 @@ class InputBookingFieldValidator_2024_06_14 implements ValidatorConstraintInterf
|
||||
boolean: BooleanFieldInput_2024_06_14,
|
||||
};
|
||||
|
||||
private reservedSystemSlugs = ["name", "email", "location", "rescheduleReason"];
|
||||
private reservedSystemSlugs = ["location", "rescheduleReason"];
|
||||
|
||||
async validate(bookingFields: { type: string; slug: string }[]) {
|
||||
if (!Array.isArray(bookingFields)) {
|
||||
@@ -365,7 +539,8 @@ class InputBookingFieldValidator_2024_06_14 implements ValidatorConstraintInterf
|
||||
throw new BadRequestException(`Each booking field must have a 'type' property.`);
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
const notSystemEditableField = type !== "name" && type !== "email";
|
||||
if (notSystemEditableField && !slug) {
|
||||
throw new BadRequestException(`Each booking field must have a 'slug' property.`);
|
||||
}
|
||||
|
||||
@@ -382,7 +557,9 @@ class InputBookingFieldValidator_2024_06_14 implements ValidatorConstraintInterf
|
||||
`Duplicate bookingFields slug '${slug}' found. All bookingFields slugs must be unique.`
|
||||
);
|
||||
}
|
||||
slugs.push(slug);
|
||||
if (notSystemEditableField) {
|
||||
slugs.push(slug);
|
||||
}
|
||||
|
||||
const ClassType = this.classTypeMap[type];
|
||||
if (!ClassType) {
|
||||
|
||||
+4
-2
@@ -17,9 +17,11 @@ import {
|
||||
CheckboxGroupFieldInput_2024_06_14,
|
||||
RadioGroupFieldInput_2024_06_14,
|
||||
BooleanFieldInput_2024_06_14,
|
||||
NameFieldInput_2024_06_14,
|
||||
EmailFieldInput_2024_06_14,
|
||||
} from "../inputs";
|
||||
|
||||
export class NameDefaultFieldOutput_2024_06_14 {
|
||||
export class NameDefaultFieldOutput_2024_06_14 extends NameFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
@DocsProperty({
|
||||
description: "This property is always true because it's a default field",
|
||||
@@ -45,7 +47,7 @@ export class NameDefaultFieldOutput_2024_06_14 {
|
||||
required!: true;
|
||||
}
|
||||
|
||||
export class EmailDefaultFieldOutput_2024_06_14 {
|
||||
export class EmailDefaultFieldOutput_2024_06_14 extends EmailFieldInput_2024_06_14 {
|
||||
@IsBoolean()
|
||||
@DocsProperty({
|
||||
description: "This property is always true because it's a default field",
|
||||
|
||||
Reference in New Issue
Block a user