diff --git a/packages/app-store/feishucalendar/lib/CalendarService.ts b/packages/app-store/feishucalendar/lib/CalendarService.ts index f00a21504b..87ddc22019 100644 --- a/packages/app-store/feishucalendar/lib/CalendarService.ts +++ b/packages/app-store/feishucalendar/lib/CalendarService.ts @@ -392,7 +392,14 @@ class FeishuCalendarService implements Calendar { ], }; if (event.location) { - feishuEvent.location = { name: getLocation(event) }; + feishuEvent.location = { + name: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), + }; } return feishuEvent; }; diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index 0ec8373a0f..c4fc1a6a16 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -189,7 +189,12 @@ class GoogleCalendarService implements Calendar { } if (calEvent.location) { - payload["location"] = getLocation(calEvent); + payload["location"] = getLocation({ + videoCallData: calEvent.videoCallData, + additionalInformation: calEvent.additionalInformation, + location: calEvent.location, + uid: calEvent.uid, + }); } if (calEvent.recurringEvent) { @@ -246,7 +251,12 @@ class GoogleCalendarService implements Calendar { calendarId: selectedCalendar, eventId: event.id || "", requestBody: { - location: getLocation(calEvent), + location: getLocation({ + videoCallData: calEvent.videoCallData, + additionalInformation: calEvent.additionalInformation, + location: calEvent.location, + uid: calEvent.uid, + }), description: calEvent.calendarDescription, }, }); @@ -345,7 +355,12 @@ class GoogleCalendarService implements Calendar { }; if (event.location) { - payload["location"] = getLocation(event); + payload["location"] = getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }); } if (event.conferenceData && event.location === MeetLocationType) { diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index 04092a7eaa..63e3e9ac0c 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -55,7 +55,12 @@ class HubspotCalendarService implements CRM { private getHubspotMeetingBody = (event: CalendarEvent): string => { const userFields = getLabelValueMapFromResponses(event); const plainText = event?.description?.replace(/<\/?[^>]+(>|$)/g, "").replace(/_/g, " "); - const location = getLocation(event); + const location = getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }); const userFieldsHtml = Object.entries(userFields) .map(([key, value]) => { const formattedValue = typeof value === "boolean" ? (value ? "Yes" : "No") : value || "-"; @@ -283,7 +288,12 @@ class HubspotCalendarService implements CRM { hs_timestamp: Date.now().toString(), hs_meeting_title: event.title, hs_meeting_body: this.getHubspotMeetingBody(event), - hs_meeting_location: getLocation(event), + hs_meeting_location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), hs_meeting_start_time: new Date(event.startTime).toISOString(), hs_meeting_end_time: new Date(event.endTime).toISOString(), hs_meeting_outcome: "SCHEDULED", @@ -323,7 +333,12 @@ class HubspotCalendarService implements CRM { hs_timestamp: Date.now().toString(), hs_meeting_title: event.title, hs_meeting_body: this.getHubspotMeetingBody(event), - hs_meeting_location: getLocation(event), + hs_meeting_location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), hs_meeting_start_time: new Date(event.startTime).toISOString(), hs_meeting_end_time: new Date(event.endTime).toISOString(), hs_meeting_outcome: "RESCHEDULED", diff --git a/packages/app-store/larkcalendar/lib/CalendarService.ts b/packages/app-store/larkcalendar/lib/CalendarService.ts index 11618548d0..354056a9e7 100644 --- a/packages/app-store/larkcalendar/lib/CalendarService.ts +++ b/packages/app-store/larkcalendar/lib/CalendarService.ts @@ -392,7 +392,14 @@ class LarkCalendarService implements Calendar { ], }; if (event.location) { - larkEvent.location = { name: getLocation(event) }; + larkEvent.location = { + name: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), + }; } return larkEvent; }; diff --git a/packages/app-store/pipedrive-crm/lib/CrmService.ts b/packages/app-store/pipedrive-crm/lib/CrmService.ts index 2c3660d5ea..cc83f6e379 100644 --- a/packages/app-store/pipedrive-crm/lib/CrmService.ts +++ b/packages/app-store/pipedrive-crm/lib/CrmService.ts @@ -121,7 +121,12 @@ class PipedriveCrmService implements CRM { startDateTime: event.startTime, endDateTime: event.endTime, description: this.getMeetingBody(event), - location: getLocation(event), + location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), associations: { contactId: String(contacts[0].id), }, @@ -147,7 +152,12 @@ class PipedriveCrmService implements CRM { startDateTime: event.startTime, endDateTime: event.endTime, description: this.getMeetingBody(event), - location: getLocation(event), + location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), }; const headers = new Headers(); headers.append("x-revert-api-token", this.revertApiKey); diff --git a/packages/app-store/salesforce/lib/CrmService.ts b/packages/app-store/salesforce/lib/CrmService.ts index 1fd9cc6e6e..d10884679c 100644 --- a/packages/app-store/salesforce/lib/CrmService.ts +++ b/packages/app-store/salesforce/lib/CrmService.ts @@ -226,7 +226,12 @@ class SalesforceCRMService implements CRM { EndDateTime: new Date(event.endTime).toISOString(), Subject: event.title, Description: this.getSalesforceEventBody(event), - Location: getLocation(event), + Location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), ...options, ...(event.recurringEvent && { IsRecurrence2: true, @@ -334,7 +339,12 @@ class SalesforceCRMService implements CRM { EndDateTime: new Date(event.endTime).toISOString(), Subject: event.title, Description: this.getSalesforceEventBody(event), - Location: getLocation(event), + Location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), ...(event.recurringEvent && { IsRecurrence2: true, Recurrence2PatternText: new RRule(event.recurringEvent).toString(), diff --git a/packages/app-store/zoho-bigin/lib/CrmService.ts b/packages/app-store/zoho-bigin/lib/CrmService.ts index d52dba8607..5bae74c32a 100644 --- a/packages/app-store/zoho-bigin/lib/CrmService.ts +++ b/packages/app-store/zoho-bigin/lib/CrmService.ts @@ -189,7 +189,12 @@ class BiginCrmService implements CRM { Start_DateTime: toISO8601String(new Date(event.startTime)), End_DateTime: toISO8601String(new Date(event.endTime)), Description: event.additionalNotes, - Venue: getLocation(event), + Venue: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), Who_Id: contacts[0].id, }; @@ -249,7 +254,12 @@ class BiginCrmService implements CRM { Start_DateTime: toISO8601String(new Date(event.startTime)), End_DateTime: toISO8601String(new Date(event.endTime)), Description: event.additionalNotes, - Location: getLocation(event), + Location: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), }; return axios diff --git a/packages/app-store/zohocalendar/lib/CalendarService.ts b/packages/app-store/zohocalendar/lib/CalendarService.ts index e0c2469210..1e11d66ad4 100644 --- a/packages/app-store/zohocalendar/lib/CalendarService.ts +++ b/packages/app-store/zohocalendar/lib/CalendarService.ts @@ -473,7 +473,14 @@ class ZohoCalendarService implements Calendar { action: "popup", }, ], - location: event.location ? getLocation(event) : undefined, + location: event.location + ? getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }) + : undefined, }; return zohoEvent; diff --git a/packages/app-store/zohocrm/lib/CrmService.ts b/packages/app-store/zohocrm/lib/CrmService.ts index 308d1a2c94..1bdf522a35 100644 --- a/packages/app-store/zohocrm/lib/CrmService.ts +++ b/packages/app-store/zohocrm/lib/CrmService.ts @@ -134,7 +134,12 @@ class ZohoCrmCrmService implements CRM { Start_DateTime: toISO8601String(new Date(event.startTime)), End_DateTime: toISO8601String(new Date(event.endTime)), Description: this.getMeetingBody(event), - Venue: getLocation(event), + Venue: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), Who_Id: contacts[0].id, // Link the first attendee as the primary Who_Id }; @@ -158,7 +163,12 @@ class ZohoCrmCrmService implements CRM { Start_DateTime: toISO8601String(new Date(event.startTime)), End_DateTime: toISO8601String(new Date(event.endTime)), Description: this.getMeetingBody(event), - Venue: getLocation(event), + Venue: getLocation({ + videoCallData: event.videoCallData, + additionalInformation: event.additionalInformation, + location: event.location, + uid: event.uid, + }), }; return axios({ method: "put", diff --git a/packages/emails/src/components/ManageLink.tsx b/packages/emails/src/components/ManageLink.tsx index fb0cc35631..44dd754e3b 100644 --- a/packages/emails/src/components/ManageLink.tsx +++ b/packages/emails/src/components/ManageLink.tsx @@ -5,9 +5,30 @@ export function ManageLink(props: { calEvent: CalendarEvent; attendee: Person }) // Only the original attendee can make changes to the event // Guests cannot const t = props.attendee.language.translate; - const cancelLink = getCancelLink(props.calEvent, props.attendee); + const cancelLink = getCancelLink( + { + platformClientId: props.calEvent.platformClientId, + platformCancelUrl: props.calEvent.platformCancelUrl, + type: props.calEvent.type, + organizer: props.calEvent.organizer, + recurringEvent: props.calEvent.recurringEvent, + bookerUrl: props.calEvent.bookerUrl, + uid: props.calEvent.uid, + attendeeSeatId: props.calEvent.attendeeSeatId, + team: props.calEvent.team, + }, + props.attendee + ); const rescheduleLink = getRescheduleLink({ calEvent: props.calEvent, attendee: props.attendee }); - const bookingLink = getBookingUrl(props.calEvent); + const bookingLink = getBookingUrl({ + platformClientId: props.calEvent.platformClientId, + platformBookingUrl: props.calEvent.platformBookingUrl, + bookerUrl: props.calEvent.bookerUrl, + type: props.calEvent.type, + uid: props.calEvent.uid, + organizer: props.calEvent.organizer, + attendeeSeatId: props.calEvent.attendeeSeatId, + }); const isOriginalAttendee = props.attendee.email === props.calEvent.attendees[0]?.email; const isOrganizer = props.calEvent.organizer.email === props.attendee.email; diff --git a/packages/features/bookings/lib/EventManager.ts b/packages/features/bookings/lib/EventManager.ts index fafc7c8fea..9c216e0005 100644 --- a/packages/features/bookings/lib/EventManager.ts +++ b/packages/features/bookings/lib/EventManager.ts @@ -1249,7 +1249,7 @@ export default class EventManager { ? await featureRepo.checkIfTeamHasFeature(event.team.id, "salesforce-crm-tasker") : false; - const uid = getUid(event); + const uid = getUid(event.uid); for (const credential of this.crmCredentials) { if (isTaskerEnabledForSalesforceCrm) { if (!event.uid) { diff --git a/packages/features/calendars/lib/CalendarManager.ts b/packages/features/calendars/lib/CalendarManager.ts index caf1785275..278f89ce99 100644 --- a/packages/features/calendars/lib/CalendarManager.ts +++ b/packages/features/calendars/lib/CalendarManager.ts @@ -295,7 +295,7 @@ export const createEvent = async ( ): Promise> => { // Some calendar libraries may edit the original event so let's clone it const formattedEvent = formatCalEvent(originalEvent); - const uid: string = getUid(formattedEvent); + const uid: string = getUid(formattedEvent.uid); const calendar = await getCalendar(credential, "booking"); let success = true; let calError: string | undefined = undefined; @@ -394,7 +394,7 @@ export const updateEvent = async ( } const calEvent = processEvent(formattedEvent); - const uid = getUid(calEvent); + const uid = getUid(calEvent.uid); const calendar = await getCalendar(credential, "booking"); let success = false; let calError: string | undefined = undefined; diff --git a/packages/features/conferencing/lib/videoClient.ts b/packages/features/conferencing/lib/videoClient.ts index 470b4a7f06..d2c6f9ade2 100644 --- a/packages/features/conferencing/lib/videoClient.ts +++ b/packages/features/conferencing/lib/videoClient.ts @@ -30,7 +30,7 @@ const createMeeting = async ( credential: CredentialPayload | CredentialForCalendarService, calEvent: CalendarEvent ) => { - const uid: string = getUid(calEvent); + const uid: string = getUid(calEvent.uid); log.debug( "createMeeting", safeStringify({ diff --git a/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts b/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts index 624acb43c9..a80038e774 100644 --- a/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts +++ b/packages/features/ee/workflows/lib/service/EmailWorkflowService.ts @@ -438,7 +438,7 @@ export class EmailWorkflowService { sendToEmail: sendTo[0], }); const meetingUrl = - getVideoCallUrlFromCalEvent(evt) || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl; + getVideoCallUrlFromCalEvent({ videoCallData: evt.videoCallData }) || bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl; const variables: VariablesType = { eventName: evt.title || "", organizerName: evt.organizer.name, diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts index 15b9a500c1..e3564ae936 100644 --- a/packages/lib/CalEventParser.ts +++ b/packages/lib/CalEventParser.ts @@ -3,7 +3,17 @@ import short from "short-uuid"; import { v5 as uuidv5 } from "uuid"; import getLabelValueMapFromResponses from "@calcom/lib/bookings/getLabelValueMapFromResponses"; -import type { CalendarEvent, Person } from "@calcom/types/Calendar"; +import { Prisma } from "@calcom/prisma/client"; +import type { + AdditionalInformation, + AppsStatus, + CalendarEvent, + CalEventResponses, + Person, + RecurringEvent, + TeamMember, + VideoCallData, +} from "@calcom/types/Calendar"; import { WEBAPP_URL } from "./constants"; import isSmsCalEmail from "./isSmsCalEmail"; @@ -12,12 +22,16 @@ 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")}:\n${calEvent.title}`; +export const getWhat = (title: string, t: TFunction) => { + return `${t("what")}:\n${title}`; }; export const getWhen = ( - calEvent: Pick, + calEvent: { + organizer: Person; + attendees?: Person[]; + seatsPerTimeSlot?: number | null; + }, t: TFunction ) => { const organizerTimezone = calEvent.organizer?.timeZone ?? "UTC"; @@ -30,10 +44,17 @@ export const getWhen = ( }; export const getWho = ( - calEvent: Pick< - CalendarEvent, - "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" | "hideOrganizerEmail" - >, + calEvent: { + attendees: Person[]; + seatsPerTimeSlot?: number | null; + seatsShowAttendees?: boolean | null; + organizer: Person; + team?: { + id: number; + members?: TeamMember[]; + }; + hideOrganizerEmail?: boolean; + }, t: TFunction ) => { let attendeesFromCalEvent = [...calEvent.attendees]; @@ -68,15 +89,20 @@ export const getWho = ( }`; }; -export const getAdditionalNotes = (calEvent: Pick, t: TFunction) => { - if (!calEvent.additionalNotes) { +export const getAdditionalNotes = (t: TFunction, additionalNotes?: string | null) => { + if (!additionalNotes) { return ""; } - return `${t("additional_notes")}:\n${calEvent.additionalNotes}`; + return `${t("additional_notes")}:\n${additionalNotes}`; }; export const getUserFieldsResponses = ( - calEvent: Parameters[0], + calEvent: { + customInputs?: Prisma.JsonObject | null; + userFieldsResponses?: CalEventResponses | null; + responses?: CalEventResponses | null; + eventTypeId?: number | null; + }, t: TFunction ) => { const labelValueMap = getLabelValueMapFromResponses(calEvent); @@ -99,12 +125,12 @@ ${labelValueMap[key]} return responsesString; }; -export const getAppsStatus = (calEvent: Pick, t: TFunction) => { - if (!calEvent.appsStatus) { +export const getAppsStatus = (t: TFunction, appsStatus?: AppsStatus[] | null) => { + if (!appsStatus) { return ""; } return `\n${t("apps_status")} - ${calEvent.appsStatus.map((app) => { + ${appsStatus.map((app) => { return `\n- ${app.appName} ${ app.success >= 1 ? `✅ ${app.success > 1 ? `(x${app.success})` : ""}` : "" }${ @@ -145,79 +171,103 @@ const htmlToPlainText = (html: string): string => { ); }; -export const getDescription = (calEvent: Pick, t: TFunction) => { - if (!calEvent.description) { +export const getDescription = (t: TFunction, description?: string | null) => { + if (!description) { return ""; } - const plainText = htmlToPlainText(calEvent.description); + const plainText = htmlToPlainText(description); return `${t("description")}\n${plainText}`; }; -export const getLocation = ( - calEvent: Parameters[0] & Parameters[0] -) => { +export const getLocation = (calEvent: { + videoCallData?: { type?: string; url?: string }; + additionalInformation?: AdditionalInformation; + location?: string | null; + uid?: string | null; +}) => { const meetingUrl = getVideoCallUrlFromCalEvent(calEvent); if (meetingUrl) { return meetingUrl; } - const providerName = getProviderName(calEvent); + const providerName = getProviderName(calEvent.location); return providerName || calEvent.location || ""; }; -export const getProviderName = (calEvent: Pick): string => { +export const getProviderName = (location?: string | null): string => { // TODO: use getAppName from @calcom/app-store - if (calEvent.location && calEvent.location.includes("integrations:")) { - let location = calEvent.location.split(":")[1]; - if (location === "daily") { - location = "Cal Video"; + if (location && location.includes("integrations:")) { + let locationName = location.split(":")[1]; + if (locationName === "daily") { + locationName = "Cal Video"; } - return location[0].toUpperCase() + location.slice(1); + return locationName[0].toUpperCase() + locationName.slice(1); } // If location its a url, probably we should be validating it with a custom library - if (calEvent.location && /^https?:\/\//.test(calEvent.location)) { - return calEvent.location; + if (location && /^https?:\/\//.test(location)) { + return location; } return ""; }; -export const getUid = (calEvent: Pick): string => { - const uid = calEvent.uid; - return uid ?? translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL)); +export const getUid = (uid?: string | null): string => { + // If uid is provided, return it. Otherwise generate a random short UUID as fallback. + // Note: The original implementation used JSON.stringify(calEvent) for deterministic fallback, + // but with narrow inputs we can't do that. In practice, uid should always be set. + return uid ?? translator.new(); }; -const getSeatReferenceId = (calEvent: Pick): string => { - return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : ""; +const getSeatReferenceId = (attendeeSeatId?: string): string => { + return attendeeSeatId ? attendeeSeatId : ""; }; -export const getBookingUrl = (calEvent: CalendarEvent) => { - const seatReferenceUid = getSeatReferenceId(calEvent); +export const getBookingUrl = (calEvent: { + platformClientId?: string | null; + platformBookingUrl?: string | null; + bookerUrl?: string; + type: string; + uid?: string | null; + organizer: Person; + attendeeSeatId?: string; +}) => { + const seatReferenceUid = getSeatReferenceId(calEvent.attendeeSeatId); if (calEvent.platformClientId) { if (!calEvent.platformBookingUrl) return ""; - return `${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${ + return `${calEvent.platformBookingUrl}/${getUid(calEvent.uid)}?slug=${calEvent.type}&username=${ calEvent.organizer.username }${seatReferenceUid ? `&seatReferenceUid=${seatReferenceUid}` : ""}&changes=true`; } - return `${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(calEvent)}?changes=true`; + return `${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid(calEvent.uid)}?changes=true`; }; export const getPlatformManageLink = ( - calEvent: Parameters[0] & - Parameters[0]["calEvent"] & - Pick, + calEvent: { + platformBookingUrl?: string | null; + platformRescheduleUrl?: string | null; + platformCancelUrl?: string | null; + type: string; + organizer: Person; + recurringEvent?: RecurringEvent | null; + bookerUrl?: string | null; + uid?: string | null; + attendeeSeatId?: string; + team?: { + id: number; + }; + }, t: TFunction ) => { const shouldDisplayReschedule = !calEvent.recurringEvent && calEvent.platformRescheduleUrl; - const seatUid = getSeatReferenceId(calEvent); + const seatUid = getSeatReferenceId(calEvent.attendeeSeatId); let res = calEvent.platformBookingUrl || shouldDisplayReschedule || calEvent.platformCancelUrl ? `${t("need_to_reschedule_or_cancel")} ` : ``; if (calEvent.platformBookingUrl) { - res += `Check Here: ${calEvent.platformBookingUrl}/${getUid(calEvent)}?slug=${calEvent.type}&username=${ - calEvent.organizer.username - }${calEvent?.team ? `&teamId=${calEvent.team.id}` : ""}${ + res += `Check Here: ${calEvent.platformBookingUrl}/${getUid(calEvent.uid)}?slug=${ + calEvent.type + }&username=${calEvent.organizer.username}${calEvent?.team ? `&teamId=${calEvent.team.id}` : ""}${ seatUid ? `&seatReferenceUid=${seatUid}` : "" }&changes=true${calEvent.platformCancelUrl || shouldDisplayReschedule ? ` ${t("or_lowercase")} ` : ""}`; } @@ -235,8 +285,21 @@ export const getPlatformManageLink = ( }; export const getManageLink = ( - calEvent: Parameters[0] & - Pick, + calEvent: { + platformClientId?: string | null; + platformBookingUrl?: string | null; + platformCancelUrl?: string | null; + platformRescheduleUrl?: string | null; + type: string; + organizer: Person; + recurringEvent?: RecurringEvent | null; + bookerUrl?: string | null; + uid?: string | null; + attendeeSeatId?: string; + team?: { + id: number; + }; + }, t: TFunction ) => { if (calEvent.platformClientId) { @@ -244,12 +307,23 @@ export const getManageLink = ( } return `${t("need_to_reschedule_or_cancel")} ${calEvent.bookerUrl ?? WEBAPP_URL}/booking/${getUid( - calEvent + calEvent.uid )}?changes=true`; }; export const getPlatformCancelLink = ( - calEvent: Pick, + calEvent: { + platformCancelUrl?: string | null; + type: string; + organizer: Person; + recurringEvent?: RecurringEvent | null; + bookerUrl?: string | null; + uid?: string | null; + attendeeSeatId?: string; + team?: { + id: number; + }; + }, bookingUid: string, seatUid?: string ): string => { @@ -269,14 +343,23 @@ export const getPlatformCancelLink = ( }; export const getCancelLink = ( - calEvent: Parameters[0] & - Parameters[0] & - Parameters[0] & - Pick, + calEvent: { + platformClientId?: string | null; + platformCancelUrl?: string | null; + type: string; + organizer: Person; + recurringEvent?: RecurringEvent | null; + bookerUrl?: string | null; + uid?: string | null; + attendeeSeatId?: string; + team?: { + id: number; + }; + }, attendee?: Person ): string => { - const Uid = getUid(calEvent); - const seatReferenceUid = getSeatReferenceId(calEvent); + const Uid = getUid(calEvent.uid); + const seatReferenceUid = getSeatReferenceId(calEvent.attendeeSeatId); if (calEvent.platformClientId) { return getPlatformCancelLink(calEvent, Uid, seatReferenceUid); } @@ -292,7 +375,14 @@ export const getCancelLink = ( }; export const getPlatformRescheduleLink = ( - calEvent: Pick, + calEvent: { + platformRescheduleUrl?: string | null; + type: string; + organizer: Person; + team?: { + id: number; + }; + }, bookingUid: string, seatUid?: string ): string => { @@ -316,15 +406,23 @@ export const getRescheduleLink = ({ allowRescheduleForCancelledBooking = false, attendee, }: { - calEvent: Parameters[0] & - Parameters[0] & - Parameters[0] & - Pick; + calEvent: { + platformClientId?: string | null; + platformRescheduleUrl?: string | null; + type: string; + organizer: Person; + team?: { + id: number; + }; + uid?: string | null; + attendeeSeatId?: string; + bookerUrl?: string | null; + }; allowRescheduleForCancelledBooking?: boolean; attendee?: Person; }): string => { - const Uid = getUid(calEvent); - const seatUid = getSeatReferenceId(calEvent); + const Uid = getUid(calEvent.uid); + const seatUid = getSeatReferenceId(calEvent.attendeeSeatId); if (calEvent.platformClientId) { return getPlatformRescheduleLink(calEvent, Uid, seatUid); @@ -341,16 +439,35 @@ export const getRescheduleLink = ({ return url.toString(); }; -type RichDescriptionCalEvent = Parameters[0] & - Parameters[0] & - Parameters[0] & - Parameters[0] & - Parameters[0] & - Parameters[0] & - Parameters[0] & - Parameters[0] & - Parameters[0] & - Pick; +type RichDescriptionCalEvent = { + organizer: Person; + paymentInfo?: { + link?: string | null; + } | null; + type: string; + cancellationReason?: string | null; + title: string; + attendees: Person[]; + seatsPerTimeSlot?: number | null; + seatsShowAttendees?: boolean | null; + team?: { + id: number; + members?: TeamMember[]; + }; + additionalNotes?: string | null; + customInputs?: Prisma.JsonObject | null; + description?: string | null; + userFieldsResponses?: CalEventResponses | null; + responses?: CalEventResponses | null; + eventTypeId?: number | null; + appsStatus?: AppsStatus[] | null; + manageLink?: string | null; + hideOrganizerEmail?: boolean; + videoCallData?: { type?: string; url?: string }; + additionalInformation?: AdditionalInformation; + location?: string | null; + uid?: string | null; +} export const getRichDescriptionHTML = ( calEvent: RichDescriptionCalEvent, @@ -389,14 +506,45 @@ export const getRichDescriptionHTML = ( // Build the HTML content for each section const parts = [ - textToHtml(getCancellationReason(calEvent, t)), - textToHtml(getWhat(calEvent, t)), - textToHtml(getWhen(calEvent, t)), - textToHtml(getWho(calEvent, t)), - textToHtml(getDescription(calEvent, t)), - textToHtml(getAdditionalNotes(calEvent, t)), - textToHtml(getUserFieldsResponses(calEvent, t)), - includeAppStatus ? textToHtml(getAppsStatus(calEvent, t)) : "", + textToHtml(getCancellationReason(t, calEvent.cancellationReason)), + textToHtml(getWhat(calEvent.title, t)), + textToHtml( + getWhen( + { + organizer: calEvent.organizer, + attendees: calEvent.attendees, + seatsPerTimeSlot: calEvent.seatsPerTimeSlot, + }, + t + ) + ), + textToHtml( + getWho( + { + attendees: calEvent.attendees, + seatsPerTimeSlot: calEvent.seatsPerTimeSlot, + seatsShowAttendees: calEvent.seatsShowAttendees, + organizer: calEvent.organizer, + team: calEvent.team, + hideOrganizerEmail: calEvent.hideOrganizerEmail, + }, + t + ) + ), + textToHtml(getDescription(t, calEvent.description)), + textToHtml(getAdditionalNotes(t, calEvent.additionalNotes)), + textToHtml( + getUserFieldsResponses( + { + customInputs: calEvent.customInputs, + userFieldsResponses: calEvent.userFieldsResponses, + responses: calEvent.responses, + eventTypeId: calEvent.eventTypeId, + }, + t + ) + ), + includeAppStatus ? textToHtml(getAppsStatus(t, calEvent.appsStatus)) : "", manageLinkHtml, calEvent.paymentInfo ? `

