diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 424a7441a7..890138ac50 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1296,6 +1296,7 @@ "update_timezone_question": "Update Timezone?", "update_timezone_description": "It seems like your local timezone has changed to {{formattedCurrentTz}}. It's very important to have the correct timezone to prevent bookings at undesired times. Do you want to update it?", "dont_update": "Don't update", + "email_address_action": "send email to a specific email address", "after_event_trigger": "after event ends", "how_long_after": "How long after event ends?" } diff --git a/packages/features/ee/workflows/api/scheduleEmailReminders.ts b/packages/features/ee/workflows/api/scheduleEmailReminders.ts index ddf4b21529..a52e59893f 100644 --- a/packages/features/ee/workflows/api/scheduleEmailReminders.ts +++ b/packages/features/ee/workflows/api/scheduleEmailReminders.ts @@ -66,10 +66,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { for (const reminder of unscheduledReminders) { if (dayjs(reminder.scheduledDate).isBefore(dateInSeventyTwoHours)) { try { - const sendTo = - reminder.workflowStep.action === WorkflowActions.EMAIL_HOST - ? reminder.booking?.user?.email - : reminder.booking?.attendees[0].email; + let sendTo; + + switch (reminder.workflowStep.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = reminder.booking?.user?.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = reminder.booking?.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = reminder.workflowStep.sendTo; + } const name = reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE diff --git a/packages/features/ee/workflows/components/v2/AddActionDialog.tsx b/packages/features/ee/workflows/components/v2/AddActionDialog.tsx index 29cd2e435c..0a0b01be66 100644 --- a/packages/features/ee/workflows/components/v2/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/v2/AddActionDialog.tsx @@ -7,7 +7,17 @@ import { z } from "zod"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import PhoneInput from "@calcom/ui/form/PhoneInputLazy"; -import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Label, Select } from "@calcom/ui/v2"; +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + EmailField, + Form, + Label, + Select, +} from "@calcom/ui/v2"; import { WORKFLOW_ACTIONS } from "../../lib/constants"; import { getWorkflowActionOptions } from "../../lib/getOptions"; @@ -27,13 +37,14 @@ export const AddActionDialog = (props: IAddActionDialog) => { const { t } = useLocale(); const { isOpenDialog, setIsOpenDialog, addAction } = props; const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(false); + const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState(false); const actionOptions = getWorkflowActionOptions(t); const formSchema = z.object({ action: z.enum(WORKFLOW_ACTIONS), sendTo: z .string() - .refine((val) => isValidPhoneNumber(val)) + .refine((val) => isValidPhoneNumber(val) || val.includes("@")) .optional(), }); @@ -58,6 +69,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { form.unregister("action"); setIsOpenDialog(false); setIsPhoneNumberNeeded(false); + setIsEmailAddressNeeded(false); }}>
@@ -75,11 +87,17 @@ export const AddActionDialog = (props: IAddActionDialog) => { form.setValue("action", val.value); if (val.value === WorkflowActions.SMS_NUMBER) { setIsPhoneNumberNeeded(true); - } else { + setIsEmailAddressNeeded(false); + } else if (val.value === WorkflowActions.EMAIL_ADDRESS) { + setIsEmailAddressNeeded(true); + setIsPhoneNumberNeeded(false); + } else { + setIsEmailAddressNeeded(false); setIsPhoneNumberNeeded(false); - form.unregister("sendTo"); } + form.unregister("sendTo"); form.clearErrors("action"); + form.clearErrors("sendTo"); } }} options={actionOptions} @@ -109,6 +127,11 @@ export const AddActionDialog = (props: IAddActionDialog) => {
)} + {isEmailAddressNeeded && ( +
+ +
+ )} diff --git a/packages/features/ee/workflows/components/v2/EventWorkflowsTab.tsx b/packages/features/ee/workflows/components/v2/EventWorkflowsTab.tsx index 4a90910740..aecdb25c07 100644 --- a/packages/features/ee/workflows/components/v2/EventWorkflowsTab.tsx +++ b/packages/features/ee/workflows/components/v2/EventWorkflowsTab.tsx @@ -84,6 +84,9 @@ const WorkflowListItem = (props: ItemProps) => { case WorkflowActions.SMS_NUMBER: sendTo.add(step.sendTo || ""); break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo.add(step.sendTo || ""); + break; } }); diff --git a/packages/features/ee/workflows/components/v2/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/v2/WorkflowStepContainer.tsx index c1931d85e5..4211c9cf44 100644 --- a/packages/features/ee/workflows/components/v2/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/v2/WorkflowStepContainer.tsx @@ -20,7 +20,7 @@ import PhoneInput from "@calcom/ui/form/PhoneInputLazy"; import { Button, DialogClose, DialogContent } from "@calcom/ui/v2"; import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent"; import Select from "@calcom/ui/v2/core/form/Select"; -import { Label, TextArea } from "@calcom/ui/v2/core/form/fields"; +import { EmailField, Label, TextArea } from "@calcom/ui/v2/core/form/fields"; import { AddVariablesDropdown } from "../../components/v2/AddVariablesDropdown"; import { @@ -49,12 +49,18 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { step?.action === WorkflowActions.SMS_NUMBER ? true : false ); + const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState( + step?.action === WorkflowActions.EMAIL_ADDRESS ? true : false + ); + const [isCustomReminderBodyNeeded, setIsCustomReminderBodyNeeded] = useState( step?.template === WorkflowTemplates.CUSTOM ? true : false ); const [isEmailSubjectNeeded, setIsEmailSubjectNeeded] = useState( - step?.action === WorkflowActions.EMAIL_ATTENDEE || step?.action === WorkflowActions.EMAIL_HOST + step?.action === WorkflowActions.EMAIL_ATTENDEE || + step?.action === WorkflowActions.EMAIL_HOST || + step?.action === WorkflowActions.EMAIL_ADDRESS ? true : false ); @@ -270,12 +276,20 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { if (val) { if (val.value === WorkflowActions.SMS_NUMBER) { setIsPhoneNumberNeeded(true); + setIsEmailAddressNeeded(false); + } else if (val.value === WorkflowActions.EMAIL_ADDRESS) { + setIsEmailAddressNeeded(true); + setIsPhoneNumberNeeded(false); } else { + setIsEmailAddressNeeded(false); setIsPhoneNumberNeeded(false); } + form.unregister(`steps.${step.stepNumber - 1}.sendTo`); + form.clearErrors(`steps.${step.stepNumber - 1}.sendTo`); if ( val.value === WorkflowActions.EMAIL_ATTENDEE || - val.value === WorkflowActions.EMAIL_HOST + val.value === WorkflowActions.EMAIL_HOST || + val.value === WorkflowActions.EMAIL_ADDRESS ) { setIsEmailSubjectNeeded(true); } else { @@ -316,6 +330,15 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { )} )} + {isEmailAddressNeeded && ( +
+ +
+ )}
diff --git a/packages/features/ee/workflows/lib/constants.ts b/packages/features/ee/workflows/lib/constants.ts index 2e99eed88d..07e43a397b 100644 --- a/packages/features/ee/workflows/lib/constants.ts +++ b/packages/features/ee/workflows/lib/constants.ts @@ -11,6 +11,7 @@ export const WORKFLOW_TRIGGER_EVENTS = [ export const WORKFLOW_ACTIONS = [ WorkflowActions.EMAIL_HOST, WorkflowActions.EMAIL_ATTENDEE, + WorkflowActions.EMAIL_ADDRESS, WorkflowActions.SMS_ATTENDEE, WorkflowActions.SMS_NUMBER, ] as const; diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts index 369c3b0e98..cb332d3e05 100644 --- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts @@ -61,9 +61,26 @@ export const scheduleEmailReminder = async ( method: "POST", }); - const name = action === WorkflowActions.EMAIL_HOST ? evt.organizer.name : evt.attendees[0].name; - const attendeeName = action === WorkflowActions.EMAIL_HOST ? evt.attendees[0].name : evt.organizer.name; - const timeZone = action === WorkflowActions.EMAIL_HOST ? evt.organizer.timeZone : evt.attendees[0].timeZone; + let name = ""; + let attendeeName = ""; + let timeZone = ""; + + switch (action) { + case WorkflowActions.EMAIL_HOST: + name = evt.organizer.name; + attendeeName = evt.attendees[0].name; + timeZone = evt.organizer.timeZone; + break; + case WorkflowActions.EMAIL_ATTENDEE: + name = evt.attendees[0].name; + attendeeName = evt.organizer.name; + timeZone = evt.attendees[0].timeZone; + break; + case WorkflowActions.EMAIL_ADDRESS: + name = ""; + attendeeName = evt.attendees[0].name; + timeZone = evt.organizer.timeZone; + } let emailContent = { emailSubject, diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index a7aec1c3bd..48b2736574 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -51,10 +51,21 @@ export const scheduleWorkflowReminders = async ( ); } else if ( step.action === WorkflowActions.EMAIL_ATTENDEE || - step.action === WorkflowActions.EMAIL_HOST + step.action === WorkflowActions.EMAIL_HOST || + step.action === WorkflowActions.EMAIL_ADDRESS ) { - const sendTo = - step.action === WorkflowActions.EMAIL_HOST ? evt.organizer.email : evt.attendees[0].email; + let sendTo = ""; + + switch (step.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = evt.organizer.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = evt.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = step.sendTo || ""; + } scheduleEmailReminder( evt, workflow.trigger, @@ -108,10 +119,21 @@ export const sendCancelledReminders = async ( ); } else if ( step.action === WorkflowActions.EMAIL_ATTENDEE || - step.action === WorkflowActions.EMAIL_HOST + step.action === WorkflowActions.EMAIL_HOST || + step.action === WorkflowActions.EMAIL_ADDRESS ) { - const sendTo = - step.action === WorkflowActions.EMAIL_HOST ? evt.organizer.email : evt.attendees[0].email; + let sendTo = ""; + + switch (step.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = evt.organizer.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = evt.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = step.sendTo || ""; + } scheduleEmailReminder( evt, workflow.trigger, diff --git a/packages/features/ee/workflows/lib/reminders/templates/emailReminderTemplate.ts b/packages/features/ee/workflows/lib/reminders/templates/emailReminderTemplate.ts index 06f2405a3e..97371a798f 100644 --- a/packages/features/ee/workflows/lib/reminders/templates/emailReminderTemplate.ts +++ b/packages/features/ee/workflows/lib/reminders/templates/emailReminderTemplate.ts @@ -18,7 +18,9 @@ const emailReminderTemplate = ( .tz(timeZone) .format("YYYY MMM D")} at ${dayjs(startTime).tz(timeZone).format("h:mmA")} ${timeZone}.`; - const introHtml = `Hi ${name},

This is a reminder about your upcoming event.

`; + const introHtml = `Hi${ + name ? " " + name : "" + },

This is a reminder about your upcoming event.

`; const eventHtml = `
Event:
${eventName}

`; diff --git a/packages/features/ee/workflows/pages/v2/workflow.tsx b/packages/features/ee/workflows/pages/v2/workflow.tsx index e8cdff48b9..b9e4ebd91a 100644 --- a/packages/features/ee/workflows/pages/v2/workflow.tsx +++ b/packages/features/ee/workflows/pages/v2/workflow.tsx @@ -55,7 +55,7 @@ const formSchema = z.object({ template: z.nativeEnum(WorkflowTemplates), sendTo: z .string() - .refine((val) => isValidPhoneNumber(val)) + .refine((val) => isValidPhoneNumber(val) || val.includes("@")) .nullable(), }) .array(), @@ -77,6 +77,7 @@ function WorkflowPage() { mode: "onBlur", resolver: zodResolver(formSchema), }); + const { workflow: workflowId } = router.isReady ? querySchema.parse(router.query) : { workflow: -1 }; const utils = trpc.useContext(); diff --git a/packages/prisma/migrations/20221007112203_add_email_address_workflow_action/migration.sql b/packages/prisma/migrations/20221007112203_add_email_address_workflow_action/migration.sql new file mode 100644 index 0000000000..afd4c057f4 --- /dev/null +++ b/packages/prisma/migrations/20221007112203_add_email_address_workflow_action/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "WorkflowActions" ADD VALUE 'EMAIL_ADDRESS'; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index dc60065494..1e98cd7c57 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -540,6 +540,7 @@ enum WorkflowActions { EMAIL_ATTENDEE SMS_ATTENDEE SMS_NUMBER + EMAIL_ADDRESS } model WorkflowStep { diff --git a/packages/trpc/server/routers/viewer/workflows.tsx b/packages/trpc/server/routers/viewer/workflows.tsx index 24a90e06a5..b088ac4450 100644 --- a/packages/trpc/server/routers/viewer/workflows.tsx +++ b/packages/trpc/server/routers/viewer/workflows.tsx @@ -416,12 +416,22 @@ export const workflowsRouter = createProtectedRouter() }; if ( step.action === WorkflowActions.EMAIL_HOST || - step.action === WorkflowActions.EMAIL_ATTENDEE + step.action === WorkflowActions.EMAIL_ATTENDEE || + step.action === WorkflowActions.EMAIL_ADDRESS ) { - const sendTo = - step.action === WorkflowActions.EMAIL_HOST - ? bookingInfo.organizer?.email - : bookingInfo.attendees[0].email; + let sendTo = ""; + + switch (step.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = bookingInfo.organizer?.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = bookingInfo.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = step.sendTo || ""; + } + await scheduleEmailReminder( bookingInfo, trigger, @@ -503,7 +513,11 @@ export const workflowsRouter = createProtectedRouter() }, data: { action: newStep.action, - sendTo: newStep.action === WorkflowActions.SMS_NUMBER ? newStep.sendTo : null, + sendTo: + newStep.action === WorkflowActions.SMS_NUMBER || + newStep.action === WorkflowActions.EMAIL_ADDRESS + ? newStep.sendTo + : null, stepNumber: newStep.stepNumber, workflowId: newStep.workflowId, reminderBody: newStep.template === WorkflowTemplates.CUSTOM ? newStep.reminderBody : null, @@ -577,12 +591,22 @@ export const workflowsRouter = createProtectedRouter() }; if ( newStep.action === WorkflowActions.EMAIL_HOST || - newStep.action === WorkflowActions.EMAIL_ATTENDEE + newStep.action === WorkflowActions.EMAIL_ATTENDEE || + newStep.action === WorkflowActions.EMAIL_ADDRESS ) { - const sendTo = - newStep.action === WorkflowActions.EMAIL_HOST - ? bookingInfo.organizer?.email - : bookingInfo.attendees[0].email; + let sendTo = ""; + + switch (newStep.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = bookingInfo.organizer?.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = bookingInfo.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = newStep.sendTo || ""; + } + await scheduleEmailReminder( bookingInfo, trigger, @@ -681,12 +705,22 @@ export const workflowsRouter = createProtectedRouter() if ( step.action === WorkflowActions.EMAIL_ATTENDEE || - step.action === WorkflowActions.EMAIL_HOST + step.action === WorkflowActions.EMAIL_HOST || + step.action === WorkflowActions.EMAIL_ADDRESS ) { - const sendTo = - step.action === WorkflowActions.EMAIL_HOST - ? bookingInfo.organizer?.email - : bookingInfo.attendees[0].email; + let sendTo = ""; + + switch (step.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = bookingInfo.organizer?.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = bookingInfo.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = step.sendTo || ""; + } + await scheduleEmailReminder( bookingInfo, trigger, @@ -821,7 +855,11 @@ export const workflowsRouter = createProtectedRouter() }; } - if (action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.EMAIL_HOST) { + if ( + action === WorkflowActions.EMAIL_ATTENDEE || + action === WorkflowActions.EMAIL_HOST || + action === WorkflowActions.EMAIL_ADDRESS + ) { scheduleEmailReminder( evt, WorkflowTriggerEvents.NEW_EVENT,