Additional fields as variables for custom event name (#7454)

* feat: add custom validate name util

* refactor: separate custom event type modal into a
different component

* feat: add validation to zod

* chore: add i18n key

* feat: add dynamic imports

* fix: padding

* Omit cache-hit exit 1, assuming it'll fail regardless

* allow custom inputs as variables in event name

* fix ui for adding custom inputs

* show edited event name in modal

* code clean up

* merge fixes

* includes new booking fields logic

* fix event name with custom booking fields

* code clean up

* Update apps/web/public/static/locales/en/common.json

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* fix type error

* remove old reqbody variable

---------

Co-authored-by: nafees nazik <nafeesnazik21@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
pull/7643/head^2
Carina Wollendorfer 2023-03-09 10:11:16 -05:00 committed by GitHub
parent 943142e77f
commit d9a555d94a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 82 additions and 21 deletions

View File

@ -52,7 +52,8 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (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"
/>

View File

@ -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<EventTypeSetupProps,
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const bookingFields: Prisma.JsonObject = {};
eventType.bookingFields.forEach(({ name }) => {
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<EventTypeSetupProps,
<CustomEventTypeModal
close={closeEventNameTip}
setValue={setEventName}
defaultValue={eventType.eventName || ""}
defaultValue={formMethods.getValues("eventName") || eventType.eventName || ""}
placeHolder={eventNamePlaceholder}
event={eventNameObject}
/>

View File

@ -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,
};

View File

@ -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,

View File

@ -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"
}

View File

@ -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) {

View File

@ -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 = {