V2 workflow improvements (#4070)

This commit is contained in:
Carina Wollendorfer 2022-09-05 23:29:00 -04:00 committed by GitHub
parent dc68b608a0
commit dc6f8d1f63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 121 additions and 87 deletions

View File

@ -1006,7 +1006,7 @@
"add_exchange2016": "Connect Exchange 2016 Server",
"custom_template": "Custom template",
"email_body": "Email body",
"subject": "Subject",
"subject": "Email subject",
"text_message": "Text message",
"specific_issue": "Have a specific issue?",
"browse_our_docs": "browse our docs",
@ -1119,5 +1119,20 @@
"two_factor_auth": "Two factor authentication",
"recurring_event_tab_description": "Set up a repeating schedule",
"today": "today",
"active": "active"
"active": "active",
"add_variable": "Add variable",
"custom_phone_number": "Custom phone number",
"message_template": "Message template",
"email_subject": "Email subject",
"add_dynamic_variables": "Add dynamic text variables",
"event_name_info": "The event type name",
"event_date_info":"The event date",
"event_time_info":"The event start time",
"location_info":"The event location",
"organizer_name_info":"Your name",
"additional_notes_info":"The Additional notes of booking",
"attendee_name_info":"The person booking's name",
"to": "To",
"attendee_required_enter_number": "This will require the attendee to enter a phone number when booking",
"workflow_turned_on_successfully": "{{workflowName}} workflow turned {{offOn}} successfully"
}

View File

