fix: new reassignment email for RR host reassignment (#17279)

* add new reassignment email

* improve reassign email

* fixes

* Minor copy fixup

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Carina Wollendorfer
2024-10-23 22:57:15 +00:00
committed by GitHub
co-authored by CarinaWolli Alex van Andel
parent 4021801698
commit d6dbbd1472
11 changed files with 103 additions and 18 deletions
@@ -39,10 +39,14 @@
"email_not_sent": "Error occurred while sending email",
"event_declined_subject": "Declined: {{title}} at {{date}}",
"event_cancelled_subject": "Canceled: {{title}} at {{date}}",
"event_reassigned_subject": "Reassigned: {{title}} at {{date}}",
"event_request_declined": "Your event request has been declined",
"event_request_declined_recurring": "Your recurring event request has been declined",
"event_request_cancelled": "Your scheduled event was canceled",
"event_request_reassigned": "Your scheduled event was reassigned",
"event_reassigned_subtitle": "You will no longer have the event on your calendar and your round robin likelihood will not be negatively impacted",
"organizer": "Organizer",
"reassigned_to": "Reassigned to",
"need_to_reschedule_or_cancel": "Need to reschedule or cancel?",
"you_can_view_booking_details_with_this_url": "You can view the booking details from this url {{url}} and add the event to your calendar",
"no_options_available": "No options available",
@@ -2654,10 +2658,10 @@
"no_columns_found": "No columns found",
"salesforce_create_record_as": "On booking, add events on and new attendees as:",
"salesforce_lead": "Lead",
"salesforce_contact_under_account": "Contact under an account",
"salesforce_contact_under_account": "Contact under an account",
"salesforce_skip_entry_creation": "Skip creating {{entry}} record if they do not exist in Salesforce",
"salesforce_if_account_does_not_exist": "If the contact does not exist under an account, create new lead from attendee",
"salesforce_create_new_contact_under_account": "Create a new contact under an account based on email domain of attendee and existing contacts",
"salesforce_create_new_contact_under_account": "Create a new contact under an account based on email domain of attendee and existing contacts",
"mass_assign_attributes": "Mass assign attributes",
"reroute_preview_custom_message": "It results in showing custom message. Try changing the response to route to an event",
"reroute_preview_external_redirect": "It results in redirecting to {{externalUrl}}. Try changing the response to route to an event",
+13 -4
View File
@@ -65,6 +65,7 @@ import OrganizerDailyVideoDownloadRecordingEmail from "./templates/organizer-dai
import OrganizerDailyVideoDownloadTranscriptEmail from "./templates/organizer-daily-video-download-transcript-email";
import OrganizerLocationChangeEmail from "./templates/organizer-location-change-email";
import OrganizerPaymentRefundFailedEmail from "./templates/organizer-payment-refund-failed-email";
import OrganizerReassignedEmail from "./templates/organizer-reassigned-email";
import OrganizerRequestEmail from "./templates/organizer-request-email";
import OrganizerRequestReminderEmail from "./templates/organizer-request-reminder-email";
import OrganizerRequestedToRescheduleEmail from "./templates/organizer-requested-to-reschedule-email";
@@ -191,7 +192,8 @@ export const sendRoundRobinRescheduledEmailsAndSMS = async (
export const sendRoundRobinCancelledEmailsAndSMS = async (
calEvent: CalendarEvent,
members: Person[],
eventTypeMetadata?: EventTypeMetadata
eventTypeMetadata?: EventTypeMetadata,
reassignedTo?: { name: string | null; email: string }
) => {
if (eventTypeDisableHostEmail(eventTypeMetadata)) return;
const calendarEvent = formatCalEvent(calEvent);
@@ -199,9 +201,16 @@ export const sendRoundRobinCancelledEmailsAndSMS = async (
const successfullyReScheduledSMS = new EventCancelledSMS(calEvent);
for (const teamMember of members) {
emailsAndSMSToSend.push(
sendEmail(() => new OrganizerCancelledEmail({ calEvent: calendarEvent, teamMember }))
);
if (!reassignedTo) {
emailsAndSMSToSend.push(
sendEmail(() => new OrganizerCancelledEmail({ calEvent: calendarEvent, teamMember }))
);
} else {
emailsAndSMSToSend.push(
sendEmail(() => new OrganizerReassignedEmail({ calEvent: calendarEvent, teamMember, reassignedTo }))
);
}
if (teamMember.phoneNumber) {
emailsAndSMSToSend.push(successfullyReScheduledSMS.sendSMSToAttendee(teamMember));
}
+1 -1
View File
@@ -5,7 +5,7 @@ import type { CalendarEvent } from "@calcom/types/Calendar";
import { Info } from "./Info";
const PersonInfo = ({ name = "", email = "", role = "", phoneNumber = "" }) => (
export const PersonInfo = ({ name = "", email = "", role = "", phoneNumber = "" }) => (
<div style={{ color: "#101010", fontWeight: 400, lineHeight: "24px" }}>
{name} - {role} {phoneNumber}
{!isSmsCalEmail(email) && (
@@ -15,6 +15,7 @@ import {
AppsStatus,
UserFieldsResponses,
} from "../components";
import { PersonInfo } from "../components/WhoInfo";
export const BaseScheduledEmail = (
props: {
@@ -26,6 +27,7 @@ export const BaseScheduledEmail = (
locale: string;
timeFormat: TimeFormat | undefined;
isOrganizer?: boolean;
reassignedTo?: { name: string | null; email: string };
} & Partial<React.ComponentProps<typeof BaseEmailHtml>>
) => {
const { t, timeZone, locale, timeFormat: timeFormat_ } = props;
@@ -79,7 +81,15 @@ export const BaseScheduledEmail = (
withSpacer
/>
)}
<Info label={t("rejection_reason")} description={props.calEvent.rejectionReason} withSpacer />
{props.reassignedTo && (
<Info
label={t("reassigned_to")}
description={
<PersonInfo name={props.reassignedTo.name || undefined} email={props.reassignedTo.email} />
}
withSpacer
/>
)}
<Info label={t("what")} description={props.calEvent.title} withSpacer />
<WhenInfo timeFormat={timeFormat} calEvent={props.calEvent} t={t} timeZone={timeZone} locale={locale} />
<WhoInfo calEvent={props.calEvent} t={t} />
@@ -0,0 +1,16 @@
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
export const OrganizerReassignedEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => {
const t = props.teamMember?.language.translate || props.calEvent.organizer.language.translate;
return (
<OrganizerScheduledEmail
title="event_request_reassigned"
headerType="xCircle"
subject="event_reassigned_subject"
subtitle={<>{t("event_reassigned_subtitle")}</>}
callToAction={null}
reassignedTo={props.reassignedTo}
{...props}
/>
);
};
@@ -10,6 +10,7 @@ export const OrganizerScheduledEmail = (
newSeat?: boolean;
attendeeCancelled?: boolean;
teamMember?: Person;
reassignedTo?: { email: string; name: string | null };
} & Partial<React.ComponentProps<typeof BaseScheduledEmail>>
) => {
let subject;
@@ -49,12 +50,17 @@ export const OrganizerScheduledEmail = (
timeFormat={timeFormat}
isOrganizer
subtitle={
<>
{props.attendeeCancelled
? t("attendee_no_longer_attending_subtitle", { name: props.attendee.name })
: ""}
</>
props.subtitle ? (
props.subtitle
) : (
<>
{props.attendeeCancelled
? t("attendee_no_longer_attending_subtitle", { name: props.attendee.name })
: ""}
</>
)
}
reassignedTo={props.reassignedTo}
{...props}
attendee={attendee}
/>
+1
View File
@@ -12,6 +12,7 @@ export { SlugReplacementEmail } from "./SlugReplacementEmail";
export { FeedbackEmail } from "./FeedbackEmail";
export { ForgotPasswordEmail } from "./ForgotPasswordEmail";
export { OrganizerCancelledEmail } from "./OrganizerCancelledEmail";
export { OrganizerReassignedEmail } from "./OrganizerReassignedEmail";
export { OrganizerLocationChangeEmail } from "./OrganizerLocationChangeEmail";
export { OrganizerPaymentRefundFailedEmail } from "./OrganizerPaymentRefundFailedEmail";
export { OrganizerRequestEmail } from "./OrganizerRequestEmail";
@@ -0,0 +1,31 @@
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
import { renderEmail } from "../";
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
import OrganizerScheduledEmail from "./organizer-scheduled-email";
export default class OrganizerReassignedEmail extends OrganizerScheduledEmail {
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> {
const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email];
return {
icalEvent: generateIcsFile({
calEvent: this.calEvent,
status: "CANCELLED",
role: GenerateIcsRole.ORGANIZER,
}),
from: `${EMAIL_FROM_NAME} <${this.getMailerOptions().from}>`,
to: toAddresses.join(","),
subject: `${this.t("event_reassigned_subject", {
title: this.calEvent.title,
date: this.getFormattedDate(),
})}`,
html: await renderEmail("OrganizerReassignedEmail", {
attendee: this.calEvent.organizer,
calEvent: this.calEvent,
reassignedTo: this.reassignedTo,
}),
text: this.getTextBody("event_request_reassigned"),
};
}
}
@@ -17,14 +17,21 @@ export default class OrganizerScheduledEmail extends BaseEmail {
t: TFunction;
newSeat?: boolean;
teamMember?: Person;
reassignedTo?: { name: string | null; email: string };
constructor(input: { calEvent: CalendarEvent; newSeat?: boolean; teamMember?: Person }) {
constructor(input: {
calEvent: CalendarEvent;
newSeat?: boolean;
teamMember?: Person;
reassignedTo?: { email: string; name: string | null };
}) {
super();
this.name = "SEND_BOOKING_CONFIRMATION";
this.calEvent = input.calEvent;
this.t = this.calEvent.organizer.language.translate;
this.newSeat = input.newSeat;
this.teamMember = input.teamMember;
this.reassignedTo = input.reassignedTo;
}
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> {
@@ -244,7 +244,6 @@ export const roundRobinManualReassignment = async ({
bookingFields: eventType.bookingFields ?? null,
booking,
}),
cancellationReason: "Manually re-assigned",
};
const credentials = await prisma.credential.findMany({
@@ -287,7 +286,7 @@ export const roundRobinManualReassignment = async ({
},
]);
// Send cancellation email to original organizer
// Send cancellation email to previous RR host
const cancelledEvt = cloneDeep(evt);
cancelledEvt.organizer = {
email: originalOrganizer.email,
@@ -308,7 +307,8 @@ export const roundRobinManualReassignment = async ({
language: { translate: previousRRHostT, locale: previousRRHost.locale || "en" },
},
],
eventType?.metadata as EventTypeMetadata
eventType?.metadata as EventTypeMetadata,
{ name: newUser.name, email: newUser.email }
);
}
@@ -375,7 +375,8 @@ export const roundRobinReassignment = async ({
language: { translate: previousRRHostT, locale: previousRRHost.locale || "en" },
},
],
eventType?.metadata as EventTypeMetadata
eventType?.metadata as EventTypeMetadata,
{ name: reassignedRRHost.name, email: reassignedRRHost.email }
);
}