feat: disable form workflows for free plan, add upgrade badge, make reminder template free (#24839)

This commit is contained in:
Dhairyashil Shinde
2025-11-03 14:08:38 +00:00
committed by GitHub
parent 02b57eb82e
commit 5663b2ae59
4 changed files with 51 additions and 15 deletions
@@ -40,6 +40,7 @@ interface IAddActionDialog {
needsCredits: boolean;
creditsTeamId?: number;
isOrganization?: boolean;
needsTeamsUpgrade?: boolean;
}[];
}
@@ -170,7 +171,16 @@ export const AddActionDialog = (props: IAddActionDialog) => {
menuPlacement="bottom"
defaultValue={actionOptions[0]}
onChange={handleSelectAction}
options={actionOptions}
options={actionOptions.map((option) => ({
label: option.label,
value: option.value,
needsTeamsUpgrade: option.needsTeamsUpgrade,
}))}
isOptionDisabled={(option: {
label: string;
value: WorkflowActions;
needsTeamsUpgrade?: boolean;
}) => !!option.needsTeamsUpgrade}
/>
);
}}
@@ -3,6 +3,7 @@ import type { Dispatch, SetStateAction } from "react";
import { useState, useEffect } from "react";
import type { UseFormReturn } from "react-hook-form";
import { useHasActiveTeamPlan } from "@calcom/features/billing/hooks/useHasPaidPlan";
import type { WorkflowPermissions } from "@calcom/features/workflows/repositories/WorkflowPermissionsRepository";
import { SENDER_ID, SENDER_NAME, SCANNING_WORKFLOW_STEPS } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -43,6 +44,7 @@ export default function WorkflowDetailsPage(props: Props) {
const { form, workflowId, selectedOptions, setSelectedOptions, teamId, isOrg, allOptions, permissions } =
props;
const { t, i18n } = useLocale();
const { hasActiveTeamPlan } = useHasActiveTeamPlan();
const [isAddActionDialogOpen, setIsAddActionDialogOpen] = useState(false);
const [isDeleteStepDialogOpen, setIsDeleteStepDialogOpen] = useState(false);
@@ -84,12 +86,15 @@ export default function WorkflowDetailsPage(props: Props) {
}
}
const needsTeamsUpgrade = isFormTrigger(form.getValues("trigger")) && !hasActiveTeamPlan;
return {
...option,
label,
creditsTeamId: teamId,
isOrganization: isOrg,
isCalAi: isCalAIAction(option.value),
needsTeamsUpgrade,
};
})
: [];
@@ -101,6 +101,7 @@ type WorkflowStepProps = {
creditsTeamId?: number;
isOrganization: boolean;
isCalAi: boolean;
needsTeamsUpgrade?: boolean;
}[];
updateTemplate: boolean;
setUpdateTemplate: Dispatch<SetStateAction<boolean>>;
@@ -371,7 +372,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
handleCreateAgent,
]);
const triggerOptions = getWorkflowTriggerOptions(t);
const triggerOptions = getWorkflowTriggerOptions(t, hasActiveTeamPlan);
const templateOptions = getWorkflowTemplateOptions(t, step?.action, hasActiveTeamPlan, trigger);
const steps = useWatch({
@@ -379,6 +380,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
name: "steps",
});
// Extract current step template from watched steps array
const currentStepTemplate = step ? steps?.[step.stepNumber - 1]?.template : null;
const isReminderTemplate = currentStepTemplate === WorkflowTemplates.REMINDER;
const hasAiAction = hasCalAIAction(steps);
const hasEmailToHostAction = steps.some((s) => s.action === WorkflowActions.EMAIL_HOST);
const hasWhatsappAction = steps.some((s) => isWhatsappAction(s.action));
@@ -531,11 +537,12 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
isDisabled={props.readOnly}
onChange={(val) => {
if (val) {
const currentTrigger = form.getValues("trigger");
const triggerValue = val.value as WorkflowTriggerEvents;
const currentTrigger = form.getValues("trigger") as WorkflowTriggerEvents;
const isCurrentFormTrigger = isFormTrigger(currentTrigger);
const isNewFormTrigger = isFormTrigger(val.value);
const isNewFormTrigger = isFormTrigger(triggerValue);
form.setValue("trigger", val.value);
form.setValue("trigger", triggerValue);
// Reset activeOn when switching between form and non-form triggers
if (isCurrentFormTrigger !== isNewFormTrigger) {
@@ -546,12 +553,12 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
form.setValue("selectAll", false);
}
const newTimeSectionText = getTimeSectionText(val.value, t);
const newTimeSectionText = getTimeSectionText(triggerValue, t);
if (newTimeSectionText) {
setTimeSectionText(newTimeSectionText);
if (
val.value === WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW ||
val.value === WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
triggerValue === WorkflowTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW ||
triggerValue === WorkflowTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
) {
form.setValue("time", 5);
form.setValue("timeUnit", TimeUnit.MINUTE);
@@ -564,7 +571,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
form.unregister("time");
form.unregister("timeUnit");
}
if (isFormTrigger(val.value)) {
if (isFormTrigger(triggerValue)) {
const steps = form.getValues("steps");
if (steps?.length) {
const updatedSteps = steps.map((step) =>
@@ -584,7 +591,14 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}
}}
defaultValue={selectedTrigger}
options={filteredTriggerOptions}
options={filteredTriggerOptions.map((option) => ({
label: option.label,
value: option.value,
needsTeamsUpgrade: option.needsTeamsUpgrade,
}))}
isOptionDisabled={(option: { label: string; value: string; needsTeamsUpgrade?: boolean }) =>
!!option.needsTeamsUpgrade
}
/>
);
}}
@@ -1321,7 +1335,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
refEmailSubject.current = e;
}}
rows={2}
disabled={props.readOnly || !hasActiveTeamPlan}
disabled={props.readOnly || (!hasActiveTeamPlan && !isReminderTemplate)}
className="my-0 focus:ring-transparent"
required
{...restEmailSubjectForm}
@@ -1354,7 +1368,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
editable={
!props.readOnly &&
!isWhatsappAction(step.action) &&
(hasActiveTeamPlan || isSMSAction(step.action))
(hasActiveTeamPlan || isSMSAction(step.action) || isReminderTemplate)
}
excludedToolbarItems={
!isSMSAction(step.action) ? [] : ["blockType", "bold", "italic", "link"]
@@ -31,7 +31,7 @@ export function getWorkflowActionOptions(t: TFunction, isOrgsPlan?: boolean) {
});
}
export function getWorkflowTriggerOptions(t: TFunction) {
export function getWorkflowTriggerOptions(t: TFunction, hasPaidPlan: boolean = false) {
// TODO: remove this after workflows are supported
const filterdWorkflowTriggerEvents = WORKFLOW_TRIGGER_EVENTS.filter(
(event) =>
@@ -41,8 +41,15 @@ export function getWorkflowTriggerOptions(t: TFunction) {
return filterdWorkflowTriggerEvents.map((triggerEvent) => {
const triggerString = t(`${triggerEvent.toLowerCase()}_trigger`);
const isFormSubmittedTrigger =
triggerEvent === WorkflowTriggerEvents.FORM_SUBMITTED ||
triggerEvent === WorkflowTriggerEvents.FORM_SUBMITTED_NO_EVENT;
return { label: triggerString.charAt(0).toUpperCase() + triggerString.slice(1), value: triggerEvent };
return {
label: triggerString.charAt(0).toUpperCase() + triggerString.slice(1),
value: triggerEvent,
needsTeamsUpgrade: !hasPaidPlan && isFormSubmittedTrigger,
};
});
}
@@ -55,7 +62,7 @@ function convertToTemplateOptions(
return {
label: t(`${template.toLowerCase()}`),
value: template,
needsTeamsUpgrade: !hasPaidPlan,
needsTeamsUpgrade: !hasPaidPlan && template !== WorkflowTemplates.REMINDER,
} as { label: string; value: any; needsTeamsUpgrade: boolean };
});
}