diff --git a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx index 7929ca13809ccb..7b60fa57712997 100644 --- a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx @@ -12,7 +12,7 @@ import type { RouterOutputs } from "@calcom/trpc/react"; import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui"; import { Button, Icon, Label, MultiSelectCheckboxes, TextField, CheckboxField, InfoBadge } from "@calcom/ui"; -import { isSMSAction, isWhatsappAction } from "../lib/actionHelperFunctions"; +import { isSMSAction } from "../lib/actionHelperFunctions"; import type { FormValues } from "../pages/workflow"; import { AddActionDialog } from "./AddActionDialog"; import { DeleteDialog } from "./DeleteDialog"; @@ -83,7 +83,7 @@ export default function WorkflowDetailsPage(props: Props) { workflowId: workflowId, reminderBody: null, emailSubject: null, - template: isWhatsappAction(action) ? WorkflowTemplates.REMINDER : WorkflowTemplates.CUSTOM, + template: WorkflowTemplates.REMINDER, 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 f687a9fd0b4600..f56ba81c5c942d 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -8,6 +8,7 @@ import "react-phone-number-input/style.css"; import { classNames } from "@calcom/lib"; import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; +import useHasPaidPlan from "@calcom/lib/hooks/useHasPaidPlan"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; @@ -84,6 +85,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const { t, i18n } = useLocale(); const utils = trpc.useUtils(); + const { hasPaidPlan } = useHasPaidPlan(); + const { step, form, reload, setReload, teamId } = props; const { data: _verifiedNumbers } = trpc.viewer.workflows.getVerifiedNumbers.useQuery( { teamId }, @@ -129,7 +132,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); const triggerOptions = getWorkflowTriggerOptions(t); - const templateOptions = getWorkflowTemplateOptions(t, step?.action); + const templateOptions = getWorkflowTemplateOptions(t, step?.action, hasPaidPlan); if (step && form.getValues(`steps.${step.stepNumber - 1}.template`) === WorkflowTemplates.REMINDER) { if (!form.getValues(`steps.${step.stepNumber - 1}.reminderBody`)) { @@ -370,7 +373,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { needsTeamsUpgrade: false, }; - const selectedTemplate = { label: t(`${step.template.toLowerCase()}`), value: step.template }; + const selectedTemplate = { + label: t(`${step.template.toLowerCase()}`), + value: step.template, + needsTeamsUpgrade: false, + }; const canRequirePhoneNumber = (workflowStep: string) => { return ( @@ -879,6 +886,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { defaultValue={selectedTemplate} value={selectedTemplate} options={templateOptions} + isOptionDisabled={(option: { + label: string; + value: any; + needsTeamsUpgrade: boolean; + }) => option.needsTeamsUpgrade} /> ); }} @@ -906,7 +918,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { refEmailSubject.current = e; }} rows={1} - disabled={props.readOnly} + disabled={props.readOnly || !hasPaidPlan} className="my-0 focus:ring-transparent" required {...restEmailSubjectForm} @@ -939,7 +951,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { updateTemplate={updateTemplate} firstRender={firstRender} setFirstRender={setFirstRender} - editable={!props.readOnly && !isWhatsappAction(step.action)} + editable={!props.readOnly && !isWhatsappAction(step.action) && hasPaidPlan} excludedToolbarItems={ !isSMSAction(step.action) ? [] : ["blockType", "bold", "italic", "link"] } diff --git a/packages/features/ee/workflows/lib/actionHelperFunctions.ts b/packages/features/ee/workflows/lib/actionHelperFunctions.ts index 76892848b96e83..4fdd8672d113e6 100644 --- a/packages/features/ee/workflows/lib/actionHelperFunctions.ts +++ b/packages/features/ee/workflows/lib/actionHelperFunctions.ts @@ -30,6 +30,14 @@ export function isSMSOrWhatsappAction(action: WorkflowActions) { return isSMSAction(action) || isWhatsappAction(action); } +export function isEmailAction(action: WorkflowActions) { + return ( + action === WorkflowActions.EMAIL_ADDRESS || + action === WorkflowActions.EMAIL_ATTENDEE || + action === WorkflowActions.EMAIL_HOST + ); +} + export function isAttendeeAction(action: WorkflowActions) { return ( action === WorkflowActions.SMS_ATTENDEE || diff --git a/packages/features/ee/workflows/lib/getOptions.ts b/packages/features/ee/workflows/lib/getOptions.ts index d015eef14a86ed..c1a58dc01bd088 100644 --- a/packages/features/ee/workflows/lib/getOptions.ts +++ b/packages/features/ee/workflows/lib/getOptions.ts @@ -1,7 +1,7 @@ import type { TFunction } from "next-i18next"; import type { WorkflowActions } from "@calcom/prisma/enums"; -import { WorkflowTriggerEvents } from "@calcom/prisma/enums"; +import { WorkflowTemplates, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { isSMSOrWhatsappAction, isWhatsappAction, isEmailToAttendeeAction } from "./actionHelperFunctions"; import { @@ -39,7 +39,11 @@ export function getWorkflowTriggerOptions(t: TFunction) { }); } -export function getWorkflowTemplateOptions(t: TFunction, action: WorkflowActions | undefined) { +export function getWorkflowTemplateOptions( + t: TFunction, + action: WorkflowActions | undefined, + hasPaidPlan: boolean +) { const TEMPLATES = action && isWhatsappAction(action) ? WHATSAPP_WORKFLOW_TEMPLATES @@ -47,6 +51,10 @@ export function getWorkflowTemplateOptions(t: TFunction, action: WorkflowActions ? ATTENDEE_WORKFLOW_TEMPLATES : BASIC_WORKFLOW_TEMPLATES; return TEMPLATES.map((template) => { - return { label: t(`${template.toLowerCase()}`), value: template }; - }) as { label: string; value: any }[]; + return { + label: t(`${template.toLowerCase()}`), + value: template, + needsTeamsUpgrade: !hasPaidPlan && template == WorkflowTemplates.CUSTOM, + }; + }) as { label: string; value: any; needsTeamsUpgrade: boolean }[]; } diff --git a/packages/trpc/server/routers/viewer/workflows/update.handler.ts b/packages/trpc/server/routers/viewer/workflows/update.handler.ts index f65e6bcf8b86a7..f65c6af17c4101 100755 --- a/packages/trpc/server/routers/viewer/workflows/update.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/update.handler.ts @@ -1,9 +1,12 @@ -import { isSMSOrWhatsappAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions"; +import { + isEmailAction, + isSMSOrWhatsappAction, +} from "@calcom/features/ee/workflows/lib/actionHelperFunctions"; import { IS_SELF_HOSTED } from "@calcom/lib/constants"; import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; import { WorkflowRepository } from "@calcom/lib/server/repository/workflow"; import type { PrismaClient } from "@calcom/prisma"; -import { WorkflowActions } from "@calcom/prisma/enums"; +import { WorkflowActions, WorkflowTemplates } from "@calcom/prisma/enums"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; import { TRPCError } from "@trpc/server"; @@ -20,6 +23,7 @@ import { verifyEmailSender, removeSmsReminderFieldForEventTypes, isStepEdited, + getEmailTemplateText, } from "./util"; type UpdateOptions = { @@ -333,8 +337,34 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }); } else if (isStepEdited(oldStep, newStep)) { // check if step that require team plan already existed before - if (!hasPaidPlan && !isSMSOrWhatsappAction(oldStep.action) && isSMSOrWhatsappAction(newStep.action)) { - throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" }); + if (!hasPaidPlan) { + const isChangingToSMSOrWhatsapp = + !isSMSOrWhatsappAction(oldStep.action) && isSMSOrWhatsappAction(newStep.action); + const isChangingToCustomTemplate = + newStep.template === WorkflowTemplates.CUSTOM && oldStep.template !== WorkflowTemplates.CUSTOM; + + if (isChangingToSMSOrWhatsapp || isChangingToCustomTemplate) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" }); + } + + //if email body or subject was changed, change to predefined template + if (newStep.emailSubject !== oldStep.emailSubject || newStep.reminderBody !== oldStep.reminderBody) { + // already existing custom templates can't be updated + if (newStep.template === WorkflowTemplates.CUSTOM) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" }); + } + + if (isEmailAction(newStep.action)) { + // on free plans always use predefined templates + const { emailBody, emailSubject } = getEmailTemplateText(newStep.template, { + locale: ctx.user.locale, + action: newStep.action, + timeFormat: ctx.user.timeFormat, + }); + + newStep = { ...newStep, reminderBody: emailBody, emailSubject }; + } + } } // update step @@ -388,8 +418,19 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { steps .filter((step) => step.id <= 0) .map(async (newStep) => { - if (isSMSOrWhatsappAction(newStep.action) && !hasPaidPlan) { - throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" }); + if (!hasPaidPlan) { + if (isSMSOrWhatsappAction(newStep.action) || newStep.template === WorkflowTemplates.CUSTOM) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Not available on free plan" }); + } + + // on free plans always use predefined templates + const { emailBody, emailSubject } = getEmailTemplateText(newStep.template, { + locale: ctx.user.locale, + action: newStep.action, + timeFormat: ctx.user.timeFormat, + }); + + newStep = { ...newStep, reminderBody: emailBody, emailSubject }; } if (newStep.action === WorkflowActions.EMAIL_ADDRESS) { diff --git a/packages/trpc/server/routers/viewer/workflows/update.schema.ts b/packages/trpc/server/routers/viewer/workflows/update.schema.ts index 21ad05785ef94a..d8b2b1868a5499 100644 --- a/packages/trpc/server/routers/viewer/workflows/update.schema.ts +++ b/packages/trpc/server/routers/viewer/workflows/update.schema.ts @@ -1,9 +1,9 @@ import { z } from "zod"; import { + WORKFLOW_TEMPLATES, TIME_UNIT, WORKFLOW_ACTIONS, - WORKFLOW_TEMPLATES, WORKFLOW_TRIGGER_EVENTS, } from "@calcom/features/ee/workflows/lib/constants"; diff --git a/packages/trpc/server/routers/viewer/workflows/util.ts b/packages/trpc/server/routers/viewer/workflows/util.ts index bc9e339fc9599b..05297c87cf6a7c 100644 --- a/packages/trpc/server/routers/viewer/workflows/util.ts +++ b/packages/trpc/server/routers/viewer/workflows/util.ts @@ -5,6 +5,8 @@ import { isSMSOrWhatsappAction } from "@calcom/ee/workflows/lib/actionHelperFunc import { getAllWorkflows } from "@calcom/ee/workflows/lib/getAllWorkflows"; import { scheduleEmailReminder } from "@calcom/ee/workflows/lib/reminders/emailReminderManager"; import { scheduleSMSReminder } from "@calcom/ee/workflows/lib/reminders/smsReminderManager"; +import emailRatingTemplate from "@calcom/ee/workflows/lib/reminders/templates/emailRatingTemplate"; +import emailReminderTemplate from "@calcom/ee/workflows/lib/reminders/templates/emailReminderTemplate"; import { scheduleWhatsappReminder } from "@calcom/ee/workflows/lib/reminders/whatsappReminderManager"; import type { Workflow as WorkflowType } from "@calcom/ee/workflows/lib/types"; import { SMS_REMINDER_NUMBER_FIELD } from "@calcom/features/bookings/lib/SystemField"; @@ -23,6 +25,7 @@ import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import prisma from "@calcom/prisma"; import type { Prisma, WorkflowStep } from "@calcom/prisma/client"; import type { TimeUnit } from "@calcom/prisma/enums"; +import { WorkflowTemplates } from "@calcom/prisma/enums"; import { SchedulingType } from "@calcom/prisma/enums"; import { BookingStatus, MembershipRole, WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -848,3 +851,28 @@ export const getEventTypeWorkflows = async ( return workflows.map((workflow) => ({ workflow })); }; + +export function getEmailTemplateText( + template: WorkflowTemplates, + params: { locale: string; action: WorkflowActions; timeFormat: number | null } +) { + const { locale, action } = params; + + const timeFormat = getTimeFormatStringFromUserTimeFormat(params.timeFormat); + + let { emailBody, emailSubject } = emailReminderTemplate(true, locale, action, timeFormat); + + if (template === WorkflowTemplates.RATING) { + const ratingTemplate = emailRatingTemplate({ + isEditingMode: true, + locale, + action, + timeFormat, + }); + + emailBody = ratingTemplate.emailBody; + emailSubject = ratingTemplate.emailSubject; + } + + return { emailBody, emailSubject }; +}