Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: disable custom templates for free plan #18635

Merged
merged 9 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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`)) {
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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}
/>
);
}}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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"]
}
Expand Down
8 changes: 8 additions & 0 deletions packages/features/ee/workflows/lib/actionHelperFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down
16 changes: 12 additions & 4 deletions packages/features/ee/workflows/lib/getOptions.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -39,14 +39,22 @@ 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
: action && isEmailToAttendeeAction(action)
? 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 }[];
}
53 changes: 47 additions & 6 deletions packages/trpc/server/routers/viewer/workflows/update.handler.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -20,6 +23,7 @@ import {
verifyEmailSender,
removeSmsReminderFieldForEventTypes,
isStepEdited,
getEmailTemplateText,
} from "./util";

type UpdateOptions = {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
28 changes: 28 additions & 0 deletions packages/trpc/server/routers/viewer/workflows/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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 };
}
Loading