diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts index dee10018b7..d851f7f9f3 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, TeamMember } from "@calcom/types/Calendar"; +import type { CalendarEvent, Person } from "@calcom/types/Calendar"; import { WEBAPP_URL } from "./constants"; import getLabelValueMapFromResponses from "./getLabelValueMapFromResponses"; @@ -10,59 +10,28 @@ import isSmsCalEmail from "./isSmsCalEmail"; const translator = short(); -export const getWhat = (calEvent: Pick, t: TFunction): string => { - return `${t("what")}:\n${calEvent.title.trim()}`; +// 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 getWhen = ( calEvent: Pick, t: TFunction -): string => { +) => { return calEvent.seatsPerTimeSlot - ? `${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"); + ? ` +${t("organizer_timezone")}: +${calEvent.organizer.timeZone} + ` + : ` +${t("invitee_timezone")}: +${calEvent.attendees[0].timeZone} + `; }; export const getWho = ( @@ -71,42 +40,78 @@ export const getWho = ( "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" >, t: TFunction -): string => { - const { attendees, organizer, team, seatsPerTimeSlot, seatsShowAttendees } = calEvent; +) => { + 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`} - 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"); +`; + }) - return `${t("who")}:\n${whoTexts}`; + .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("")} + `; }; -export const getAdditionalNotes = ( - calEvent: Pick, - t: TFunction -): string => { - if (!calEvent.additionalNotes) return ""; - return `${t("additional_notes")}:\n${calEvent.additionalNotes.trim()}`; +export const getAdditionalNotes = (calEvent: Pick, t: TFunction) => { + if (!calEvent.additionalNotes) { + return ""; + } + return ` +${t("additional_notes")}: +${calEvent.additionalNotes} + `; }; -export const getUserFieldsResponses = ( - calEvent: Parameters[0] -): string => { +export const getUserFieldsResponses = (calEvent: Parameters[0]) => { const labelValueMap = getLabelValueMapFromResponses(calEvent); - if (!labelValueMap) return ""; + if (!labelValueMap) { + return ""; + } + const responsesString = Object.keys(labelValueMap) + .map((key) => { + if (!labelValueMap) return ""; + if (labelValueMap[key] !== "") { + return ` +${key}: +${labelValueMap[key]} + `; + } + }) + .join(""); - return Object.keys(labelValueMap) - .map((key) => (labelValueMap[key] !== "" ? `${key}:\n${labelValueMap[key]}` : "")) - .join("\n\n"); + return responsesString; }; -export const getAppsStatus = (calEvent: Pick, t: TFunction): string => { - if (!calEvent.appsStatus) return ""; - - return `${t("apps_status")}: +export const getAppsStatus = (calEvent: Pick, t: TFunction) => { + if (!calEvent.appsStatus) { + return ""; + } + return `\n${t("apps_status")} ${calEvent.appsStatus.map((app) => { return `\n- ${app.appName} ${ app.success >= 1 ? `✅ ${app.success > 1 ? `(x${app.success})` : ""}` : "" @@ -119,20 +124,23 @@ export const getAppsStatus = (calEvent: Pick, t: TF `; }; -export const getDescription = (calEvent: Pick, t: TFunction): string => { - if (!calEvent.description) return ""; - - return `${t("description")}:\n${calEvent.description.trim()}`; +export const getDescription = (calEvent: Pick, t: TFunction) => { + if (!calEvent.description) { + return ""; + } + return `\n${t("description")} + ${calEvent.description} + `; }; - 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 || "").trim(); + return providerName || calEvent.location || ""; }; export const getProviderName = (calEvent: Pick): string => { @@ -160,7 +168,7 @@ const getSeatReferenceId = (calEvent: Pick): st return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : ""; }; -export const getBookingUrl = (calEvent: CalendarEvent): string => { +export const getBookingUrl = (calEvent: CalendarEvent) => { if (calEvent.platformClientId) { if (!calEvent.platformBookingUrl) return ""; return `${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${ @@ -176,7 +184,7 @@ export const getPlatformManageLink = ( Parameters[0]["calEvent"] & Pick, t: TFunction -): string => { +) => { const shouldDisplayReschedule = !calEvent.recurringEvent && calEvent.platformRescheduleUrl; let res = calEvent.platformBookingUrl || shouldDisplayReschedule || calEvent.platformCancelUrl @@ -206,7 +214,7 @@ export const getManageLink = ( calEvent: Parameters[0] & Pick, t: TFunction -): string => { +) => { if (calEvent.platformClientId) { return getPlatformManageLink(calEvent, t); } @@ -325,34 +333,39 @@ export const getRichDescription = ( ) => { const t = t_ ?? calEvent.organizer.language.translate; - 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"); + 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(); }; -export const getCancellationReason = ( - calEvent: Pick, - t: TFunction -): string => { +export const getCancellationReason = (calEvent: Pick, t: TFunction) => { if (!calEvent.cancellationReason) return ""; - return `${t("cancellation_reason")}:\n${calEvent.cancellationReason}`; + return ` +${t("cancellation_reason")}: +${calEvent.cancellationReason} + `; }; export const isDailyVideoCall = (calEvent: Pick): boolean => {