import type { WorkflowStep } from "@prisma/client"; import type { Dispatch, SetStateAction } from "react"; import { useRef, useState, useEffect } from "react"; import type { UseFormReturn } from "react-hook-form"; import { Controller } from "react-hook-form"; import "react-phone-number-input/style.css"; import { classNames } from "@calcom/lib"; import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { WorkflowTemplates, TimeUnit, WorkflowActions } from "@calcom/prisma/enums"; import { WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import type { RouterOutputs } from "@calcom/trpc/react"; import { Badge, Button, CheckboxField, Dialog, DialogClose, DialogContent, DialogFooter, Dropdown, DropdownMenuContent, DropdownMenuItem, DropdownItem, DropdownMenuTrigger, EmailField, Label, PhoneInput, Select, showToast, TextArea, TextField, Editor, AddVariablesDropdown, Input, Tooltip, } from "@calcom/ui"; import { ArrowDown, MoreHorizontal, Trash2, HelpCircle, Info } from "@calcom/ui/components/icon"; import { isAttendeeAction, isSMSAction, isSMSOrWhatsappAction, isWhatsappAction, getWhatsappTemplateForAction, isTextMessageToAttendeeAction, } from "../lib/actionHelperFunctions"; import { DYNAMIC_TEXT_VARIABLES } from "../lib/constants"; 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"; type User = RouterOutputs["viewer"]["me"]; type WorkflowStepProps = { step?: WorkflowStep; form: UseFormReturn; user: User; reload?: boolean; setReload?: Dispatch>; teamId?: number; readOnly: boolean; setKYCVerificationDialogOpen: Dispatch>; }; export default function WorkflowStepContainer(props: WorkflowStepProps) { const { t } = useLocale(); const utils = trpc.useContext(); const { step, form, reload, setReload, teamId } = props; const { data: _verifiedNumbers } = trpc.viewer.workflows.getVerifiedNumbers.useQuery( { teamId }, { enabled: !!teamId } ); const timeFormat = getTimeFormatStringFromUserTimeFormat(props.user.timeFormat); const verifiedNumbers = _verifiedNumbers?.map((number) => number.phoneNumber) || []; const [isAdditionalInputsDialogOpen, setIsAdditionalInputsDialogOpen] = useState(false); const [verificationCode, setVerificationCode] = useState(""); 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 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 ); const [isEmailSubjectNeeded, setIsEmailSubjectNeeded] = useState( step?.action === WorkflowActions.EMAIL_ATTENDEE || step?.action === WorkflowActions.EMAIL_HOST || step?.action === WorkflowActions.EMAIL_ADDRESS ? true : false ); const [showTimeSection, setShowTimeSection] = useState( form.getValues("trigger") === WorkflowTriggerEvents.BEFORE_EVENT || form.getValues("trigger") === WorkflowTriggerEvents.AFTER_EVENT ); const [showTimeSectionAfter, setShowTimeSectionAfter] = useState( form.getValues("trigger") === WorkflowTriggerEvents.AFTER_EVENT ); const [isRequiresConfirmationNeeded, setIsRequiresConfirmationNeeded] = useState( isTextMessageToAttendeeAction(step?.action) ); const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); const triggerOptions = getWorkflowTriggerOptions(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`)) { const action = form.getValues(`steps.${step.stepNumber - 1}.action`); if (isSMSAction(action)) { form.setValue( `steps.${step.stepNumber - 1}.reminderBody`, smsReminderTemplate(true, action, timeFormat) ); } else if (isWhatsappAction(action)) { form.setValue( `steps.${step.stepNumber - 1}.reminderBody`, whatsappReminderTemplate(true, action, timeFormat) ); } else { const reminderBodyTemplate = emailReminderTemplate(true, action, timeFormat).emailBody; form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, reminderBodyTemplate); } } if (!form.getValues(`steps.${step.stepNumber - 1}.emailSubject`)) { const subjectTemplate = emailReminderTemplate( true, form.getValues(`steps.${step.stepNumber - 1}.action`), timeFormat ).emailSubject; form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, subjectTemplate); } } else if (step && isWhatsappAction(step.action)) { const templateBody = getWhatsappTemplateForAction(step.action, step.template, timeFormat); form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, templateBody); } const { ref: emailSubjectFormRef, ...restEmailSubjectForm } = step ? form.register(`steps.${step.stepNumber - 1}.emailSubject`) : { ref: null, name: "" }; const { ref: reminderBodyFormRef, ...restReminderBodyForm } = step ? form.register(`steps.${step.stepNumber - 1}.reminderBody`) : { ref: null, name: "" }; const refEmailSubject = useRef(null); const refReminderBody = useRef(null); 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) { const currentMessageBody = refReminderBody?.current?.value || ""; const cursorPosition = refReminderBody?.current?.selectionStart || currentMessageBody.length; const messageWithAddedVariable = `${currentMessageBody.substring(0, cursorPosition)}{${variable .toUpperCase() .replace(/ /g, "_")}}${currentMessageBody.substring(cursorPosition)}`; form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, messageWithAddedVariable); } }; const addVariableEmailSubject = (variable: string) => { if (step) { const currentEmailSubject = refEmailSubject?.current?.value || ""; const cursorPosition = refEmailSubject?.current?.selectionStart || currentEmailSubject.length; const subjectWithAddedVariable = `${currentEmailSubject.substring(0, cursorPosition)}{${variable .toUpperCase() .replace(/ /g, "_")}}${currentEmailSubject.substring(cursorPosition)}`; form.setValue(`steps.${step.stepNumber - 1}.emailSubject`, subjectWithAddedVariable); } }; const sendVerificationCodeMutation = trpc.viewer.workflows.sendVerificationCode.useMutation({ onSuccess: async () => { showToast(t("verification_code_sent"), "success"); }, onError: async (error) => { showToast(error.message, "error"); }, }); const verifyPhoneNumberMutation = trpc.viewer.workflows.verifyPhoneNumber.useMutation({ onSuccess: async (isVerified) => { showToast(isVerified ? t("verified_successfully") : t("wrong_code"), "success"); setNumberVerified(isVerified); utils.viewer.workflows.getVerifiedNumbers.invalidate(); }, onError: (err) => { if (err instanceof HttpError) { const message = `${err.statusCode}: ${err.message}`; showToast(message, "error"); setNumberVerified(false); } }, }); /* const testActionMutation = trpc.viewer.workflows.testAction.useMutation({ onSuccess: async () => { showToast(t("notification_sent"), "success"); }, onError: (err) => { let message = t("unexpected_error_try_again"); if (err instanceof TRPCClientError) { if (err.message === "rate-limit-exceeded") { message = t("rate_limit_exceeded"); } else { message = err.message; } } if (err instanceof HttpError) { message = `${err.statusCode}: ${err.message}`; } showToast(message, "error"); }, }); */ //trigger if (!step) { const trigger = form.getValues("trigger"); const triggerString = t(`${trigger.toLowerCase()}_trigger`); const selectedTrigger = { label: triggerString.charAt(0).toUpperCase() + triggerString.slice(1), value: trigger, }; return ( <>
1
{t("trigger")}
{t("when_something_happens")}
{ return ( { if (val) { const oldValue = form.getValues(`steps.${step.stepNumber - 1}.action`); const setNumberRequiredConfigs = ( phoneNumberIsNeeded: boolean, senderNeeded = true ) => { setIsSenderIsNeeded(senderNeeded); setIsEmailAddressNeeded(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); setIsSenderIsNeeded(false); setIsEmailAddressNeeded(val.value === WorkflowActions.EMAIL_ADDRESS); setIsEmailSubjectNeeded(true); } if (isTextMessageToAttendeeAction(val.value)) { setIsRequiresConfirmationNeeded(true); } else { setIsRequiresConfirmationNeeded(false); } if ( form.getValues(`steps.${step.stepNumber - 1}.template`) === WorkflowTemplates.REMINDER ) { if (isSMSOrWhatsappAction(val.value) === isSMSOrWhatsappAction(oldValue)) { if (isAttendeeAction(oldValue) !== isAttendeeAction(val.value)) { const currentReminderBody = form.getValues(`steps.${step.stepNumber - 1}.reminderBody`) || ""; const newReminderBody = currentReminderBody .replaceAll("{ORGANIZER}", "{PLACEHOLDER}") .replaceAll("{ATTENDEE}", "{ORGANIZER}") .replaceAll("{PLACEHOLDER}", "{ATTENDEE}"); form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, newReminderBody); if (!isSMSOrWhatsappAction(val.value)) { const currentEmailSubject = form.getValues(`steps.${step.stepNumber - 1}.emailSubject`) || ""; const newEmailSubject = isAttendeeAction(val.value) ? currentEmailSubject.replace("{ORGANIZER}", "{ATTENDEE}") : currentEmailSubject.replace("{ATTENDEE}", "{ORGANIZER}"); form.setValue( `steps.${step.stepNumber - 1}.emailSubject`, newEmailSubject || "" ); } } } else { if (isSMSAction(val.value)) { form.setValue( `steps.${step.stepNumber - 1}.reminderBody`, smsReminderTemplate(true, val.value, timeFormat) ); } else if (isWhatsappAction(val.value)) { form.setValue( `steps.${step.stepNumber - 1}.reminderBody`, whatsappReminderTemplate(true, val.value, timeFormat) ); } else { const emailReminderBody = emailReminderTemplate( true, val.value, timeFormat ); form.setValue( `steps.${step.stepNumber - 1}.reminderBody`, emailReminderBody.emailBody ); form.setValue( `steps.${step.stepNumber - 1}.emailSubject`, emailReminderBody.emailSubject ); } } } 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`); form.setValue(`steps.${step.stepNumber - 1}.action`, val.value); setUpdateTemplate(!updateTemplate); } }} defaultValue={selectedAction} options={actionOptions?.map((option) => ({ ...option, verificationAction: () => props.setKYCVerificationDialogOpen(true), }))} isOptionDisabled={(option: { label: string; value: WorkflowActions; needsUpgrade: boolean; needsVerification: boolean; }) => option.needsUpgrade || option.needsVerification} /> ); }} /> {isRequiresConfirmationNeeded ? (

{t("requires_confirmation_mandatory")}

) : ( <> )}
{isPhoneNumberNeeded && (
( { const isAlreadyVerified = !!verifiedNumbers ?.concat([]) .find((number) => number.replace(/\s/g, "") === val?.replace(/\s/g, "")); setNumberVerified(isAlreadyVerified); onChange(val); }} /> )} />
{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && (

{form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""}

)} {numberVerified ? (
{t("number_verified")}
) : ( !props.readOnly && ( <>
{ setVerificationCode(e.target.value); }} required />
{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && (

{form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""}

)} ) )}
)} {!isWhatsappAction(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (
{isSenderIsNeeded ? ( <>
{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sender && (

{t("sender_id_error_message")}

)} ) : ( <>
)}
)} {canRequirePhoneNumber(form.getValues(`steps.${step.stepNumber - 1}.action`)) && (
( form.setValue(`steps.${step.stepNumber - 1}.numberRequired`, e.target.checked) } /> )} />
)} {isEmailAddressNeeded && (
)}
{ return (