From 06eca4b28a441ab27e3afafc0c3788012e730e75 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Thu, 3 Nov 2022 09:24:41 +0100 Subject: [PATCH] Make SMS workflow actions only available for teams plan (#5330) * fix issue when updating workflow * make SMS actions pro feature * Improved clean up on inline functions Co-authored-by: CarinaWolli Co-authored-by: Peer Richelsen Co-authored-by: Alan --- .../components/v2/AddActionDialog.tsx | 57 ++++++++++++------- .../components/v2/WorkflowDetailsPage.tsx | 8 ++- .../components/v2/WorkflowStepContainer.tsx | 13 ++++- .../trpc/server/routers/viewer/workflows.tsx | 13 ++++- 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/packages/features/ee/workflows/components/v2/AddActionDialog.tsx b/packages/features/ee/workflows/components/v2/AddActionDialog.tsx index 1ebb9dc2c6..eb3251630e 100644 --- a/packages/features/ee/workflows/components/v2/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/v2/AddActionDialog.tsx @@ -27,6 +27,12 @@ interface IAddActionDialog { isOpenDialog: boolean; setIsOpenDialog: Dispatch>; addAction: (action: WorkflowActions, sendTo?: string, numberRequired?: boolean) => void; + isFreeUser: boolean; +} + +interface ISelectActionOption { + label: string; + value: WorkflowActions; } type AddActionFormValues = { @@ -35,12 +41,19 @@ type AddActionFormValues = { numberRequired?: boolean; }; +const cleanUpActionsForFreeUser = (actions: ISelectActionOption[]) => { + return actions.filter( + (item) => item.value !== WorkflowActions.SMS_ATTENDEE && item.value !== WorkflowActions.SMS_NUMBER + ); +}; + export const AddActionDialog = (props: IAddActionDialog) => { const { t } = useLocale(); - const { isOpenDialog, setIsOpenDialog, addAction } = props; + const { isOpenDialog, setIsOpenDialog, addAction, isFreeUser } = props; const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(false); const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState(false); - const actionOptions = getWorkflowActionOptions(t); + const workflowActions = getWorkflowActionOptions(t); + const actionOptions = isFreeUser ? cleanUpActionsForFreeUser(workflowActions) : workflowActions; const formSchema = z.object({ action: z.enum(WORKFLOW_ACTIONS), @@ -59,6 +72,26 @@ export const AddActionDialog = (props: IAddActionDialog) => { resolver: zodResolver(formSchema), }); + const handleSelectAction = (newValue: ISelectActionOption | null) => { + if (newValue) { + form.setValue("action", newValue.value); + if (newValue.value === WorkflowActions.SMS_NUMBER) { + setIsPhoneNumberNeeded(true); + setIsEmailAddressNeeded(false); + } else if (newValue.value === WorkflowActions.EMAIL_ADDRESS) { + setIsEmailAddressNeeded(true); + setIsPhoneNumberNeeded(false); + } else { + setIsEmailAddressNeeded(false); + setIsPhoneNumberNeeded(false); + } + form.unregister("sendTo"); + form.unregister("numberRequired"); + form.clearErrors("action"); + form.clearErrors("sendTo"); + } + }; + return ( @@ -86,25 +119,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { isSearchable={false} className="text-sm" defaultValue={actionOptions[0]} - onChange={(val) => { - if (val) { - form.setValue("action", val.value); - if (val.value === WorkflowActions.SMS_NUMBER) { - setIsPhoneNumberNeeded(true); - setIsEmailAddressNeeded(false); - } else if (val.value === WorkflowActions.EMAIL_ADDRESS) { - setIsEmailAddressNeeded(true); - setIsPhoneNumberNeeded(false); - } else { - setIsEmailAddressNeeded(false); - setIsPhoneNumberNeeded(false); - } - form.unregister("sendTo"); - form.unregister("numberRequired"); - form.clearErrors("action"); - form.clearErrors("sendTo"); - } - }} + onChange={handleSelectAction} options={actionOptions} /> ); diff --git a/packages/features/ee/workflows/components/v2/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/v2/WorkflowDetailsPage.tsx index 18aad85ee0..314dbe08cd 100644 --- a/packages/features/ee/workflows/components/v2/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/v2/WorkflowDetailsPage.tsx @@ -6,6 +6,7 @@ import { Controller, UseFormReturn } from "react-hook-form"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; +import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import { Icon } from "@calcom/ui/Icon"; import { Button } from "@calcom/ui/v2"; import { Label, TextField } from "@calcom/ui/v2"; @@ -28,6 +29,9 @@ export default function WorkflowDetailsPage(props: Props) { const { t } = useLocale(); const router = useRouter(); + const me = useMeQuery(); + const isFreeUser = me.data?.plan === "FREE"; + const [isAddActionDialogOpen, setIsAddActionDialogOpen] = useState(false); const [reload, setReload] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); @@ -120,7 +124,7 @@ export default function WorkflowDetailsPage(props: Props) {
{form.getValues("trigger") && (
- +
)} {form.getValues("steps") && ( @@ -133,6 +137,7 @@ export default function WorkflowDetailsPage(props: Props) { step={step} reload={reload} setReload={setReload} + isFreeUser={isFreeUser} /> ); })} @@ -152,6 +157,7 @@ export default function WorkflowDetailsPage(props: Props) { isOpenDialog={isAddActionDialogOpen} setIsOpenDialog={setIsAddActionDialogOpen} addAction={addAction} + isFreeUser={isFreeUser} /> ; reload?: boolean; setReload?: Dispatch>; + isFreeUser: boolean; }; export default function WorkflowStepContainer(props: WorkflowStepProps) { const { t, i18n } = useLocale(); - const { step, form, reload, setReload } = props; + const { step, form, reload, setReload, isFreeUser } = props; const [isAdditionalInputsDialogOpen, setIsAdditionalInputsDialogOpen] = useState(false); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); @@ -300,7 +301,15 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { } }} defaultValue={selectedAction} - options={actionOptions} + options={ + isFreeUser + ? actionOptions.filter( + (actionOption) => + actionOption.value !== WorkflowActions.SMS_ATTENDEE && + actionOption.value !== WorkflowActions.SMS_NUMBER + ) + : actionOptions + } /> ); }} diff --git a/packages/trpc/server/routers/viewer/workflows.tsx b/packages/trpc/server/routers/viewer/workflows.tsx index a706830e2a..e994c72f0a 100644 --- a/packages/trpc/server/routers/viewer/workflows.tsx +++ b/packages/trpc/server/routers/viewer/workflows.tsx @@ -27,12 +27,17 @@ import { scheduleSMSReminder, } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; import { getErrorFromUnknown } from "@calcom/lib/errors"; -import { getTranslation } from "@calcom/lib/server/i18n"; import { TRPCError } from "@trpc/server"; import { createProtectedRouter } from "../../createRouter"; +function isSMSAction(action: WorkflowActions) { + if (action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER) { + return true; + } +} + export const workflowsRouter = createProtectedRouter() .query("list", { async resolve({ ctx }) { @@ -515,6 +520,9 @@ export const workflowsRouter = createProtectedRouter() }); //step was edited } else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) { + if (user.plan === "FREE" && !isSMSAction(oldStep.action) && isSMSAction(newStep.action)) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } await ctx.prisma.workflowStep.update({ where: { id: oldStep.id, @@ -652,6 +660,9 @@ export const workflowsRouter = createProtectedRouter() //added steps const addedSteps = steps.map((s) => { if (s.id <= 0) { + if (user.plan === "FREE" && isSMSAction(s.action)) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } const { id: stepId, ...stepToAdd } = s; return stepToAdd; }