diff --git a/packages/features/ee/workflows/components/AddActionDialog.tsx b/packages/features/ee/workflows/components/AddActionDialog.tsx index b2945754db..a38bf4e03d 100644 --- a/packages/features/ee/workflows/components/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/AddActionDialog.tsx @@ -7,6 +7,7 @@ import { z } from "zod"; import { SENDER_ID } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; import { Button, Checkbox, @@ -23,7 +24,6 @@ import { } from "@calcom/ui"; import { WORKFLOW_ACTIONS } from "../lib/constants"; -import { getWorkflowActionOptions } from "../lib/getOptions"; import { onlyLettersNumbersSpaces } from "../pages/workflow"; interface IAddActionDialog { @@ -50,7 +50,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(false); const [isSenderIdNeeded, setIsSenderIdNeeded] = useState(false); const [isEmailAddressNeeded, setIsEmailAddressNeeded] = useState(false); - const actionOptions = getWorkflowActionOptions(t); + const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); const formSchema = z.object({ action: z.enum(WORKFLOW_ACTIONS), @@ -101,6 +101,8 @@ export const AddActionDialog = (props: IAddActionDialog) => { } }; + if (!actionOptions) return null; + return ( @@ -131,6 +133,11 @@ export const AddActionDialog = (props: IAddActionDialog) => { defaultValue={actionOptions[0]} onChange={handleSelectAction} options={actionOptions} + isOptionDisabled={(option: { + label: string; + value: WorkflowActions; + disabled: boolean; + }) => option.disabled} /> ); }} diff --git a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx index 6569a3242a..de13871f15 100644 --- a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx @@ -6,7 +6,6 @@ import { Controller, UseFormReturn } from "react-hook-form"; import { SENDER_ID } from "@calcom/lib/constants"; 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"; import { Button, Label, TextField } from "@calcom/ui"; import { MultiSelectCheckboxes } from "@calcom/ui"; diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index 97b6925ea3..45febd510c 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -92,8 +92,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const [showTimeSectionAfter, setShowTimeSectionAfter] = useState( form.getValues("trigger") === WorkflowTriggerEvents.AFTER_EVENT ); - - const actionOptions = getWorkflowActionOptions(t); + const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery(); const triggerOptions = getWorkflowTriggerOptions(t); const templateOptions = getWorkflowTemplateOptions(t); @@ -227,6 +226,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { const selectedAction = { label: actionString.charAt(0).toUpperCase() + actionString.slice(1), value: step.action, + disabled: false, }; const selectedTemplate = { label: t(`${step.template.toLowerCase()}`), value: step.template }; @@ -341,6 +341,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { }} defaultValue={selectedAction} options={actionOptions} + isOptionDisabled={(option: { + label: string; + value: WorkflowActions; + disabled: boolean; + }) => option.disabled} /> ); }} diff --git a/packages/features/ee/workflows/lib/getOptions.ts b/packages/features/ee/workflows/lib/getOptions.ts index 9b0623ef45..c3059e74e1 100644 --- a/packages/features/ee/workflows/lib/getOptions.ts +++ b/packages/features/ee/workflows/lib/getOptions.ts @@ -1,12 +1,19 @@ +import { WorkflowActions } from "@prisma/client"; import { TFunction } from "next-i18next"; import { TIME_UNIT, WORKFLOW_ACTIONS, WORKFLOW_TEMPLATES, WORKFLOW_TRIGGER_EVENTS } from "./constants"; -export function getWorkflowActionOptions(t: TFunction) { +export function getWorkflowActionOptions(t: TFunction, isTeamsPlan?: boolean) { return WORKFLOW_ACTIONS.map((action) => { const actionString = t(`${action.toLowerCase()}_action`); - return { label: actionString.charAt(0).toUpperCase() + actionString.slice(1), value: action }; + const isSMSAction = action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER; + + return { + label: actionString.charAt(0).toUpperCase() + actionString.slice(1), + value: action, + disabled: isSMSAction && !isTeamsPlan, + }; }); } diff --git a/packages/trpc/server/routers/viewer/workflows.tsx b/packages/trpc/server/routers/viewer/workflows.tsx index 1a970bf546..1b3be67122 100644 --- a/packages/trpc/server/routers/viewer/workflows.tsx +++ b/packages/trpc/server/routers/viewer/workflows.tsx @@ -17,6 +17,7 @@ import { WORKFLOW_ACTIONS, TIME_UNIT, } from "@calcom/features/ee/workflows/lib/constants"; +import { getWorkflowActionOptions } from "@calcom/features/ee/workflows/lib/getOptions"; import { deleteScheduledEmailReminder, scheduleEmailReminder, @@ -28,11 +29,16 @@ import { } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; import { SENDER_ID } from "@calcom/lib/constants"; import { getErrorFromUnknown } from "@calcom/lib/errors"; +import { getTranslation } from "@calcom/lib/server/i18n"; import { TRPCError } from "@trpc/server"; import { router, authedProcedure, authedRateLimitedProcedure } from "../../trpc"; +function isSMSAction(action: WorkflowActions) { + return action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER; +} + export const workflowsRouter = router({ list: authedProcedure.query(async ({ ctx }) => { const workflows = await ctx.prisma.workflow.findMany({ @@ -249,6 +255,11 @@ export const workflowsRouter = router({ }, select: { userId: true, + user: { + select: { + teams: true, + }, + }, steps: true, }, }); @@ -519,6 +530,13 @@ export const workflowsRouter = router({ }); //step was edited } else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) { + if ( + !userWorkflow.user.teams.length && + !isSMSAction(oldStep.action) && + isSMSAction(newStep.action) + ) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } await ctx.prisma.workflowStep.update({ where: { id: oldStep.id, @@ -658,6 +676,9 @@ export const workflowsRouter = router({ //added steps const addedSteps = steps.map((s) => { if (s.id <= 0) { + if (!userWorkflow.user.teams.length && isSMSAction(s.action)) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } const { id: stepId, ...stepToAdd } = s; return stepToAdd; } @@ -984,4 +1005,10 @@ export const workflowsRouter = router({ }); } }), + getWorkflowActionOptions: authedProcedure.query(async ({ ctx }) => { + const userId = ctx.user.id; + const hasTeamPlan = (await ctx.prisma.membership.count({ where: { userId } })) > 0; + const t = await getTranslation(ctx.user.locale, "common"); + return getWorkflowActionOptions(t, hasTeamPlan); + }), });