@ -7,16 +7,7 @@ import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
Form,
Select,
} from "@calcom/ui/v2";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Label, Select } from "@calcom/ui/v2";
import { WORKFLOW_ACTIONS } from "../../lib/constants";
import { getWorkflowActionOptions } from "../../lib/getOptions";
@ -56,10 +47,9 @@ export const AddActionDialog = (props: IAddActionDialog) => {
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent type="creation" useOwnActionButtons={true}>
<DialogContent type="creation" useOwnActionButtons={true} title={t("add_action")}>
<div className="space-x-3 ">
<div className="pt-1">
<DialogHeader title={t("add_action")} />
<Form
form={form}
handleSubmit={(values) => {
@ -69,10 +59,8 @@ export const AddActionDialog = (props: IAddActionDialog) => {
setIsOpenDialog(false);
setIsPhoneNumberNeeded(false);
}}>
<div className="space-y-1">
<label htmlFor="label" className="mt-5 block text-sm font-medium text-gray-700">
{t("action")}:
</label>
<div className="mt-5 space-y-1">
<Label htmlFor="label">{t("action")}:</Label>
<Controller
name="action"
control={form.control}
@ -80,7 +68,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
return (
<Select
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
className="text-sm"
defaultValue={actionOptions[0]}
onChange={(val) => {
if (val) {
@ -105,9 +93,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
</div>
{isPhoneNumberNeeded && (
<div className="mt-5 space-y-1">
<label htmlFor="sendTo" className="block text-sm font-medium text-gray-700 dark:text-white">
{t("phone_number")}
</label>
<Label htmlFor="sendTo">{t("phone_number")}</Label>
<div className="mt-1">
<PhoneInput<AddActionFormValues>
control={form.control}

View File

@ -1,5 +1,6 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { Label } from "@calcom/ui/v2";
import {
Dropdown,
DropdownMenuContent,
@ -27,9 +28,13 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => {
return (
<Dropdown>
<DropdownMenuTrigger className="text-sm text-gray-900 focus:bg-transparent focus:ring-transparent focus:ring-offset-0 ">
{t("add_variable")}
<Icon.FiChevronDown className="ml-1 h-4 w-4" />
<DropdownMenuTrigger className="px-0 py-0 focus:bg-transparent focus:ring-transparent focus:ring-offset-0 ">
<Label>
<div className="flex">
{t("add_variable")}
<Icon.FiChevronDown className="mt-[1.8px] ml-1 h-4 w-4" />
</div>
</Label>
</DropdownMenuTrigger>
<DropdownMenuContent className="h-40 overflow-scroll">
<div className="p-3">
@ -39,10 +44,10 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => {
<button
key={variable}
type="button"
className="button w-full py-2"
className="w-full py-2"
onClick={() => props.addVariable(props.isEmailSubject, t(`${variable}_workflow`))}>
<div className="md:grid md:grid-cols-2">
<div className="text-left md:col-span-1">
<div className="mr-3 text-left md:col-span-1">
{`{${t(`${variable}_workflow`).toUpperCase().replace(" ", "_")}}`}
</div>
<div className="invisible text-left text-gray-600 md:visible md:col-span-1">

View File

@ -37,7 +37,36 @@ const WorkflowListItem = (props: ItemProps) => {
const isActive = activeEventTypeIds.includes(eventType.id);
const activateEventTypeMutation = trpc.useMutation("viewer.workflows.activateEventType");
const activateEventTypeMutation = trpc.useMutation("viewer.workflows.activateEventType", {
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";
}
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");
}
},
});
const sendTo: Set<string> = new Set();
@ -59,14 +88,16 @@ const WorkflowListItem = (props: ItemProps) => {
});
return (
<div className="mb-4 flex w-full items-center rounded-md border border-gray-200 p-4">
<div className="mt-[3px] mr-5 flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 p-1 text-xs font-medium">
<div className="mb-4 flex w-full items-center overflow-hidden rounded-md border border-gray-200 p-4">
<div className="mr-5 flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 p-1 text-xs font-medium sm:flex sm:h-10 sm:w-10">
{getActionIcon(
workflow.steps,
isActive ? "h-7 w-7 stroke-[1.5px] text-gray-700" : "h-7 w-7 stroke-[1.5px] text-gray-400"
isActive
? "sm:h-7 sm:w-7 w-[17px] h-[17px] stroke-[1.5px] text-gray-700"
: "h-7 w-7 w-[25px] h-[25px] stroke-[1.5px] text-gray-400"
)}
</div>
<div className="ml-4 grow">
<div className="grow sm:ml-4">
<div
className={classNames(
"w-full truncate text-sm font-medium leading-6 text-gray-900 md:max-w-max",
@ -90,12 +121,12 @@ const WorkflowListItem = (props: ItemProps) => {
})}
</div>
</div>
<div className="flex-none sm:mr-3">
<div className="flex-none">
<Link href={`/workflows/${workflow.id}`} passHref={true}>
<a target="_blank">
<Button type="button" color="minimal" className="text-sm text-gray-900 hover:bg-transparent">
{t("edit")}
<Icon.FiExternalLink className="ml-2 -mt-[2px] h-4 w-4 stroke-2 text-gray-600" />
<div className="mr-2 hidden sm:block">{t("edit")}</div>
<Icon.FiExternalLink className="-mt-[2px] h-4 w-4 stroke-2 text-gray-600" />
</Button>
</a>
</Link>
@ -106,16 +137,6 @@ const WorkflowListItem = (props: ItemProps) => {
checked={isActive}
onCheckedChange={() => {
activateEventTypeMutation.mutate({ workflowId: workflow.id, eventTypeId: eventType.id });
if (activeEventTypeIds.includes(eventType.id)) {
const newActiveEventTypeIds = activeEventTypeIds.filter((id) => {
return id !== eventType.id;
});
setActiveEventTypeIds(newActiveEventTypeIds);
} else {
const newActiveEventTypeIds = activeEventTypeIds;
newActiveEventTypeIds.push(eventType.id);
setActiveEventTypeIds(newActiveEventTypeIds);
}
}}
/>
</div>

View File

@ -84,7 +84,7 @@ export default function WorkflowDetailsPage(props: Props) {
<div className="mb-5">
<TextField label={`${t("workflow_name")}:`} type="text" {...form.register("name")} />
</div>
<Label className="text-sm font-medium">{t("which_event_type_apply")}:</Label>
<Label>{t("which_event_type_apply")}</Label>
<Controller
name="activeOn"
control={form.control}
@ -115,7 +115,7 @@ export default function WorkflowDetailsPage(props: Props) {
</div>
{/* Workflow Trigger Event & Steps */}
<div className="w-full rounded-md border bg-gray-100 p-3 py-5 md:ml-3 md:max-h-[calc(100vh-116px)] md:overflow-scroll md:p-8">
<div className="w-full rounded-md border border-gray-200 bg-gray-50 p-3 py-5 md:ml-3 md:max-h-[calc(100vh-116px)] md:overflow-scroll md:p-8">
{form.getValues("trigger") && (
<div>
<WorkflowStepContainer form={form} />

View File

@ -138,6 +138,7 @@ export default function WorkflowListPage({ workflows }: Props) {
<Button
type="button"
color="secondary"
className="hidden sm:block"
onClick={async () => await router.replace("/workflows/" + workflow.id)}>
{t("edit")}
</Button>
@ -151,6 +152,15 @@ export default function WorkflowListPage({ workflows }: Props) {
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem className="block text-sm sm:hidden">
<Button
type="button"
color="minimal"
onClick={async () => await router.replace("/workflows/" + workflow.id)}
StartIcon={Icon.FiEdit}>
{t("edit")}
</Button>
</DropdownMenuItem>
<DropdownMenuItem>
<Button
onClick={() => {

View File

@ -115,15 +115,11 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
if (!step) {
const trigger = form.getValues("trigger");
const triggerString = t(`${trigger.toLowerCase()}_trigger`);
const timeUnit = form.getValues("timeUnit");
const selectedTrigger = {
label: triggerString.charAt(0).toUpperCase() + triggerString.slice(1),
value: trigger,
};
const selectedTimeUnit = timeUnit
? { label: t(`${timeUnit.toLowerCase()}_timeUnit`), value: timeUnit }
: undefined;
return (
<>
@ -139,7 +135,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
</div>
<div className="my-7 border-t border-gray-200" />
<Label className="block text-sm font-medium text-gray-700">{t("when")}</Label>
<Label>{t("when")}</Label>
<Controller
name="trigger"
control={form.control}
@ -147,7 +143,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
return (
<Select
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
className="text-sm"
onChange={(val) => {
if (val) {
form.setValue("trigger", val.value);
@ -169,8 +165,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}}
/>
{showTimeSection && (
<div className="mt-5 space-y-1">
<Label className="block text-sm font-medium text-gray-700">{t("how_long_before")}</Label>
<div className="mt-5">
<Label>{t("how_long_before")}</Label>
<TimeTimeUnitInput form={form} />
</div>
)}
@ -181,7 +177,13 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
}
if (step && step.action) {
const selectedAction = { label: t(`${step.action.toLowerCase()}_action`), value: step.action };
const actionString = t(`${step.action.toLowerCase()}_action`);
const selectedAction = {
label: actionString.charAt(0).toUpperCase() + actionString.slice(1),
value: step.action,
};
const selectedTemplate = { label: t(`${step.template.toLowerCase()}`), value: step.template };
return (
@ -241,7 +243,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
<div className="my-7 border-t border-gray-200" />
<div>
<Label className="block text-sm font-medium text-gray-700">{t("do_this")}</Label>
<Label>{t("do_this")}</Label>
<Controller
name={`steps.${step.stepNumber - 1}.action`}
control={form.control}
@ -249,7 +251,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
return (
<Select
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
className="text-sm"
onChange={(val) => {
if (val) {
if (val.value === WorkflowActions.SMS_NUMBER) {
@ -282,12 +284,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)}
</div>
{isPhoneNumberNeeded && (
<div className="mt-5 rounded-md bg-gray-50 p-5">
<label
htmlFor="sendTo"
className="mb-2 block text-sm font-medium text-gray-700 dark:text-white">
{t("custom_phone_number")}
</label>
<div className="mt-5 rounded-md bg-gray-50 p-4">
<Label>{t("custom_phone_number")}</Label>
<PhoneInput<FormValues>
control={form.control}
name={`steps.${step.stepNumber - 1}.sendTo`}
@ -305,9 +303,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
</div>
)}
<div className="mt-5">
<label htmlFor="label" className="mt-5 block text-sm font-medium text-gray-700">
{t("message_template")}
</label>
<Label>{t("message_template")}</Label>
<Controller
name={`steps.${step.stepNumber - 1}.template`}
control={form.control}
@ -315,7 +311,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
return (
<Select
isSearchable={false}
className="mt-3 block w-full min-w-0 flex-1 rounded-sm text-sm"
className="text-sm"
onChange={(val) => {
if (val) {
form.setValue(`steps.${step.stepNumber - 1}.template`, val.value);
@ -331,14 +327,12 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
/>
</div>
{isCustomReminderBodyNeeded && (
<div className="mt-2 rounded-md bg-gray-50 px-5 pb-5">
<div className="mt-2 rounded-md bg-gray-50 p-2 md:p-4">
{isEmailSubjectNeeded && (
<>
<div className="mb-5">
<div className="flex">
<label className="mt-5 flex-none text-sm font-medium text-gray-700 dark:text-white">
{t("email_subject")}
</label>
<div className="mt-3 -mb-1 flex-grow text-right">
<Label className="flex-none">{t("subject")}</Label>
<div className="flex-grow text-right">
<AddVariablesDropdown addVariable={addVariable} isEmailSubject={true} />
</div>
</div>
@ -347,6 +341,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
emailSubjectFormRef?.(e);
refEmailSubject.current = e;
}}
className="my-0"
required
{...restEmailSubjectForm}
/>
@ -356,13 +351,13 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
{form.formState?.errors?.steps[step.stepNumber - 1]?.emailSubject?.message || ""}
</p>
)}
</>
</div>
)}
<div className="flex">
<label className="mt-5 flex-none text-sm font-medium text-gray-700 dark:text-white">
<Label className="flex-none">
{isEmailSubjectNeeded ? t("email_body") : t("text_message")}
</label>
<div className="mt-3 -mb-1 flex-grow text-right">
</Label>
<div className="flex-grow text-right">
<AddVariablesDropdown addVariable={addVariable} isEmailSubject={false} />
</div>
</div>
@ -371,7 +366,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
reminderBodyFormRef?.(e);
refReminderBody.current = e;
}}
className="h-24"
className="my-0 h-24"
required
{...restReminderBodyForm}
/>
@ -383,9 +378,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
)}
<div className="mt-3 ">
<button type="button" onClick={() => setIsAdditionalInputsDialogOpen(true)}>
<div className="mt-2 flex items-center text-sm text-gray-600">
<Icon.FiHelpCircle className="mr-2 h-3 w-3" />
<p>{t("using_additional_inputs_as_variables")}</p>
<div className="mt-2 flex text-sm text-gray-600">
<Icon.FiHelpCircle className="mt-[3px] mr-2 h-3 w-3" />
<p className="text-left">{t("using_additional_inputs_as_variables")}</p>
</div>
</button>
</div>

View File

@ -4,7 +4,9 @@ import { TIME_UNIT, WORKFLOW_ACTIONS, WORKFLOW_TEMPLATES, WORKFLOW_TRIGGER_EVENT
export function getWorkflowActionOptions(t: TFunction) {
return WORKFLOW_ACTIONS.map((action) => {
return { label: t(`${action.toLowerCase()}_action`), value: action };
const actionString = t(`${action.toLowerCase()}_action`);
return { label: actionString.charAt(0).toUpperCase() + actionString.slice(1), value: action };
});
}

View File

@ -43,7 +43,7 @@ function WorkflowsPage() {
return session.data ? (
<Shell
heading={data?.workflows.length ? "workflows" : ""}
heading={data?.workflows.length ? t("workflows") : ""}
subtitle={data?.workflows.length ? t("workflows_to_automate_notifications") : ""}
CTA={
session.data?.hasValidLicense && !isFreeUser && data?.workflows && data?.workflows.length > 0 ? (
@ -51,7 +51,7 @@ function WorkflowsPage() {
StartIcon={Icon.FiPlus}
onClick={() => createMutation.mutate()}
loading={createMutation.isLoading}>
{t("new_workflow_btn")}
{t("new")}
</Button>
) : (
<></>

View File

@ -187,7 +187,7 @@ function WorkflowPage() {
});
}}>
<Shell
title="Title"
title={workflow && workflow.name ? workflow.name : "Untitled"}
CTA={
!isFreeUser && (
<div>

View File

@ -34,7 +34,7 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
portalled={props.portalled}
align={align}
{...props}
className="w-50 relative z-10 mt-1 -ml-0 origin-top-right bg-white text-sm ring-1 ring-black ring-opacity-5 focus:outline-none"
className="w-50 relative z-10 mt-1 -ml-0 origin-top-right rounded-md bg-white text-sm ring-1 ring-black ring-opacity-5 focus:outline-none"
ref={forwardedRef}>
{children}
</DropdownMenuPrimitive.Content>