diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx
index 51922d13a6..f27a2a36f4 100644
--- a/apps/web/components/booking/pages/BookingPage.tsx
+++ b/apps/web/components/booking/pages/BookingPage.tsx
@@ -299,6 +299,39 @@ const BookingPage = ({
}
const bookEvent = (booking: BookingFormValues) => {
+ const bookingCustomInputs = Object.keys(booking.customInputs || {}).map((inputId) => ({
+ label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
+ value: booking.customInputs && booking.customInputs[inputId] ? booking.customInputs[inputId] : "",
+ }));
+
+ // Checking if custom inputs of type Phone number are valid to display error message on UI
+ if (eventType.customInputs.length) {
+ let isErrorFound = false;
+ eventType.customInputs.forEach((customInput) => {
+ if (customInput.required && customInput.type === EventTypeCustomInputType.PHONE) {
+ const input = bookingCustomInputs.find((i) => i.label === customInput.label);
+ try {
+ z.string({
+ errorMap: () => ({
+ message: `Missing ${customInput.type} customInput: '${customInput.label}'`,
+ }),
+ })
+ .refine((val) => isValidPhoneNumber(val), {
+ message: "Phone number is invalid",
+ })
+ .parse(input?.value);
+ } catch (err) {
+ isErrorFound = true;
+ bookingForm.setError(`customInputs.${customInput.id}`, {
+ type: "custom",
+ message: "Invalid Phone number",
+ });
+ }
+ }
+ });
+ if (isErrorFound) return;
+ }
+
telemetry.event(
top !== window ? telemetryEventTypes.embedBookingConfirmed : telemetryEventTypes.bookingConfirmed,
{ isTeamBooking: document.URL.includes("team/") }
@@ -355,10 +388,7 @@ const BookingPage = ({
attendeeAddress: booking.attendeeAddress,
}),
metadata,
- customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
- label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
- value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
- })),
+ customInputs: bookingCustomInputs,
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
@@ -386,10 +416,7 @@ const BookingPage = ({
attendeeAddress: booking.attendeeAddress,
}),
metadata,
- customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
- label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
- value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
- })),
+ customInputs: bookingCustomInputs,
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
@@ -793,6 +820,23 @@ const BookingPage = ({
)}
+ {input.type === EventTypeCustomInputType.PHONE && (
+
+
+ name={`customInputs.${input.id}`}
+ control={bookingForm.control}
+ placeholder={t("enter_phone_number")}
+ id={`customInputs.${input.id}`}
+ required={input.required}
+ />
+ {bookingForm.formState.errors?.customInputs?.[input.id] && (
+
+
+
{t("invalid_number")}
+
+ )}
+
+ )}
))}
{!eventType.disableGuests && guestToggle && (
diff --git a/apps/web/components/eventtype/CustomInputTypeForm.tsx b/apps/web/components/eventtype/CustomInputTypeForm.tsx
index 1a7d7fee20..240be6091d 100644
--- a/apps/web/components/eventtype/CustomInputTypeForm.tsx
+++ b/apps/web/components/eventtype/CustomInputTypeForm.tsx
@@ -40,6 +40,7 @@ const CustomInputTypeForm: FC = (props) => {
value: EventTypeCustomInputType.RADIO,
label: t("radio"),
},
+ { value: EventTypeCustomInputType.PHONE, label: t("phone_number") },
];
const { selectedCustomInput } = props;
diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts
index 880fc4dc24..763e041f92 100644
--- a/packages/features/bookings/lib/handleNewBooking.ts
+++ b/packages/features/bookings/lib/handleNewBooking.ts
@@ -7,6 +7,7 @@ import {
WebhookTriggerEvents,
} from "@prisma/client";
import async from "async";
+import { isValidPhoneNumber } from "libphonenumber-js";
import { cloneDeep } from "lodash";
import type { NextApiRequest } from "next";
import short from "short-uuid";
@@ -1095,6 +1096,16 @@ function handleCustomInputs(
z.literal(true, {
errorMap: () => ({ message: `Missing ${etcInput.type} customInput: '${etcInput.label}'` }),
}).parse(input?.value);
+ } else if (etcInput.type === "PHONE") {
+ z.string({
+ errorMap: () => ({
+ message: `Missing ${etcInput.type} customInput: '${etcInput.label}'`,
+ }),
+ })
+ .refine((val) => isValidPhoneNumber(val), {
+ message: "Phone number is invalid",
+ })
+ .parse(input?.value);
} else {
// type: NUMBER are also passed as string
z.string({
diff --git a/packages/features/ee/package.json b/packages/features/ee/package.json
index 3804e0c251..18043f1a38 100644
--- a/packages/features/ee/package.json
+++ b/packages/features/ee/package.json
@@ -12,6 +12,7 @@
"@hookform/resolvers": "^2.9.7",
"@sendgrid/client": "^7.7.0",
"@sendgrid/mail": "^7.6.2",
+ "libphonenumber-js": "^1.10.12",
"twilio": "^3.80.1",
"zod": "^3.20.2"
},
diff --git a/packages/prisma/migrations/20221121115813_/migration.sql b/packages/prisma/migrations/20221121115813_/migration.sql
new file mode 100644
index 0000000000..631ff23cbb
--- /dev/null
+++ b/packages/prisma/migrations/20221121115813_/migration.sql
@@ -0,0 +1,2 @@
+-- AlterEnum
+ALTER TYPE "EventTypeCustomInputType" ADD VALUE 'phone';
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index 20a630b61f..97b8b147e8 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -344,6 +344,7 @@ enum EventTypeCustomInputType {
NUMBER @map("number")
BOOL @map("bool")
RADIO @map("radio")
+ PHONE @map("phone")
}
model EventTypeCustomInput {