From d58924ecad308cebeef61e50c42c53621d1d0469 Mon Sep 17 00:00:00 2001 From: jemiluv8 <119384208+jemiluv8@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:48:44 +0000 Subject: [PATCH] feat: New workflow action to send Whatsapp message (#8818) Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Co-authored-by: CarinaWolli --- .env.example | 1 + .../cron-scheduleWhatsappReminders.yml | 23 ++ .../workflows/scheduleWhatsappReminders.ts | 1 + apps/web/public/static/locales/en/common.json | 8 +- apps/web/public/static/locales/fr/common.json | 1 - .../features/bookings/lib/getBookingFields.ts | 4 +- .../bookings/lib/handleCancelBooking.ts | 3 + .../features/bookings/lib/handleNewBooking.ts | 4 + .../api/scheduleWhatsappReminders.ts | 111 ++++++++++ .../workflows/components/AddActionDialog.tsx | 32 ++- .../components/EventWorkflowsTab.tsx | 6 +- .../components/WorkflowDetailsPage.tsx | 4 +- .../components/WorkflowStepContainer.tsx | 207 +++++++++++------- .../ee/workflows/lib/actionHelperFunctions.ts | 52 ++++- .../features/ee/workflows/lib/constants.ts | 22 +- .../ee/workflows/lib/getActionIcon.tsx | 13 +- .../features/ee/workflows/lib/getOptions.ts | 13 +- .../lib/reminders/reminderScheduler.ts | 39 ++++ .../reminders/smsProviders/twilioProvider.ts | 24 +- .../lib/reminders/templates/whatsapp/index.ts | 4 + .../whatsappEventCancelledTemplate.ts | 35 +++ .../whatsappEventCompletedTemplate.ts | 35 +++ .../whatsapp/whatsappEventReminderTemplate.ts | 36 +++ .../whatsappEventRescheduledTemplate.ts | 36 +++ .../lib/reminders/whatsappReminderManager.ts | 141 ++++++++++++ .../features/ee/workflows/pages/workflow.tsx | 6 +- .../migration.sql | 16 ++ packages/prisma/schema.prisma | 6 + .../bookings/requestReschedule.handler.ts | 3 + .../workflows/activateEventType.handler.ts | 33 ++- .../viewer/workflows/update.handler.ts | 85 ++++++- .../server/routers/viewer/workflows/util.ts | 4 +- packages/ui/form/PhoneInput.tsx | 4 + 33 files changed, 863 insertions(+), 149 deletions(-) create mode 100644 .github/workflows/cron-scheduleWhatsappReminders.yml create mode 100644 apps/web/pages/api/cron/workflows/scheduleWhatsappReminders.ts create mode 100644 packages/features/ee/workflows/api/scheduleWhatsappReminders.ts create mode 100644 packages/features/ee/workflows/lib/reminders/templates/whatsapp/index.ts create mode 100644 packages/features/ee/workflows/lib/reminders/templates/whatsapp/whatsappEventCancelledTemplate.ts create mode 100644 packages/features/ee/workflows/lib/reminders/templates/whatsapp/whatsappEventCompletedTemplate.ts create mode 100644 packages/features/ee/workflows/lib/reminders/templates/whatsapp/whatsappEventReminderTemplate.ts create mode 100644 packages/features/ee/workflows/lib/reminders/templates/whatsapp/whatsappEventRescheduledTemplate.ts create mode 100644 packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts create mode 100644 packages/prisma/migrations/20230420222915_add_whatsapp_workflow_migrations/migration.sql diff --git a/.env.example b/.env.example index 15b2b95efd..20bef8f8aa 100644 --- a/.env.example +++ b/.env.example @@ -121,6 +121,7 @@ TWILIO_SID= TWILIO_TOKEN= TWILIO_MESSAGING_SID= TWILIO_PHONE_NUMBER= +TWILIO_WHATSAPP_PHONE_NUMBER= # For NEXT_PUBLIC_SENDER_ID only letters, numbers and spaces are allowed (max. 11 characters) NEXT_PUBLIC_SENDER_ID= TWILIO_VERIFY_SID= diff --git a/.github/workflows/cron-scheduleWhatsappReminders.yml b/.github/workflows/cron-scheduleWhatsappReminders.yml new file mode 100644 index 0000000000..ca12b21812 --- /dev/null +++ b/.github/workflows/cron-scheduleWhatsappReminders.yml @@ -0,0 +1,23 @@ +name: Cron - scheduleWhatsappReminders + +on: + # "Scheduled workflows run on the latest commit on the default or base branch." + # — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule + schedule: + # Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru) + - cron: "0,15,30,45 * * * *" +jobs: + cron-scheduleWhatsappReminders: + env: + APP_URL: ${{ secrets.APP_URL }} + CRON_API_KEY: ${{ secrets.CRON_API_KEY }} + runs-on: ubuntu-latest + steps: + - name: cURL request + if: ${{ env.APP_URL && env.CRON_API_KEY }} + run: | + curl ${{ secrets.APP_URL }}/api/cron/workflows/scheduleWhatsappReminders \ + -X POST \ + -H 'content-type: application/json' \ + -H 'authorization: ${{ secrets.CRON_API_KEY }}' \ + --fail diff --git a/apps/web/pages/api/cron/workflows/scheduleWhatsappReminders.ts b/apps/web/pages/api/cron/workflows/scheduleWhatsappReminders.ts new file mode 100644 index 0000000000..2cde406ee8 --- /dev/null +++ b/apps/web/pages/api/cron/workflows/scheduleWhatsappReminders.ts @@ -0,0 +1 @@ +export { default } from "@calcom/features/ee/workflows/api/scheduleWhatsappReminders"; diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index d5825854a0..4d35da9dd0 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1086,6 +1086,8 @@ "email_attendee_action": "send email to attendees", "sms_attendee_action": "Send SMS to attendee", "sms_number_action": "send SMS to a specific number", + "whatsapp_number_action": "send Whatsapp to a specific number", + "whatsapp_attendee_action": "send Whatsapp to attendee", "workflows": "Workflows", "new_workflow_btn": "New Workflow", "add_new_workflow": "Add a new workflow", @@ -1147,8 +1149,10 @@ "specific_issue": "Have a specific issue?", "browse_our_docs": "browse our docs", "choose_template": "Choose a template", - "reminder": "Reminder", "custom": "Custom", + "reminder": "Reminder", + "rescheduled": "Rescheduled", + "completed": "Completed", "reminder_email": "Reminder: {{eventType}} with {{name}} at {{date}}", "not_triggering_existing_bookings": "Won't trigger for already existing bookings as user will be asked for phone number when booking the event.", "minute_one": "{{count}} minute", @@ -1491,7 +1495,7 @@ "team_url_required": "Must enter a team URL", "url_taken": "This URL is already taken", "team_publish": "Publish team", - "number_sms_notifications": "Phone number (SMS notifications)", + "number_text_notifications": "Phone number (Text notifications)", "attendee_email_variable": "Attendee email", "attendee_email_info": "The person booking's email", "kbar_search_placeholder": "Type a command or search...", diff --git a/apps/web/public/static/locales/fr/common.json b/apps/web/public/static/locales/fr/common.json index e9e7771203..f88617a1e1 100644 --- a/apps/web/public/static/locales/fr/common.json +++ b/apps/web/public/static/locales/fr/common.json @@ -1490,7 +1490,6 @@ "team_url_required": "Vous devez saisir un lien d'équipe", "url_taken": "Ce lien est déjà pris", "team_publish": "Publier l'équipe", - "number_sms_notifications": "Numéro de téléphone (notifications par SMS)", "attendee_email_variable": "Adresse e-mail du participant", "attendee_email_info": "Adresse e-mail du participant", "kbar_search_placeholder": "Saisissez une commande ou une recherche...", diff --git a/packages/features/bookings/lib/getBookingFields.ts b/packages/features/bookings/lib/getBookingFields.ts index 3b156881eb..450f4ecccd 100644 --- a/packages/features/bookings/lib/getBookingFields.ts +++ b/packages/features/bookings/lib/getBookingFields.ts @@ -23,7 +23,7 @@ export const getSmsReminderNumberField = () => ({ name: SMS_REMINDER_NUMBER_FIELD, type: "phone", - defaultLabel: "number_sms_notifications", + defaultLabel: "number_text_notifications", defaultPlaceholder: "enter_phone_number", editable: "system", } as const); @@ -136,7 +136,7 @@ export const ensureBookingInputsHaveSystemFields = ({ const smsNumberSources = [] as NonNullable<(typeof bookingFields)[number]["sources"]>; workflows.forEach((workflow) => { workflow.workflow.steps.forEach((step) => { - if (step.action === "SMS_ATTENDEE") { + if (step.action === "SMS_ATTENDEE" || step.action === "WHATSAPP_ATTENDEE") { const workflowId = workflow.workflow.id; smsNumberSources.push( getSmsReminderNumberSource({ diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index ff1fea4488..8686379f92 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -13,6 +13,7 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR import { deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; +import { deleteScheduledWhatsappReminder } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; @@ -655,6 +656,8 @@ async function handler(req: CustomRequest) { deleteScheduledEmailReminder(reminder.id, reminder.referenceId); } else if (reminder.method === WorkflowMethods.SMS) { deleteScheduledSMSReminder(reminder.id, reminder.referenceId); + } else if (reminder.method === WorkflowMethods.WHATSAPP) { + deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId); } }); }); diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 52799fe7d2..2f8d32c601 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -41,6 +41,8 @@ import { 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"; +import { deleteScheduledWhatsappReminder } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; + import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; @@ -1976,6 +1978,8 @@ async function handler( deleteScheduledEmailReminder(reminder.id, reminder.referenceId); } else if (reminder.method === WorkflowMethods.SMS) { deleteScheduledSMSReminder(reminder.id, reminder.referenceId); + } else if (reminder.method === WorkflowMethods.WHATSAPP) { + deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId); } }); } catch (error) { diff --git a/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts b/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts new file mode 100644 index 0000000000..fd6570a09b --- /dev/null +++ b/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts @@ -0,0 +1,111 @@ +/* Schedule any workflow reminder that falls within 7 days for WHATSAPP */ +import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@prisma/client"; +import type { NextApiRequest, NextApiResponse } from "next"; + +import dayjs from "@calcom/dayjs"; +import { defaultHandler } from "@calcom/lib/server"; +import prisma from "@calcom/prisma"; + +import * as twilio from "../lib/reminders/smsProviders/twilioProvider"; +import { getWhatsappTemplateFunction } from "../lib/actionHelperFunctions"; + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const apiKey = req.headers.authorization || req.query.apiKey; + if (process.env.CRON_API_KEY !== apiKey) { + res.status(401).json({ message: "Not authenticated" }); + return; + } + + //delete all scheduled whatsapp reminders where scheduled date is past current date + await prisma.workflowReminder.deleteMany({ + where: { + method: WorkflowMethods.WHATSAPP, + scheduledDate: { + lte: dayjs().toISOString(), + }, + }, + }); + + //find all unscheduled WHATSAPP reminders + const unscheduledReminders = await prisma.workflowReminder.findMany({ + where: { + method: WorkflowMethods.WHATSAPP, + scheduled: false, + scheduledDate: { + lte: dayjs().add(7, "day").toISOString(), + }, + }, + include: { + workflowStep: true, + booking: { + include: { + eventType: true, + user: true, + attendees: true, + }, + }, + }, + }); + + if (!unscheduledReminders.length) res.json({ ok: true }); + + for (const reminder of unscheduledReminders) { + if (!reminder.workflowStep || !reminder.booking) { + continue; + } + try { + const sendTo = + reminder.workflowStep.action === WorkflowActions.WHATSAPP_NUMBER + ? reminder.workflowStep.sendTo + : reminder.booking?.smsReminderNumber; + + const userName = + reminder.workflowStep.action === WorkflowActions.WHATSAPP_ATTENDEE + ? reminder.booking?.attendees[0].name + : ""; + + const attendeeName = + reminder.workflowStep.action === WorkflowActions.WHATSAPP_ATTENDEE + ? reminder.booking?.user?.name + : reminder.booking?.attendees[0].name; + + const timeZone = + reminder.workflowStep.action === WorkflowActions.WHATSAPP_ATTENDEE + ? reminder.booking?.attendees[0].timeZone + : reminder.booking?.user?.timeZone; + + const templateFunction = getWhatsappTemplateFunction(reminder.workflowStep.template) + const message = templateFunction( + false, + reminder.workflowStep.action, + reminder.booking?.startTime.toISOString() || "", + reminder.booking?.eventType?.title || "", + timeZone || "", + attendeeName || "", + userName + ); + + if (message?.length && message?.length > 0 && sendTo) { + const scheduledSMS = await twilio.scheduleSMS(sendTo, message, reminder.scheduledDate, "", true); + + await prisma.workflowReminder.update({ + where: { + id: reminder.id, + }, + data: { + scheduled: true, + referenceId: scheduledSMS.sid, + }, + }); + } + } catch (error) { + console.log(`Error scheduling WHATSAPP with error ${error}`); + } + } + + res.status(200).json({ message: "WHATSAPP scheduled" }); +} + +export default defaultHandler({ + POST: Promise.resolve({ default: handler }), +}); diff --git a/packages/features/ee/workflows/components/AddActionDialog.tsx b/packages/features/ee/workflows/components/AddActionDialog.tsx index 0071e854ab..ff5100406e 100644 --- a/packages/features/ee/workflows/components/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/AddActionDialog.tsx @@ -5,8 +5,7 @@ import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; -import { SENDER_ID } from "@calcom/lib/constants"; -import { SENDER_NAME } from "@calcom/lib/constants"; +import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { WorkflowActions } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; @@ -94,6 +93,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { setIsPhoneNumberNeeded(true); setIsSenderIdNeeded(true); setIsEmailAddressNeeded(false); + form.resetField("senderId", { defaultValue: SENDER_ID }) } else if (newValue.value === WorkflowActions.EMAIL_ADDRESS) { setIsEmailAddressNeeded(true); setIsSenderIdNeeded(false); @@ -102,6 +102,11 @@ export const AddActionDialog = (props: IAddActionDialog) => { setIsSenderIdNeeded(true); setIsEmailAddressNeeded(false); setIsPhoneNumberNeeded(false); + form.resetField("senderId", { defaultValue: SENDER_ID }) + } else if (newValue.value === WorkflowActions.WHATSAPP_NUMBER) { + setIsSenderIdNeeded(false); + setIsPhoneNumberNeeded(true); + setIsEmailAddressNeeded(false); } else { setIsSenderIdNeeded(false); setIsEmailAddressNeeded(false); @@ -116,6 +121,20 @@ export const AddActionDialog = (props: IAddActionDialog) => { if (!actionOptions) return null; + const canRequirePhoneNumber = (workflowStep: string) => { + return ( + WorkflowActions.SMS_ATTENDEE === workflowStep || + WorkflowActions.WHATSAPP_ATTENDEE === workflowStep + ) + } + + const showSender = (action: string) => { + return !isSenderIdNeeded && !( + WorkflowActions.WHATSAPP_NUMBER === action || + WorkflowActions.WHATSAPP_ATTENDEE === action + ) + } + return ( @@ -167,7 +186,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { {isPhoneNumberNeeded && (
-
+
{
)} - {isSenderIdNeeded ? ( + {isSenderIdNeeded && ( <>
@@ -208,13 +227,14 @@ export const AddActionDialog = (props: IAddActionDialog) => {

{t("sender_id_error_message")}

)} - ) : ( + )} + {showSender(form.getValues('action')) && (
)} - {form.getValues("action") === WorkflowActions.SMS_ATTENDEE && ( + {canRequirePhoneNumber(form.getValues("action")) && (
{ sendTo.add(t("organizer")); break; case WorkflowActions.EMAIL_ATTENDEE: - sendTo.add(t("attendee_name_variable")); - break; case WorkflowActions.SMS_ATTENDEE: + case WorkflowActions.WHATSAPP_ATTENDEE: sendTo.add(t("attendee_name_variable")); break; case WorkflowActions.SMS_NUMBER: - sendTo.add(step.sendTo || ""); - break; + case WorkflowActions.WHATSAPP_NUMBER: case WorkflowActions.EMAIL_ADDRESS: sendTo.add(step.sendTo || ""); break; diff --git a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx index 85da5b44ce..f986732b77 100644 --- a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx @@ -13,7 +13,7 @@ import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui"; import { Button, Label, MultiSelectCheckboxes, TextField } from "@calcom/ui"; import { ArrowDown, Trash2 } from "@calcom/ui/components/icon"; -import { isSMSAction } from "../lib/actionHelperFunctions"; +import { isSMSAction, isWhatsappAction } from "../lib/actionHelperFunctions"; import type { FormValues } from "../pages/workflow"; import { AddActionDialog } from "./AddActionDialog"; import { DeleteDialog } from "./DeleteDialog"; @@ -98,7 +98,7 @@ export default function WorkflowDetailsPage(props: Props) { workflowId: workflowId, reminderBody: null, emailSubject: null, - template: WorkflowTemplates.CUSTOM, + template: isWhatsappAction(action) ? WorkflowTemplates.REMINDER : WorkflowTemplates.CUSTOM, numberRequired: numberRequired || false, sender: isSMSAction(action) ? sender || SENDER_ID : SENDER_ID, senderName: !isSMSAction(action) ? senderName || SENDER_NAME : SENDER_NAME, diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index d6e265c49d..fe88c365f5 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -6,8 +6,7 @@ import { Controller } from "react-hook-form"; import "react-phone-number-input/style.css"; import { classNames } from "@calcom/lib"; -import { SENDER_ID } from "@calcom/lib/constants"; -import { SENDER_NAME } from "@calcom/lib/constants"; +import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { WorkflowTemplates, TimeUnit, WorkflowActions } from "@calcom/prisma/enums"; @@ -40,11 +39,12 @@ import { } from "@calcom/ui"; import { ArrowDown, MoreHorizontal, Trash2, HelpCircle, Info } from "@calcom/ui/components/icon"; -import { isAttendeeAction, isSMSAction } from "../lib/actionHelperFunctions"; import { DYNAMIC_TEXT_VARIABLES } from "../lib/constants"; +import { isAttendeeAction, isSMSAction, isSMSOrWhatsappAction, isWhatsappAction, getWhatsappTemplateForAction } from "../lib/actionHelperFunctions"; import { getWorkflowTemplateOptions, getWorkflowTriggerOptions } from "../lib/getOptions"; import emailReminderTemplate from "../lib/reminders/templates/emailReminderTemplate"; import smsReminderTemplate from "../lib/reminders/templates/smsReminderTemplate"; +import { whatsappReminderTemplate } from "../lib/reminders/templates/whatsapp"; import type { FormValues } from "../pages/workflow"; import { TimeTimeUnitInput } from "./TimeTimeUnitInput"; @@ -71,24 +71,16 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const [verificationCode, setVerificationCode] = useState(""); - const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState( - step?.action === WorkflowActions.SMS_NUMBER ? true : false - ); + const action = step?.action + const requirePhoneNumber = WorkflowActions.SMS_NUMBER === action || WorkflowActions.WHATSAPP_NUMBER === action; + const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(requirePhoneNumber); const [updateTemplate, setUpdateTemplate] = useState(false); const [firstRender, setFirstRender] = useState(true); - const [isSenderIdNeeded, setIsSenderIdNeeded] = useState( - step?.action === WorkflowActions.SMS_NUMBER || step?.action === WorkflowActions.SMS_ATTENDEE - ? true - : false - ); - useEffect(() => { - setNumberVerified( - !!step && - !!verifiedNumbers.find((number) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`)) - ); - }, [verifiedNumbers.length]); + const senderNeeded = step?.action === WorkflowActions.SMS_NUMBER || step?.action === WorkflowActions.SMS_ATTENDEE; + + const [isSenderIsNeeded, setIsSenderIsNeeded] = useState(senderNeeded); const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState( step?.action === WorkflowActions.EMAIL_ADDRESS ? true : false @@ -112,17 +104,25 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ); const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); const triggerOptions = getWorkflowTriggerOptions(t); - const templateOptions = getWorkflowTemplateOptions(t); + const templateOptions = getWorkflowTemplateOptions(t, step?.action); if (step && form.getValues(`steps.${step.stepNumber - 1}.template`) === WorkflowTemplates.REMINDER) { if (!form.getValues(`steps.${step.stepNumber - 1}.reminderBody`)) { - if (isSMSAction(form.getValues(`steps.${step.stepNumber - 1}.action`))) { - const smsBody = smsReminderTemplate(true, form.getValues(`steps.${step.stepNumber - 1}.action`)); - form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, smsBody); + const action = form.getValues(`steps.${step.stepNumber - 1}.action`); + if (isSMSAction(action)) { + form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, smsReminderTemplate( + true, + action + )); + } else if (isWhatsappAction(action)) { + form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, whatsappReminderTemplate( + true, + action + )) } else { const reminderBodyTemplate = emailReminderTemplate( true, - form.getValues(`steps.${step.stepNumber - 1}.action`) + action ).emailBody; form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, reminderBodyTemplate); } @@ -134,6 +134,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ).emailSubject; form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, subjectTemplate); } + } else if (step && isWhatsappAction(step.action)) { + const templateBody = getWhatsappTemplateForAction(step.action, step.template) + form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, templateBody) } const { ref: emailSubjectFormRef, ...restEmailSubjectForm } = step @@ -148,10 +151,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const refReminderBody = useRef(null); - const [numberVerified, setNumberVerified] = useState( - step && - !!verifiedNumbers.find((number) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`)) - ); + const getNumberVerificationStatus = () => !!step && !!verifiedNumbers.find((number: string) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`)) + + const [numberVerified, setNumberVerified] = useState(getNumberVerificationStatus()); + + useEffect(() => setNumberVerified(getNumberVerificationStatus()), [verifiedNumbers.length]); const addVariableBody = (variable: string) => { if (step) { @@ -232,17 +236,17 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { return ( <>
-
+
1
-
{t("trigger")}
-
{t("when_something_happens")}
+
{t("trigger")}
+
{t("when_something_happens")}
-
+
{showTimeSectionAfter ? t("how_long_after") : t("how_long_before")} {!props.readOnly && ( -
+

{t("testing_workflow_info_message")}

@@ -301,6 +305,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { } if (step && step.action) { + const templateValue = form.watch(`steps.${step.stepNumber - 1}.template`); const actionString = t(`${step.action.toLowerCase()}_action`); const selectedAction = { @@ -311,13 +316,20 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const selectedTemplate = { label: t(`${step.template.toLowerCase()}`), value: step.template }; + const canRequirePhoneNumber = (workflowStep: string) => { + return ( + WorkflowActions.SMS_ATTENDEE === workflowStep || + WorkflowActions.WHATSAPP_ATTENDEE === workflowStep + ) + } + return ( <> -
+
-
+
@@ -326,8 +338,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { {step.stepNumber + 1}
-
{t("action")}
-
{t("action_is_performed")}
+
{t("action")}
+
{t("action_is_performed")}
@@ -367,7 +379,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)}
-
+
{ + setIsSenderIsNeeded(senderNeeded); setIsEmailAddressNeeded(false); - setIsPhoneNumberNeeded(val.value === WorkflowActions.SMS_NUMBER); - setNumberVerified(false); + setIsPhoneNumberNeeded(phoneNumberIsNeeded); + setNumberVerified(getNumberVerificationStatus()); + } + if (isSMSAction(val.value)) { + + setNumberRequiredConfigs(val.value === WorkflowActions.SMS_NUMBER) // email action changes to sms action if (!isSMSAction(oldValue)) { form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, ""); form.setValue(`steps.${step.stepNumber - 1}.sender`, SENDER_ID); } + setIsEmailSubjectNeeded(false); + } else if (isWhatsappAction(val.value)) { + setNumberRequiredConfigs(val.value === WorkflowActions.WHATSAPP_NUMBER, false); + + if (!isWhatsappAction(oldValue)) { + form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, ""); + form.setValue(`steps.${step.stepNumber - 1}.sender`, ""); + } + setIsEmailSubjectNeeded(false); } else { setIsPhoneNumberNeeded(false); - setIsSenderIdNeeded(false); + setIsSenderIsNeeded(false); setIsEmailAddressNeeded(val.value === WorkflowActions.EMAIL_ADDRESS); setIsEmailSubjectNeeded(true); } @@ -407,7 +432,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { form.getValues(`steps.${step.stepNumber - 1}.template`) === WorkflowTemplates.REMINDER ) { - if (isSMSAction(val.value) === isSMSAction(oldValue)) { + if (isSMSOrWhatsappAction(val.value) === isSMSOrWhatsappAction(oldValue)) { if (isAttendeeAction(oldValue) !== isAttendeeAction(val.value)) { const currentReminderBody = form.getValues(`steps.${step.stepNumber - 1}.reminderBody`) || ""; @@ -417,7 +442,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { .replaceAll("{PLACEHOLDER}", "{ATTENDEE}"); form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, newReminderBody); - if (!isSMSAction(val.value)) { + if (!isSMSOrWhatsappAction(val.value)) { const currentEmailSubject = form.getValues(`steps.${step.stepNumber - 1}.emailSubject`) || ""; const newEmailSubject = isAttendeeAction(val.value) @@ -436,6 +461,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { `steps.${step.stepNumber - 1}.reminderBody`, smsReminderTemplate(true, val.value) ); + } else if (isWhatsappAction(val.value)) { + form.setValue( + `steps.${step.stepNumber - 1}.reminderBody`, + whatsappReminderTemplate(true, val.value) + ); } else { const emailReminderBody = emailReminderTemplate(true, val.value); form.setValue( @@ -448,6 +478,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ); } } + } else { + const template = isWhatsappAction(val.value) ? "REMINDER" : "CUSTOM"; + template && form.setValue(`steps.${step.stepNumber - 1}.template`, template); } form.unregister(`steps.${step.stepNumber - 1}.sendTo`); form.clearErrors(`steps.${step.stepNumber - 1}.sendTo`); @@ -468,7 +501,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { />
{isPhoneNumberNeeded && ( -
+
-
+
)} -
- {isSenderIdNeeded ? ( + {!isWhatsappAction(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (
+ {isSenderIsNeeded ? ( <>
@@ -592,8 +625,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)} -
- {form.getValues(`steps.${step.stepNumber - 1}.action`) === WorkflowActions.SMS_ATTENDEE && ( +
)} + {canRequirePhoneNumber(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (
)} {isEmailAddressNeeded && ( -
+
{ + render={({ field }) => { return (