diff --git a/apps/web/components/eventtype/CustomEventTypeModal.tsx b/apps/web/components/eventtype/CustomEventTypeModal.tsx index aa7984d94b..8ee5c02ea0 100644 --- a/apps/web/components/eventtype/CustomEventTypeModal.tsx +++ b/apps/web/components/eventtype/CustomEventTypeModal.tsx @@ -52,7 +52,8 @@ const CustomEventTypeModalForm: FC = (props) => { type="text" placeholder={placeHolder_} {...register("customEventName", { - validate: (value) => validateCustomEventName(value, t("invalid_event_name_variables")), + validate: (value) => + validateCustomEventName(value, t("invalid_event_name_variables"), event.bookingFields), })} className="mb-0" /> diff --git a/apps/web/components/eventtype/EventAdvancedTab.tsx b/apps/web/components/eventtype/EventAdvancedTab.tsx index e23389d0d3..b8bf84a2ad 100644 --- a/apps/web/components/eventtype/EventAdvancedTab.tsx +++ b/apps/web/components/eventtype/EventAdvancedTab.tsx @@ -12,6 +12,7 @@ import DestinationCalendarSelector from "@calcom/features/calendars/DestinationC import { FormBuilder } from "@calcom/features/form-builder/FormBuilder"; import { APP_NAME, CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import type { Prisma } from "@calcom/prisma/client"; import { trpc } from "@calcom/trpc/react"; import { Badge, Button, Checkbox, Label, SettingsToggle, showToast, TextField, Tooltip } from "@calcom/ui"; import { FiEdit, FiCopy } from "@calcom/ui/components/icon"; @@ -36,11 +37,19 @@ export const EventAdvancedTab = ({ eventType, team }: Pick { + bookingFields[name] = name + " input"; + }); + const eventNameObject: EventNameObjectType = { attendeeName: t("scheduler"), eventType: eventType.title, eventName: eventType.eventName, host: eventType.users[0]?.name || "Nameless", + bookingFields: bookingFields, t, }; @@ -308,7 +317,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index 84ca08284a..bdb0de015d 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -250,6 +250,7 @@ export default function Success(props: SuccessProps) { eventName: (props.dynamicEventName as string) || props.eventType.eventName, host: props.profile.name || "Nameless", location: location, + bookingFields: bookingInfo.responses, t, }; diff --git a/apps/web/pages/event-types/[type]/index.tsx b/apps/web/pages/event-types/[type]/index.tsx index 0aa39a988c..82f6d2dd8d 100644 --- a/apps/web/pages/event-types/[type]/index.tsx +++ b/apps/web/pages/event-types/[type]/index.tsx @@ -17,6 +17,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; +import type { Prisma } from "@calcom/prisma/client"; import { eventTypeBookingFields } from "@calcom/prisma/zod-utils"; import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -180,6 +181,12 @@ const EventTypePage = (props: EventTypeSetupProps) => { delete metadata.config?.useHostSchedulesForTeamEvent; } + const bookingFields: Prisma.JsonObject = {}; + + eventType.bookingFields.forEach(({ name }) => { + bookingFields[name] = name; + }); + const defaultValues = { title: eventType.title, locations: eventType.locations || [], @@ -211,9 +218,13 @@ const EventTypePage = (props: EventTypeSetupProps) => { // Make it optional because it's not submitted from all tabs of the page eventName: z .string() - .refine((val) => validateCustomEventName(val, t("invalid_event_name_variables")) === true, { - message: t("invalid_event_name_variables"), - }) + .refine( + (val) => + validateCustomEventName(val, t("invalid_event_name_variables"), bookingFields) === true, + { + message: t("invalid_event_name_variables"), + } + ) .optional(), length: z.union([z.string().transform((val) => +val), z.number()]).optional(), bookingFields: eventTypeBookingFields, diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 5aa876a7a7..e1b7041017 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1642,8 +1642,8 @@ "verification_code": "Verification code", "can_you_try_again": "Can you try again with a different time?", "verify": "Verify", + "invalid_event_name_variables":"There is an invalid variable in your event name", "select_all": "Select All", "default_conferencing_bulk_title": "Bulk update existing event types", - "default_conferencing_bulk_description": "Update the locations for the selected event types", - "invalid_event_name_variables": "There is an invalid variable in your event name" + "default_conferencing_bulk_description": "Update the locations for the selected event types" } diff --git a/packages/core/event.ts b/packages/core/event.ts index adea9b7eae..691181574a 100644 --- a/packages/core/event.ts +++ b/packages/core/event.ts @@ -1,6 +1,7 @@ import type { TFunction } from "next-i18next"; import { guessEventLocationType } from "@calcom/app-store/locations"; +import type { Prisma } from "@calcom/prisma/client"; export type EventNameObjectType = { attendeeName: string; @@ -8,6 +9,7 @@ export type EventNameObjectType = { eventName?: string | null; host: string; location?: string; + bookingFields?: Prisma.JsonObject; t: TFunction; }; @@ -31,21 +33,56 @@ export function getEventName(eventNameObj: EventNameObjectType, forAttendeeView eventName = eventName.replace("{LOCATION}", locationString); } - return ( - eventName - // Need this for compatibility with older event names - .replace("{Event type title}", eventNameObj.eventType) - .replace("{Scheduler}", eventNameObj.attendeeName) - .replace("{Organiser}", eventNameObj.host) - .replace("{USER}", eventNameObj.attendeeName) - .replace("{ATTENDEE}", eventNameObj.attendeeName) - .replace("{HOST}", eventNameObj.host) - .replace("{HOST/ATTENDEE}", forAttendeeView ? eventNameObj.host : eventNameObj.attendeeName) - ); + let dynamicEventName = eventName + // Need this for compatibility with older event names + .replaceAll("{Event type title}", eventNameObj.eventType) + .replaceAll("{Scheduler}", eventNameObj.attendeeName) + .replaceAll("{Organiser}", eventNameObj.host) + .replaceAll("{USER}", eventNameObj.attendeeName) + .replaceAll("{ATTENDEE}", eventNameObj.attendeeName) + .replaceAll("{HOST}", eventNameObj.host) + .replaceAll("{HOST/ATTENDEE}", forAttendeeView ? eventNameObj.host : eventNameObj.attendeeName); + + const customInputvariables = dynamicEventName.match(/\{(.+?)}/g)?.map((variable) => { + return variable.replace("{", "").replace("}", ""); + }); + + customInputvariables?.forEach((variable) => { + if (eventNameObj.bookingFields) { + Object.keys(eventNameObj.bookingFields).forEach((bookingField) => { + if (variable === bookingField) { + let fieldValue; + if (eventNameObj.bookingFields) { + fieldValue = + eventNameObj.bookingFields[bookingField as keyof typeof eventNameObj.bookingFields]?.toString(); + } + dynamicEventName = dynamicEventName.replace(`{${variable}}`, fieldValue || ""); + } + }); + } + }); + + return dynamicEventName; } -export const validateCustomEventName = (value: string, message: string) => { - const validVariables = ["{Event type title}", "{Organiser}", "{Scheduler}", "{Location}"]; +export const validateCustomEventName = ( + value: string, + message: string, + bookingFields?: Prisma.JsonObject +) => { + let customInputVariables: string[] = []; + if (bookingFields) { + customInputVariables = Object.keys(bookingFields).map((customInput) => { + return `{${customInput}}`; + }); + } + + const validVariables = customInputVariables.concat([ + "{Event type title}", + "{Organiser}", + "{Scheduler}", + "{Location}", + ]); const matches = value.match(/\{([^}]+)\}/g); if (matches?.length) { for (const item of matches) { diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 34cdb45a4a..6470b94ee8 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -714,6 +714,8 @@ async function handler( const attendeesList = [...invitee, ...guests]; + const responses = "responses" in reqBody ? reqBody.responses : null; + const eventNameObject = { //TODO: Can we have an unnamed attendee? If not, I would really like to throw an error here. attendeeName: bookerName || "Nameless", @@ -722,6 +724,7 @@ async function handler( // TODO: Can we have an unnamed organizer? If not, I would really like to throw an error here. host: organizerUser.name || "Nameless", location: bookingLocation, + bookingFields: { ...responses }, t: tOrganizer, }; @@ -733,7 +736,6 @@ async function handler( } } - const responses = "responses" in reqBody ? reqBody.responses : null; const calEventUserFieldsResponses = "calEventUserFieldsResponses" in reqBody ? reqBody.calEventUserFieldsResponses : null; let evt: CalendarEvent = {