diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts index d851f7f9f3..dee10018b7 100644 --- a/packages/lib/CalEventParser.ts +++ b/packages/lib/CalEventParser.ts @@ -2,7 +2,7 @@ import type { TFunction } from "next-i18next"; import short from "short-uuid"; import { v5 as uuidv5 } from "uuid"; -import type { CalendarEvent, Person } from "@calcom/types/Calendar"; +import type { CalendarEvent, Person, TeamMember } from "@calcom/types/Calendar"; import { WEBAPP_URL } from "./constants"; import getLabelValueMapFromResponses from "./getLabelValueMapFromResponses"; @@ -10,28 +10,59 @@ import isSmsCalEmail from "./isSmsCalEmail"; const translator = short(); -// The odd indentation in this file is necessary because otherwise the leading tabs will be applied into the event description. - -export const getWhat = (calEvent: Pick, t: TFunction) => { - return ` -${t("what")}: -${calEvent.title} - `; +export const getWhat = (calEvent: Pick, t: TFunction): string => { + return `${t("what")}:\n${calEvent.title.trim()}`; }; export const getWhen = ( calEvent: Pick, t: TFunction -) => { +): string => { return calEvent.seatsPerTimeSlot - ? ` -${t("organizer_timezone")}: -${calEvent.organizer.timeZone} - ` - : ` -${t("invitee_timezone")}: -${calEvent.attendees[0].timeZone} - `; + ? `${t("organizer_timezone")}:\n${calEvent.organizer.timeZone.trim()}` + : `${t("invitee_timezone")}:\n${calEvent.attendees[0].timeZone.trim()}`; +}; + +const getAttendeesText = (t: TFunction, hideAttendees: boolean, attendees?: Person[]): string => { + if (hideAttendees || !attendees || attendees.length === 0) return ""; + + /* + This will generate something like this: + John Doe + john.doe@acme.com + + Nic Doe + nic.doe@acme.com + + jane.doe@acme.com - Guest + + simon.doe@acme.com - Guest + */ + return attendees + .map((attendee) => { + const { name, email, phoneNumber } = attendee; + const label = !isSmsCalEmail(email) ? `${email}` : `${phoneNumber}`; + if (name) return `${name.trim()}\n${label.trim()}`; + // Guests don't have a name. + return `${label.trim()} - ${t("guest")}`; + }) + .join("\n\n"); +}; + +const getTeamMembersText = (t: TFunction, teamMembers?: TeamMember[]): string => { + if (!teamMembers || teamMembers.length === 0) return ""; + + /* + This will generate something like this: + John Doe - Team Member + john.doe@acme.com + + Jane Doe - Team Member + jane.doe@acme.com + */ + return teamMembers + .map((member) => `${member.name.trim()} - ${t("team_member")}\n${member.email.trim()}`) + .join("\n\n"); }; export const getWho = ( @@ -40,78 +71,42 @@ export const getWho = ( "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" >, t: TFunction -) => { - let attendeesFromCalEvent = [...calEvent.attendees]; - if (calEvent.seatsPerTimeSlot && !calEvent.seatsShowAttendees) { - attendeesFromCalEvent = []; - } - const attendees = attendeesFromCalEvent - .map((attendee) => { - return ` -${attendee?.name || t("guest")} -${!isSmsCalEmail(attendee.email) ? `${attendee.email}\n` : `${attendee.phoneNumber}\n`} +): string => { + const { attendees, organizer, team, seatsPerTimeSlot, seatsShowAttendees } = calEvent; -`; - }) + const organizerText = `${organizer.name} - ${t("organizer")}\n${organizer.email}`; + const hideAttendees = Boolean(seatsPerTimeSlot && !seatsShowAttendees); + const attendeesText = getAttendeesText(t, hideAttendees, attendees); + const teamMembersText = getTeamMembersText(t, team?.members); + const whoTexts = [organizerText, teamMembersText, attendeesText].filter(Boolean).join("\n\n"); - .join(""); - - const organizer = ` -${calEvent.organizer.name} - ${t("organizer")} -${calEvent.organizer.email} - `; - - const teamMembers = calEvent.team?.members - ? calEvent.team.members.map((member) => { - return ` -${member.name} - ${t("team_member")} -${member.email} - `; - }) - : []; - - return ` -${t("who")}: -${organizer + attendees + teamMembers.join("")} - `; + return `${t("who")}:\n${whoTexts}`; }; -export const getAdditionalNotes = (calEvent: Pick, t: TFunction) => { - if (!calEvent.additionalNotes) { - return ""; - } - return ` -${t("additional_notes")}: -${calEvent.additionalNotes} - `; +export const getAdditionalNotes = ( + calEvent: Pick, + t: TFunction +): string => { + if (!calEvent.additionalNotes) return ""; + return `${t("additional_notes")}:\n${calEvent.additionalNotes.trim()}`; }; -export const getUserFieldsResponses = (calEvent: Parameters[0]) => { +export const getUserFieldsResponses = ( + calEvent: Parameters[0] +): string => { const labelValueMap = getLabelValueMapFromResponses(calEvent); - if (!labelValueMap) { - return ""; - } - const responsesString = Object.keys(labelValueMap) - .map((key) => { - if (!labelValueMap) return ""; - if (labelValueMap[key] !== "") { - return ` -${key}: -${labelValueMap[key]} - `; - } - }) - .join(""); + if (!labelValueMap) return ""; - return responsesString; + return Object.keys(labelValueMap) + .map((key) => (labelValueMap[key] !== "" ? `${key}:\n${labelValueMap[key]}` : "")) + .join("\n\n"); }; -export const getAppsStatus = (calEvent: Pick, t: TFunction) => { - if (!calEvent.appsStatus) { - return ""; - } - return `\n${t("apps_status")} +export const getAppsStatus = (calEvent: Pick, t: TFunction): string => { + if (!calEvent.appsStatus) return ""; + + return `${t("apps_status")}: ${calEvent.appsStatus.map((app) => { return `\n- ${app.appName} ${ app.success >= 1 ? `✅ ${app.success > 1 ? `(x${app.success})` : ""}` : "" @@ -124,23 +119,20 @@ export const getAppsStatus = (calEvent: Pick, t: TF `; }; -export const getDescription = (calEvent: Pick, t: TFunction) => { - if (!calEvent.description) { - return ""; - } - return `\n${t("description")} - ${calEvent.description} - `; +export const getDescription = (calEvent: Pick, t: TFunction): string => { + if (!calEvent.description) return ""; + + return `${t("description")}:\n${calEvent.description.trim()}`; }; + export const getLocation = ( calEvent: Parameters[0] & Parameters[0] -) => { +): string => { const meetingUrl = getVideoCallUrlFromCalEvent(calEvent); - if (meetingUrl) { - return meetingUrl; - } + if (meetingUrl) return meetingUrl; + const providerName = getProviderName(calEvent); - return providerName || calEvent.location || ""; + return (providerName || calEvent.location || "").trim(); }; export const getProviderName = (calEvent: Pick): string => { @@ -168,7 +160,7 @@ const getSeatReferenceId = (calEvent: Pick): st return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : ""; }; -export const getBookingUrl = (calEvent: CalendarEvent) => { +export const getBookingUrl = (calEvent: CalendarEvent): string => { if (calEvent.platformClientId) { if (!calEvent.platformBookingUrl) return ""; return `${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${ @@ -184,7 +176,7 @@ export const getPlatformManageLink = ( Parameters[0]["calEvent"] & Pick, t: TFunction -) => { +): string => { const shouldDisplayReschedule = !calEvent.recurringEvent && calEvent.platformRescheduleUrl; let res = calEvent.platformBookingUrl || shouldDisplayReschedule || calEvent.platformCancelUrl @@ -214,7 +206,7 @@ export const getManageLink = ( calEvent: Parameters[0] & Pick, t: TFunction -) => { +): string => { if (calEvent.platformClientId) { return getPlatformManageLink(calEvent, t); } @@ -333,39 +325,34 @@ export const getRichDescription = ( ) => { const t = t_ ?? calEvent.organizer.language.translate; - return ` -${getCancellationReason(calEvent, t)} -${getWhat(calEvent, t)} -${getWhen(calEvent, t)} -${getWho(calEvent, t)} -${t("where")}: -${getLocation(calEvent)} -${getDescription(calEvent, t)} -${getAdditionalNotes(calEvent, t)} -${getUserFieldsResponses(calEvent)} -${includeAppStatus ? getAppsStatus(calEvent, t) : ""} -${ - // TODO: Only the original attendee can make changes to the event - // Guests cannot - calEvent.seatsPerTimeSlot ? "" : getManageLink(calEvent, t) -} -${ - calEvent.paymentInfo - ? ` -${t("pay_now")}: -${calEvent.paymentInfo.link} -` - : "" -} - `.trim(); + const location = getLocation(calEvent); + const locationField = location.length > 0 ? `${t("where")}:\n${location}` : ""; + + return [ + getCancellationReason(calEvent, t), + getWhat(calEvent, t), + getWhen(calEvent, t), + getWho(calEvent, t), + locationField, + getDescription(calEvent, t), + getAdditionalNotes(calEvent, t), + getUserFieldsResponses(calEvent), + includeAppStatus ? getAppsStatus(calEvent, t) : "", + // TODO: Only the original attendee can make changes to the event + // Guests cannot + calEvent.seatsPerTimeSlot ? "" : getManageLink(calEvent, t), + calEvent.paymentInfo ? `${t("pay_now")}:\n${calEvent.paymentInfo.link}` : "", + ] + .filter(Boolean) // Remove empty strings + .join("\n\n"); }; -export const getCancellationReason = (calEvent: Pick, t: TFunction) => { +export const getCancellationReason = ( + calEvent: Pick, + t: TFunction +): string => { if (!calEvent.cancellationReason) return ""; - return ` -${t("cancellation_reason")}: -${calEvent.cancellationReason} - `; + return `${t("cancellation_reason")}:\n${calEvent.cancellationReason}`; }; export const isDailyVideoCall = (calEvent: Pick): boolean => {