disable standard confirmation emails if workflow exists (#8747)
* add switches to advanced even type settings * only show switches when workflow is enabled * check in event type update handler if user is allowed to disable standard emails * don't send emails if disabled * make sure emails can't be disabled if no workflow exist * send workflow emails to all attendees * code clean up * add translations * always send to all attendees when scheduling reminders * change text --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
co-authored by
CarinaWolli
parent
6fc9e9315d
commit
36d8554fd5
@@ -11,6 +11,10 @@ import { getEventName } from "@calcom/core/event";
|
||||
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||
import {
|
||||
allowDisablingAttendeeConfirmationEmails,
|
||||
allowDisablingHostConfirmationEmails,
|
||||
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
||||
@@ -43,6 +47,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
|
||||
const bookingFields: Prisma.JsonObject = {};
|
||||
|
||||
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
||||
|
||||
eventType.bookingFields.forEach(({ name }) => {
|
||||
bookingFields[name] = name + " input";
|
||||
});
|
||||
@@ -338,7 +344,51 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
{allowDisablingAttendeeConfirmationEmails(workflows) && (
|
||||
<>
|
||||
<hr className="border-subtle" />
|
||||
<Controller
|
||||
name="metadata.disableStandardEmails.confirmation.attendee"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
title={t("disable_attendees_confirmation_emails")}
|
||||
description={t("disable_attendees_confirmation_emails_description")}
|
||||
checked={value || false}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("metadata.disableStandardEmails.confirmation.attendee", e);
|
||||
onChange(e);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{allowDisablingHostConfirmationEmails(workflows) && (
|
||||
<>
|
||||
<hr className="border-subtle" />
|
||||
<Controller
|
||||
name="metadata.disableStandardEmails.confirmation.host"
|
||||
control={formMethods.control}
|
||||
defaultValue={!!eventType.seatsPerTimeSlot}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
title={t("disable_host_confirmation_emails")}
|
||||
description={t("disable_host_confirmation_emails_description")}
|
||||
checked={value || false}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("metadata.disableStandardEmails.confirmation.host", e);
|
||||
onChange(e);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showEventNameTip && (
|
||||
<CustomEventTypeModal
|
||||
close={closeEventNameTip}
|
||||
|
||||
@@ -1039,7 +1039,7 @@
|
||||
"event_cancelled_trigger": "when event is cancelled",
|
||||
"new_event_trigger": "when new event is booked",
|
||||
"email_host_action": "send email to host",
|
||||
"email_attendee_action": "send email to attendee",
|
||||
"email_attendee_action": "send email to attendees",
|
||||
"sms_attendee_action": "send SMS to attendee",
|
||||
"sms_number_action": "send SMS to a specific number",
|
||||
"workflows": "Workflows",
|
||||
@@ -1809,5 +1809,9 @@
|
||||
"pop_up_element_click": "Pop up via element click",
|
||||
"open_dialog_with_element_click": "Open your Cal dialog when someone clicks an element.",
|
||||
"need_help_embedding": "Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options.",
|
||||
"book_my_cal": "Book my Cal"
|
||||
"book_my_cal": "Book my Cal",
|
||||
"disable_attendees_confirmation_emails": "Disable default confirmation emails for attendees",
|
||||
"disable_attendees_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the attendees when the event is booked.",
|
||||
"disable_host_confirmation_emails": "Disable default confirmation emails for host",
|
||||
"disable_host_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the host when the event is booked."
|
||||
}
|
||||
|
||||
@@ -47,33 +47,42 @@ const sendEmail = (prepare: () => BaseEmail) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const sendScheduledEmails = async (calEvent: CalendarEvent, eventNameObject?: EventNameObjectType) => {
|
||||
export const sendScheduledEmails = async (
|
||||
calEvent: CalendarEvent,
|
||||
eventNameObject?: EventNameObjectType,
|
||||
hostEmailDisabled?: boolean,
|
||||
attendeeEmailDisabled?: boolean
|
||||
) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
|
||||
emailsToSend.push(sendEmail(() => new OrganizerScheduledEmail({ calEvent })));
|
||||
if (!hostEmailDisabled) {
|
||||
emailsToSend.push(sendEmail(() => new OrganizerScheduledEmail({ calEvent })));
|
||||
|
||||
if (calEvent.team) {
|
||||
for (const teamMember of calEvent.team.members) {
|
||||
emailsToSend.push(sendEmail(() => new OrganizerScheduledEmail({ calEvent, teamMember })));
|
||||
if (calEvent.team) {
|
||||
for (const teamMember of calEvent.team.members) {
|
||||
emailsToSend.push(sendEmail(() => new OrganizerScheduledEmail({ calEvent, teamMember })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emailsToSend.push(
|
||||
...calEvent.attendees.map((attendee) => {
|
||||
return sendEmail(
|
||||
() =>
|
||||
new AttendeeScheduledEmail(
|
||||
{
|
||||
...calEvent,
|
||||
...(eventNameObject && {
|
||||
title: getEventName({ ...eventNameObject, t: attendee.language.translate }),
|
||||
}),
|
||||
},
|
||||
attendee
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
if (!attendeeEmailDisabled) {
|
||||
emailsToSend.push(
|
||||
...calEvent.attendees.map((attendee) => {
|
||||
return sendEmail(
|
||||
() =>
|
||||
new AttendeeScheduledEmail(
|
||||
{
|
||||
...calEvent,
|
||||
...(eventNameObject && {
|
||||
title: getEventName({ ...eventNameObject, t: attendee.language.translate }),
|
||||
}),
|
||||
},
|
||||
attendee
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
||||
|
||||
@@ -52,9 +52,10 @@ export default class BaseEmail {
|
||||
...(parseSubject.success && { subject: decodeHTML(parseSubject.data) }),
|
||||
};
|
||||
|
||||
new Promise((resolve, reject) =>
|
||||
createTransport(this.getMailerOptions().transport)
|
||||
.sendMail(payloadWithUnEscapedSubject, (_err, info) => {
|
||||
new Promise((resolve, reject) =>
|
||||
createTransport(this.getMailerOptions().transport).sendMail(
|
||||
payloadWithUnEscapedSubject,
|
||||
(_err, info) => {
|
||||
if (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
this.printNodeMailerError(err);
|
||||
@@ -62,7 +63,8 @@ export default class BaseEmail {
|
||||
} else {
|
||||
resolve(info);
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
).catch((e) => console.error("sendEmail", e));
|
||||
return new Promise((resolve) => resolve("send mail async"));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ import {
|
||||
} from "@calcom/emails";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import {
|
||||
allowDisablingAttendeeConfirmationEmails,
|
||||
allowDisablingHostConfirmationEmails,
|
||||
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import { deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
|
||||
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
||||
@@ -1966,6 +1970,26 @@ async function handler(
|
||||
videoCallUrl = metadata.hangoutLink || defaultLocationUrl || videoCallUrl;
|
||||
}
|
||||
if (noEmail !== true) {
|
||||
let isHostConfirmationEmailsDisabled = false;
|
||||
let isAttendeeConfirmationEmailDisabled = false;
|
||||
|
||||
const workflows = eventType.workflows.map((workflow) => workflow.workflow);
|
||||
|
||||
if (eventType.workflows) {
|
||||
isHostConfirmationEmailsDisabled =
|
||||
eventType.metadata?.disableStandardEmails?.confirmation?.host || false;
|
||||
isAttendeeConfirmationEmailDisabled =
|
||||
eventType.metadata?.disableStandardEmails?.confirmation?.attendee || false;
|
||||
|
||||
if (isHostConfirmationEmailsDisabled) {
|
||||
isHostConfirmationEmailsDisabled = allowDisablingHostConfirmationEmails(workflows);
|
||||
}
|
||||
|
||||
if (isAttendeeConfirmationEmailDisabled) {
|
||||
isAttendeeConfirmationEmailDisabled = allowDisablingAttendeeConfirmationEmails(workflows);
|
||||
}
|
||||
}
|
||||
|
||||
await sendScheduledEmails(
|
||||
{
|
||||
...evt,
|
||||
@@ -1973,7 +1997,9 @@ async function handler(
|
||||
additionalNotes,
|
||||
customInputs,
|
||||
},
|
||||
eventNameObject
|
||||
eventNameObject,
|
||||
isHostConfirmationEmailsDisabled,
|
||||
isAttendeeConfirmationEmailDisabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ const ProfileView = () => {
|
||||
const utils = trpc.useContext();
|
||||
const session = useSession();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
|
||||
useLayoutEffect(() => {
|
||||
document.body.focus();
|
||||
}, []);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { Workflow, WorkflowStep } from "@calcom/prisma/client";
|
||||
import { WorkflowTriggerEvents } from "@calcom/prisma/client";
|
||||
import { WorkflowActions } from "@calcom/prisma/enums";
|
||||
|
||||
export function allowDisablingHostConfirmationEmails(
|
||||
workflows: (Workflow & {
|
||||
steps: WorkflowStep[];
|
||||
})[]
|
||||
) {
|
||||
return !!workflows.find(
|
||||
(workflow) =>
|
||||
workflow.trigger === WorkflowTriggerEvents.NEW_EVENT &&
|
||||
!!workflow.steps.find((step) => step.action === WorkflowActions.EMAIL_HOST)
|
||||
);
|
||||
}
|
||||
|
||||
export function allowDisablingAttendeeConfirmationEmails(
|
||||
workflows: (Workflow & {
|
||||
steps: WorkflowStep[];
|
||||
})[]
|
||||
) {
|
||||
return !!workflows.find(
|
||||
(workflow) =>
|
||||
workflow.trigger === WorkflowTriggerEvents.NEW_EVENT &&
|
||||
!!workflow.steps.find((step) => step.action === WorkflowActions.EMAIL_ATTENDEE)
|
||||
);
|
||||
}
|
||||
@@ -134,17 +134,33 @@ export const scheduleEmailReminder = async (
|
||||
triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT
|
||||
) {
|
||||
try {
|
||||
await sgMail.send({
|
||||
to: sendTo,
|
||||
from: {
|
||||
email: senderEmail,
|
||||
name: sender,
|
||||
},
|
||||
subject: emailContent.emailSubject,
|
||||
html: emailContent.emailBody,
|
||||
batchId: batchIdResponse[1].batch_id,
|
||||
replyTo: evt.organizer.email,
|
||||
});
|
||||
if (Array.isArray(sendTo)) {
|
||||
for (const email of sendTo) {
|
||||
await sgMail.send({
|
||||
to: email,
|
||||
from: {
|
||||
email: senderEmail,
|
||||
name: sender,
|
||||
},
|
||||
subject: emailContent.emailSubject,
|
||||
html: emailContent.emailBody,
|
||||
batchId: batchIdResponse[1].batch_id,
|
||||
replyTo: evt.organizer.email,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await sgMail.send({
|
||||
to: sendTo,
|
||||
from: {
|
||||
email: senderEmail,
|
||||
name: sender,
|
||||
},
|
||||
subject: emailContent.emailSubject,
|
||||
html: emailContent.emailBody,
|
||||
batchId: batchIdResponse[1].batch_id,
|
||||
replyTo: evt.organizer.email,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error sending Email");
|
||||
}
|
||||
|
||||
@@ -77,16 +77,19 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA
|
||||
step.action === WorkflowActions.EMAIL_ATTENDEE ||
|
||||
step.action === WorkflowActions.EMAIL_HOST
|
||||
) {
|
||||
let sendTo = "";
|
||||
let sendTo: string[] = [];
|
||||
|
||||
switch (step.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = evt.organizer?.email || "";
|
||||
sendTo = [evt.organizer?.email || ""];
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = !!emailAttendeeSendToOverride
|
||||
? emailAttendeeSendToOverride
|
||||
: evt.attendees?.[0]?.email || "";
|
||||
const attendees = !!emailAttendeeSendToOverride
|
||||
? [emailAttendeeSendToOverride]
|
||||
: evt.attendees?.map((attendee) => attendee.email);
|
||||
|
||||
sendTo = attendees;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,16 @@ export const EventTypeMetaDataSchema = z
|
||||
apps: z.object(appDataSchemas).partial().optional(),
|
||||
additionalNotesRequired: z.boolean().optional(),
|
||||
disableSuccessPage: z.boolean().optional(),
|
||||
disableStandardEmails: z
|
||||
.object({
|
||||
confirmation: z
|
||||
.object({
|
||||
host: z.boolean().optional(),
|
||||
attendee: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
managedEventConfig: z
|
||||
.object({
|
||||
unlockedFields: z.custom<{ [k in keyof Omit<Prisma.EventTypeSelect, "id">]: true }>().optional(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { NextApiResponse, GetServerSidePropsContext } from "next";
|
||||
import { stripeDataSchema } from "@calcom/app-store/stripepayment/lib/server";
|
||||
import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes";
|
||||
import { validateIntervalLimitOrder } from "@calcom/lib";
|
||||
import { WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -141,6 +142,44 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (input.metadata?.disableStandardEmails) {
|
||||
//check if user is allowed to disabled standard emails
|
||||
|
||||
const workflows = await ctx.prisma.workflow.findMany({
|
||||
where: {
|
||||
activeOn: {
|
||||
some: {
|
||||
eventTypeId: input.id,
|
||||
},
|
||||
},
|
||||
trigger: WorkflowTriggerEvents.NEW_EVENT,
|
||||
},
|
||||
include: {
|
||||
steps: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (input.metadata?.disableStandardEmails.confirmation?.host) {
|
||||
if (
|
||||
!workflows.find(
|
||||
(workflow) => !!workflow.steps.find((step) => step.action === WorkflowActions.EMAIL_HOST)
|
||||
)
|
||||
) {
|
||||
input.metadata.disableStandardEmails.confirmation.host = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.metadata?.disableStandardEmails.confirmation?.attendee) {
|
||||
if (
|
||||
!workflows.find(
|
||||
(workflow) => !!workflow.steps.find((step) => step.action === WorkflowActions.EMAIL_ATTENDEE)
|
||||
)
|
||||
) {
|
||||
input.metadata.disableStandardEmails.confirmation.attendee = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input?.price || input.metadata?.apps?.stripe?.price) {
|
||||
data.price = input.price || input.metadata?.apps?.stripe?.price;
|
||||
const paymentCredential = await ctx.prisma.credential.findFirst({
|
||||
|
||||
@@ -237,14 +237,14 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
step.action === WorkflowActions.EMAIL_ATTENDEE /*||
|
||||
step.action === WorkflowActions.EMAIL_ADDRESS*/
|
||||
) {
|
||||
let sendTo = "";
|
||||
let sendTo: string[] = [];
|
||||
|
||||
switch (step.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = bookingInfo.organizer?.email;
|
||||
sendTo = [bookingInfo.organizer?.email];
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = bookingInfo.attendees[0].email;
|
||||
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
||||
break;
|
||||
/*case WorkflowActions.EMAIL_ADDRESS:
|
||||
sendTo = step.sendTo || "";*/
|
||||
@@ -435,14 +435,14 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
newStep.action === WorkflowActions.EMAIL_ATTENDEE /*||
|
||||
newStep.action === WorkflowActions.EMAIL_ADDRESS*/
|
||||
) {
|
||||
let sendTo = "";
|
||||
let sendTo: string[] = [];
|
||||
|
||||
switch (newStep.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = bookingInfo.organizer?.email;
|
||||
sendTo = [bookingInfo.organizer?.email];
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = bookingInfo.attendees[0].email;
|
||||
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
||||
break;
|
||||
/*case WorkflowActions.EMAIL_ADDRESS:
|
||||
sendTo = newStep.sendTo || "";*/
|
||||
@@ -565,14 +565,14 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
step.action === WorkflowActions.EMAIL_HOST /*||
|
||||
step.action === WorkflowActions.EMAIL_ADDRESS*/
|
||||
) {
|
||||
let sendTo = "";
|
||||
let sendTo: string[] = [];
|
||||
|
||||
switch (step.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = bookingInfo.organizer?.email;
|
||||
sendTo = [bookingInfo.organizer?.email];
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = bookingInfo.attendees[0].email;
|
||||
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
||||
break;
|
||||
/*case WorkflowActions.EMAIL_ADDRESS:
|
||||
sendTo = step.sendTo || "";*/
|
||||
|
||||
Reference in New Issue
Block a user