2022-08-31 19:42:37 +00:00
|
|
|
import Link from "next/link";
|
|
|
|
import { useRouter } from "next/router";
|
2022-11-23 02:55:25 +00:00
|
|
|
import { useEffect, useState } from "react";
|
2022-08-31 19:42:37 +00:00
|
|
|
|
2023-04-13 02:10:23 +00:00
|
|
|
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
2022-08-31 19:42:37 +00:00
|
|
|
import classNames from "@calcom/lib/classNames";
|
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
|
|
import { HttpError } from "@calcom/lib/http-error";
|
2023-05-02 11:44:05 +00:00
|
|
|
import { WorkflowActions } from "@calcom/prisma/enums";
|
2023-04-13 02:10:23 +00:00
|
|
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
2022-08-31 19:42:37 +00:00
|
|
|
import { trpc } from "@calcom/trpc/react";
|
2023-04-13 02:10:23 +00:00
|
|
|
import { Button, EmptyScreen, showToast, Switch, Tooltip, Alert } from "@calcom/ui";
|
|
|
|
import { ExternalLink, Zap, Lock } from "@calcom/ui/components/icon";
|
2022-08-31 19:42:37 +00:00
|
|
|
|
2023-04-19 14:15:08 +00:00
|
|
|
import LicenseRequired from "../../common/components/LicenseRequired";
|
2022-11-23 02:55:25 +00:00
|
|
|
import { getActionIcon } from "../lib/getActionIcon";
|
2022-09-30 18:39:23 +00:00
|
|
|
import SkeletonLoader from "./SkeletonLoaderEventWorkflowsTab";
|
2023-02-27 07:24:43 +00:00
|
|
|
import type { WorkflowType } from "./WorkflowListPage";
|
2022-08-31 19:42:37 +00:00
|
|
|
|
|
|
|
type ItemProps = {
|
|
|
|
workflow: WorkflowType;
|
|
|
|
eventType: {
|
|
|
|
id: number;
|
|
|
|
title: string;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const WorkflowListItem = (props: ItemProps) => {
|
|
|
|
const { workflow, eventType } = props;
|
|
|
|
const { t } = useLocale();
|
|
|
|
|
|
|
|
const [activeEventTypeIds, setActiveEventTypeIds] = useState(
|
|
|
|
workflow.activeOn.map((active) => {
|
|
|
|
if (active.eventType) {
|
|
|
|
return active.eventType.id;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
const isActive = activeEventTypeIds.includes(eventType.id);
|
2023-03-02 18:15:28 +00:00
|
|
|
const utils = trpc.useContext();
|
2022-08-31 19:42:37 +00:00
|
|
|
|
2022-11-10 23:40:01 +00:00
|
|
|
const activateEventTypeMutation = trpc.viewer.workflows.activateEventType.useMutation({
|
2022-09-06 03:29:00 +00:00
|
|
|
onSuccess: async () => {
|
|
|
|
let offOn = "";
|
|
|
|
if (activeEventTypeIds.includes(eventType.id)) {
|
|
|
|
const newActiveEventTypeIds = activeEventTypeIds.filter((id) => {
|
|
|
|
return id !== eventType.id;
|
|
|
|
});
|
|
|
|
setActiveEventTypeIds(newActiveEventTypeIds);
|
|
|
|
offOn = "off";
|
|
|
|
} else {
|
|
|
|
const newActiveEventTypeIds = activeEventTypeIds;
|
|
|
|
newActiveEventTypeIds.push(eventType.id);
|
|
|
|
setActiveEventTypeIds(newActiveEventTypeIds);
|
|
|
|
offOn = "on";
|
|
|
|
}
|
2023-03-02 18:15:28 +00:00
|
|
|
await utils.viewer.eventTypes.get.invalidate({ id: eventType.id });
|
2022-09-06 03:29:00 +00:00
|
|
|
showToast(
|
|
|
|
t("workflow_turned_on_successfully", {
|
|
|
|
workflowName: workflow.name,
|
|
|
|
offOn,
|
|
|
|
}),
|
|
|
|
"success"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
onError: (err) => {
|
|
|
|
if (err instanceof HttpError) {
|
|
|
|
const message = `${err.statusCode}: ${err.message}`;
|
|
|
|
showToast(message, "error");
|
|
|
|
}
|
2023-02-27 07:24:43 +00:00
|
|
|
if (err.data?.code === "UNAUTHORIZED") {
|
|
|
|
// TODO: Add missing translation
|
|
|
|
const message = `${err.data.code}: You are not authorized to enable or disable this workflow`;
|
|
|
|
showToast(message, "error");
|
|
|
|
}
|
2022-09-06 03:29:00 +00:00
|
|
|
},
|
|
|
|
});
|
2022-08-31 19:42:37 +00:00
|
|
|
|
|
|
|
const sendTo: Set<string> = new Set();
|
|
|
|
|
|
|
|
workflow.steps.forEach((step) => {
|
|
|
|
switch (step.action) {
|
|
|
|
case WorkflowActions.EMAIL_HOST:
|
2023-01-09 14:15:11 +00:00
|
|
|
sendTo.add(t("organizer_name_variable"));
|
2022-08-31 19:42:37 +00:00
|
|
|
break;
|
|
|
|
case WorkflowActions.EMAIL_ATTENDEE:
|
2023-01-09 14:15:11 +00:00
|
|
|
sendTo.add(t("attendee_name_variable"));
|
2022-08-31 19:42:37 +00:00
|
|
|
break;
|
|
|
|
case WorkflowActions.SMS_ATTENDEE:
|
2023-01-09 14:15:11 +00:00
|
|
|
sendTo.add(t("attendee_name_variable"));
|
2022-08-31 19:42:37 +00:00
|
|
|
break;
|
|
|
|
case WorkflowActions.SMS_NUMBER:
|
|
|
|
sendTo.add(step.sendTo || "");
|
|
|
|
break;
|
2022-10-10 13:40:20 +00:00
|
|
|
case WorkflowActions.EMAIL_ADDRESS:
|
|
|
|
sendTo.add(step.sendTo || "");
|
|
|
|
break;
|
2022-08-31 19:42:37 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
2023-04-11 21:44:14 +00:00
|
|
|
<div className="border-subtle flex w-full items-center overflow-hidden rounded-md border p-6 px-3 md:p-6">
|
2023-04-05 18:14:46 +00:00
|
|
|
<div className="bg-subtle mr-4 flex h-10 w-10 items-center justify-center rounded-full text-xs font-medium">
|
2022-08-31 19:42:37 +00:00
|
|
|
{getActionIcon(
|
|
|
|
workflow.steps,
|
2023-04-05 18:14:46 +00:00
|
|
|
isActive ? "h-6 w-6 stroke-[1.5px] text-default" : "h-6 w-6 stroke-[1.5px] text-muted"
|
2022-08-31 19:42:37 +00:00
|
|
|
)}
|
|
|
|
</div>
|
2022-09-07 14:05:25 +00:00
|
|
|
<div className=" grow">
|
2022-08-31 19:42:37 +00:00
|
|
|
<div
|
|
|
|
className={classNames(
|
2023-04-05 18:14:46 +00:00
|
|
|
"text-emphasis mb-1 w-full truncate text-base font-medium leading-4 md:max-w-max",
|
|
|
|
workflow.name && isActive ? "text-emphasis" : "text-subtle"
|
2022-08-31 19:42:37 +00:00
|
|
|
)}>
|
|
|
|
{workflow.name
|
|
|
|
? workflow.name
|
|
|
|
: "Untitled (" +
|
|
|
|
`${t(`${workflow.steps[0].action.toLowerCase()}_action`)}`.charAt(0).toUpperCase() +
|
|
|
|
`${t(`${workflow.steps[0].action.toLowerCase()}_action`)}`.slice(1) +
|
|
|
|
")"}
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className={classNames(
|
2022-09-07 14:05:25 +00:00
|
|
|
" flex w-fit items-center whitespace-nowrap rounded-sm text-sm leading-4",
|
2023-04-05 18:14:46 +00:00
|
|
|
isActive ? "text-default" : "text-muted"
|
2022-08-31 19:42:37 +00:00
|
|
|
)}>
|
|
|
|
<span className="mr-1">{t("to")}:</span>
|
|
|
|
{Array.from(sendTo).map((sendToPerson, index) => {
|
|
|
|
return <span key={index}>{`${index ? ", " : ""}${sendToPerson}`}</span>;
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-09-06 03:29:00 +00:00
|
|
|
<div className="flex-none">
|
2023-01-06 12:13:56 +00:00
|
|
|
<Link href={`/workflows/${workflow.id}`} passHref={true} target="_blank">
|
|
|
|
<Button type="button" color="minimal" className="mr-4">
|
|
|
|
<div className="hidden ltr:mr-2 rtl:ml-2 sm:block">{t("edit")}</div>
|
2023-04-12 15:26:31 +00:00
|
|
|
<ExternalLink className="text-default -mt-[2px] h-4 w-4 stroke-2" />
|
2023-01-06 12:13:56 +00:00
|
|
|
</Button>
|
2022-08-31 19:42:37 +00:00
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
<Tooltip content={t("turn_off") as string}>
|
2023-01-04 07:38:45 +00:00
|
|
|
<div className="ltr:mr-2 rtl:ml-2">
|
2022-08-31 19:42:37 +00:00
|
|
|
<Switch
|
|
|
|
checked={isActive}
|
|
|
|
onCheckedChange={() => {
|
|
|
|
activateEventTypeMutation.mutate({ workflowId: workflow.id, eventTypeId: eventType.id });
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Tooltip>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-04-13 02:10:23 +00:00
|
|
|
type EventTypeSetup = RouterOutputs["viewer"]["eventTypes"]["get"]["eventType"];
|
|
|
|
|
2022-08-31 19:42:37 +00:00
|
|
|
type Props = {
|
2023-04-13 02:10:23 +00:00
|
|
|
eventType: EventTypeSetup;
|
2022-08-31 19:42:37 +00:00
|
|
|
workflows: WorkflowType[];
|
|
|
|
};
|
|
|
|
|
|
|
|
function EventWorkflowsTab(props: Props) {
|
2023-02-27 07:24:43 +00:00
|
|
|
const { workflows, eventType } = props;
|
2022-08-31 19:42:37 +00:00
|
|
|
const { t } = useLocale();
|
2023-02-27 07:24:43 +00:00
|
|
|
const { data, isLoading } = trpc.viewer.workflows.list.useQuery({
|
|
|
|
teamId: eventType.team?.id,
|
|
|
|
userId: eventType.userId || undefined,
|
|
|
|
});
|
2022-08-31 19:42:37 +00:00
|
|
|
const router = useRouter();
|
|
|
|
const [sortedWorkflows, setSortedWorkflows] = useState<Array<WorkflowType>>([]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (data?.workflows) {
|
|
|
|
const activeWorkflows = workflows.map((workflowOnEventType) => {
|
|
|
|
return workflowOnEventType;
|
|
|
|
});
|
|
|
|
const disabledWorkflows = data.workflows.filter(
|
|
|
|
(workflow) =>
|
|
|
|
!workflows
|
|
|
|
.map((workflow) => {
|
|
|
|
return workflow.id;
|
|
|
|
})
|
|
|
|
.includes(workflow.id)
|
|
|
|
);
|
|
|
|
setSortedWorkflows(activeWorkflows.concat(disabledWorkflows));
|
|
|
|
}
|
|
|
|
}, [isLoading]);
|
|
|
|
|
2023-02-27 07:24:43 +00:00
|
|
|
const createMutation = trpc.viewer.workflows.create.useMutation({
|
2022-08-31 19:42:37 +00:00
|
|
|
onSuccess: async ({ workflow }) => {
|
|
|
|
await router.replace("/workflows/" + workflow.id);
|
|
|
|
},
|
|
|
|
onError: (err) => {
|
|
|
|
if (err instanceof HttpError) {
|
|
|
|
const message = `${err.statusCode}: ${err.message}`;
|
|
|
|
showToast(message, "error");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err.data?.code === "UNAUTHORIZED") {
|
|
|
|
const message = `${err.data.code}: You are not able to create this workflow`;
|
|
|
|
showToast(message, "error");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-04-13 02:10:23 +00:00
|
|
|
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
|
|
|
eventType,
|
|
|
|
t("locked_fields_admin_description"),
|
|
|
|
t("locked_fields_member_description")
|
|
|
|
);
|
|
|
|
|
2022-08-31 19:42:37 +00:00
|
|
|
return (
|
|
|
|
<LicenseRequired>
|
|
|
|
{!isLoading ? (
|
2023-04-13 02:10:23 +00:00
|
|
|
<>
|
|
|
|
{isManagedEventType && (
|
|
|
|
<Alert
|
|
|
|
severity="neutral"
|
|
|
|
title={t("locked_for_members")}
|
|
|
|
message={t("locked_workflows_description")}
|
2022-10-07 09:25:30 +00:00
|
|
|
/>
|
2023-04-13 02:10:23 +00:00
|
|
|
)}
|
|
|
|
{data?.workflows && data?.workflows.length > 0 ? (
|
|
|
|
<div>
|
|
|
|
<div className="space-y-4">
|
|
|
|
{sortedWorkflows.map((workflow) => {
|
|
|
|
return (
|
|
|
|
<WorkflowListItem key={workflow.id} workflow={workflow} eventType={props.eventType} />
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="pt-2 before:border-0">
|
|
|
|
<EmptyScreen
|
|
|
|
Icon={Zap}
|
|
|
|
headline={t("workflows")}
|
|
|
|
description={t("no_workflows_description")}
|
|
|
|
buttonRaw={
|
|
|
|
isChildrenManagedEventType && !isManagedEventType ? (
|
|
|
|
<Button StartIcon={Lock} color="secondary" disabled>
|
|
|
|
{t("locked_by_admin")}
|
|
|
|
</Button>
|
|
|
|
) : (
|
|
|
|
<Button
|
|
|
|
target="_blank"
|
|
|
|
color="secondary"
|
|
|
|
onClick={() => createMutation.mutate({ teamId: eventType.team?.id })}
|
|
|
|
loading={createMutation.isLoading}>
|
|
|
|
{t("create_workflow")}
|
|
|
|
</Button>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</>
|
2022-08-31 19:42:37 +00:00
|
|
|
) : (
|
2022-09-30 18:39:23 +00:00
|
|
|
<SkeletonLoader />
|
2022-08-31 19:42:37 +00:00
|
|
|
)}
|
|
|
|
</LicenseRequired>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default EventWorkflowsTab;
|