import { TimeUnit, WorkflowActions, WorkflowStep, WorkflowTemplates, WorkflowTriggerEvents, } from "@prisma/client"; import { Dispatch, SetStateAction, useRef, useState } from "react"; import { Controller, UseFormReturn } from "react-hook-form"; import "react-phone-number-input/style.css"; import { classNames } from "@calcom/lib"; import { SENDER_ID } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { trpc, TRPCClientError } from "@calcom/trpc/react"; import { Badge, Button, Checkbox, ConfirmationDialogContent, Dialog, DialogClose, DialogContent, Dropdown, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, EmailField, Icon, Label, PhoneInput, Select, showToast, TextArea, TextField, Editor, AddVariablesDropdown, } from "@calcom/ui"; import { getWorkflowTemplateOptions, getWorkflowTriggerOptions } from "../lib/getOptions"; import { translateVariablesToEnglish } from "../lib/variableTranslations"; import type { FormValues } from "../pages/workflow"; import { TimeTimeUnitInput } from "./TimeTimeUnitInput"; type WorkflowStepProps = { step?: WorkflowStep; form: UseFormReturn; reload?: boolean; setReload?: Dispatch>; }; const dynamicTextVariables = [ "event_name", "event_date", "event_time", "location", "organizer_name", "attendee_name", "attendee_email", "additional_notes", "meeting_url", ]; export default function WorkflowStepContainer(props: WorkflowStepProps) { const { t, i18n } = useLocale(); const utils = trpc.useContext(); const { step, form, reload, setReload } = props; const { data: _verifiedNumbers } = trpc.viewer.workflows.getVerifiedNumbers.useQuery(); const verifiedNumbers = _verifiedNumbers?.map((number) => number.phoneNumber); const [isAdditionalInputsDialogOpen, setIsAdditionalInputsDialogOpen] = useState(false); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); const [verificationCode, setVerificationCode] = useState(""); const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState( step?.action === WorkflowActions.SMS_NUMBER ? true : false ); const [isSenderIdNeeded, setIsSenderIdNeeded] = useState( step?.action === WorkflowActions.SMS_NUMBER || step?.action === WorkflowActions.SMS_ATTENDEE ? 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_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 { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); const triggerOptions = getWorkflowTriggerOptions(t); const templateOptions = getWorkflowTemplateOptions(t); 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 [numberVerified, setNumberVerified] = useState( verifiedNumbers && step ? !!verifiedNumbers.find((number) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`)) : false ); 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 wasSMSAction = oldValue === WorkflowActions.SMS_ATTENDEE || oldValue === WorkflowActions.SMS_NUMBER; const isSMSAction = val.value === WorkflowActions.SMS_ATTENDEE || val.value === WorkflowActions.SMS_NUMBER; if (isSMSAction) { setIsSenderIdNeeded(true); setIsEmailAddressNeeded(false); setIsPhoneNumberNeeded(val.value === WorkflowActions.SMS_NUMBER); setNumberVerified(false); if (!wasSMSAction) { form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, ""); } } else { setIsPhoneNumberNeeded(false); setIsSenderIdNeeded(false); setIsEmailAddressNeeded(val.value === WorkflowActions.EMAIL_ADDRESS); if (wasSMSAction) { form.setValue(`steps.${step.stepNumber - 1}.reminderBody`, ""); } } 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_ADDRESS ) { setIsEmailSubjectNeeded(true); } else { setIsEmailSubjectNeeded(false); } form.setValue(`steps.${step.stepNumber - 1}.action`, val.value); } }} defaultValue={selectedAction} options={actionOptions} isOptionDisabled={(option: { label: string; value: WorkflowActions; disabled: boolean; }) => option.disabled} /> ); }} />
{(isPhoneNumberNeeded || isSenderIdNeeded) && (
{isPhoneNumberNeeded && ( <>
control={form.control} name={`steps.${step.stepNumber - 1}.sendTo`} placeholder={t("phone_number")} id={`steps.${step.stepNumber - 1}.sendTo`} className="min-w-fit sm:rounded-tl-md sm:rounded-bl-md sm:border-r-transparent" required onChange={() => { const isAlreadyVerified = !!verifiedNumbers ?.concat([]) .find( (number) => number === form.getValues(`steps.${step.stepNumber - 1}.sendTo`) ); setNumberVerified(isAlreadyVerified); }} />
{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")}
) : ( <>
{ setVerificationCode(e.target.value); }} required />
)} )} {isSenderIdNeeded && ( <>
{form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sender && (

{t("sender_id_error_message")}

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