${t("pay_now")}: ${ @@ -419,15 +567,37 @@ export const getRichDescription = ( // Join all parts with single newlines and remove extra whitespace const parts = [ - getCancellationReason(calEvent, t), - getWhat(calEvent, t), - getWhen(calEvent, t), - getWho(calEvent, t), - `${t("where")}:\n${getLocation(calEvent)}`, - getDescription(calEvent, t), - getAdditionalNotes(calEvent, t), + getCancellationReason(t, calEvent.cancellationReason), + getWhat(calEvent.title, t), + getWhen( + { + organizer: calEvent.organizer, + attendees: calEvent.attendees, + seatsPerTimeSlot: calEvent.seatsPerTimeSlot, + }, + t + ), + getWho( + { + attendees: calEvent.attendees, + seatsPerTimeSlot: calEvent.seatsPerTimeSlot, + seatsShowAttendees: calEvent.seatsShowAttendees, + organizer: calEvent.organizer, + team: calEvent.team, + hideOrganizerEmail: calEvent.hideOrganizerEmail, + }, + t + ), + `${t("where")}:\n${getLocation({ + videoCallData: calEvent.videoCallData, + additionalInformation: calEvent.additionalInformation, + location: calEvent.location, + uid: calEvent.uid, + })}`, + getDescription(t, calEvent.description), + getAdditionalNotes(t, calEvent.additionalNotes), getUserFieldsResponses(calEvent, t), - includeAppStatus ? getAppsStatus(calEvent, t) : "", + includeAppStatus ? getAppsStatus(t, calEvent.appsStatus) : "", // TODO: Only the original attendee can make changes to the event // Guests cannot calEvent.seatsPerTimeSlot ? "" : getManageLink(calEvent, t), @@ -441,31 +611,33 @@ export const getRichDescription = ( return parts; }; -export const getCancellationReason = (calEvent: Pick, t: TFunction) => { - if (!calEvent.cancellationReason) return ""; - const sanitized = calEvent.cancellationReason.startsWith("$RCH$") - ? calEvent.cancellationReason.substring(5).trim() - : calEvent.cancellationReason.trim(); +export const getCancellationReason = (t: TFunction, cancellationReason?: string | null) => { + if (!cancellationReason) return ""; + const sanitized = cancellationReason.startsWith("$RCH$") + ? cancellationReason.substring(5).trim() + : cancellationReason.trim(); return `${t("cancellation_reason")}:\n${sanitized}`; }; -export const isDailyVideoCall = (calEvent: Pick): boolean => { - return calEvent?.videoCallData?.type === "daily_video"; +export const isDailyVideoCall = (videoCallData?: VideoCallData): boolean => { + return videoCallData?.type === "daily_video"; }; -export const getPublicVideoCallUrl = (calEvent: Pick): string => { - return `${WEBAPP_URL}/video/${getUid(calEvent)}`; +export const getPublicVideoCallUrl = (uid?: string | null): string => { + return `${WEBAPP_URL}/video/${getUid(uid)}`; }; -export const getVideoCallUrlFromCalEvent = ( - calEvent: Parameters[0] & - Pick -): string => { +export const getVideoCallUrlFromCalEvent = (calEvent: { + videoCallData?: { type?: string; url?: string }; + additionalInformation?: AdditionalInformation; + location?: string | null; + uid?: string | null; +}): string => { if (calEvent.videoCallData) { - if (isDailyVideoCall(calEvent)) { - return getPublicVideoCallUrl(calEvent); + if (calEvent.videoCallData.type === "daily_video") { + return getPublicVideoCallUrl(calEvent.uid); } - return calEvent.videoCallData.url; + return calEvent.videoCallData.url ?? ""; } if (calEvent.additionalInformation?.hangoutLink) { return calEvent.additionalInformation.hangoutLink; @@ -476,6 +648,6 @@ export const getVideoCallUrlFromCalEvent = ( return ""; }; -export const getVideoCallPassword = (calEvent: CalendarEvent): string => { - return isDailyVideoCall(calEvent) ? "" : calEvent?.videoCallData?.password ?? ""; +export const getVideoCallPassword = (videoCallData?: VideoCallData): string => { + return isDailyVideoCall(videoCallData) ? "" : videoCallData?.password ?? ""; }; diff --git a/packages/lib/bookings/getLabelValueMapFromResponses.ts b/packages/lib/bookings/getLabelValueMapFromResponses.ts index d72eb52afa..b072a0e821 100644 --- a/packages/lib/bookings/getLabelValueMapFromResponses.ts +++ b/packages/lib/bookings/getLabelValueMapFromResponses.ts @@ -2,10 +2,16 @@ import type z from "zod"; import { TITLE_FIELD, SMS_REMINDER_NUMBER_FIELD } from "@calcom/lib/bookings/SystemField"; import type { dbReadResponseSchema as bookingResponse } from "@calcom/lib/dbReadResponseSchema"; -import type { CalendarEvent } from "@calcom/types/Calendar"; +import type { CalEventResponses } from "@calcom/types/Calendar"; +import type { Prisma } from "@calcom/prisma/client"; export default function getLabelValueMapFromResponses( - calEvent: Pick, + calEvent: { + customInputs?: Prisma.JsonObject | null; + userFieldsResponses?: CalEventResponses | null; + responses?: CalEventResponses | null; + eventTypeId?: number | null; + }, isOrganizer = false ) { const { customInputs, userFieldsResponses, responses, eventTypeId } = calEvent; diff --git a/packages/lib/test/CalEventParser.test.ts b/packages/lib/test/CalEventParser.test.ts index 064efaf47e..aa281096d7 100644 --- a/packages/lib/test/CalEventParser.test.ts +++ b/packages/lib/test/CalEventParser.test.ts @@ -56,7 +56,7 @@ describe("getVideoCallUrl", () => { }), }); - expect(getVideoCallUrlFromCalEvent(calEvent)).toEqual(getPublicVideoCallUrl(calEvent)); + expect(getVideoCallUrlFromCalEvent(calEvent)).toEqual(getPublicVideoCallUrl(calEvent.uid)); }); }); @@ -68,12 +68,12 @@ describe("getVideoCallPassword", () => { }), }); - expect(getVideoCallPassword(calEvent)).toEqual(""); + expect(getVideoCallPassword(calEvent.videoCallData)).toEqual(""); }); it("should return original password for other video call meetings", () => { const calEvent = buildCalendarEvent(); expect(calEvent?.videoCallData?.type).not.toBe("daily_video"); - expect(getVideoCallPassword(calEvent)).toEqual(calEvent?.videoCallData.password); + expect(getVideoCallPassword(calEvent.videoCallData)).toEqual(calEvent?.videoCallData?.password); }); });