diff --git a/apps/web/components/v2/eventtype/EventTypeSingleLayout.tsx b/apps/web/components/v2/eventtype/EventTypeSingleLayout.tsx index abb81cf93d..1859b2d93f 100644 --- a/apps/web/components/v2/eventtype/EventTypeSingleLayout.tsx +++ b/apps/web/components/v2/eventtype/EventTypeSingleLayout.tsx @@ -38,6 +38,7 @@ type Props = { team: EventTypeSetupInfered["team"]; disableBorder?: boolean; enabledAppsNumber: number; + enabledWorkflowsNumber: number; formMethods: UseFormReturn; }; @@ -48,6 +49,7 @@ function EventTypeSingleLayout({ team, disableBorder, enabledAppsNumber, + enabledWorkflowsNumber, formMethods, }: Props) { const utils = trpc.useContext(); @@ -113,13 +115,12 @@ function EventTypeSingleLayout({ icon: Icon.FiGrid, info: `${enabledAppsNumber} Active`, }, - // TODO: After V2 workflow page has been completed - // { - // name: "workflows", - // tabName: "workflows", - // icon: Icon.FiCloudLightning, - // info: `X Active`, - // }, + { + name: "workflows", + tabName: "workflows", + icon: Icon.FiZap, + info: `${enabledWorkflowsNumber} Active`, + }, ] as VerticalTabItemProps[]; // If there is a team put this navigation item within the tabs @@ -155,7 +156,7 @@ function EventTypeSingleLayout({ heading={eventType.title} subtitle={eventType.description || ""} CTA={ -
+

{t("hide_from_profile")}

) => { hasGiphyIntegration={props.hasGiphyIntegration} /> ), + workflows: ( + workflowOnEventType.workflow)} + /> + ), } as const; return (
) => { }} className="space-y-6"> {tabMap[tabName]} + {tabName !== "workflows" && ( +
+ + +
+ )}
); @@ -349,6 +367,25 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => currency: true, destinationCalendar: true, seatsPerTimeSlot: true, + workflows: { + include: { + workflow: { + include: { + activeOn: { + select: { + eventType: { + select: { + id: true, + title: true, + }, + }, + }, + }, + steps: true, + }, + }, + }, + }, }, }); diff --git a/apps/web/pages/v2/workflows/[workflow].tsx b/apps/web/pages/v2/workflows/[workflow].tsx new file mode 100644 index 0000000000..44d7bdeae3 --- /dev/null +++ b/apps/web/pages/v2/workflows/[workflow].tsx @@ -0,0 +1,28 @@ +import { GetStaticPaths, GetStaticProps } from "next"; +import { z } from "zod"; + +export { default } from "@calcom/features/ee/workflows/pages/v2/workflow"; + +const querySchema = z.object({ + workflow: z.string(), +}); + +export const getStaticProps: GetStaticProps = (ctx) => { + const params = querySchema.safeParse(ctx.params); + console.log("Built workflow page:", params); + if (!params.success) return { notFound: true }; + + return { + props: { + workflow: params.data.workflow, + }, + revalidate: 10, // seconds + }; +}; + +export const getStaticPaths: GetStaticPaths = () => { + return { + paths: [], + fallback: "blocking", + }; +}; diff --git a/apps/web/pages/v2/workflows/index.tsx b/apps/web/pages/v2/workflows/index.tsx new file mode 100644 index 0000000000..740b209b29 --- /dev/null +++ b/apps/web/pages/v2/workflows/index.tsx @@ -0,0 +1 @@ +export { default } from "@calcom/features/ee/workflows/pages/v2/index"; diff --git a/apps/web/pages/workflows/[workflow].tsx b/apps/web/pages/workflows/[workflow].tsx index 02cf9fa15b..8bd2c2363f 100644 --- a/apps/web/pages/workflows/[workflow].tsx +++ b/apps/web/pages/workflows/[workflow].tsx @@ -1,3 +1,8 @@ +/** + * @deprecated file + * All new changes should be made to the V2 file in + * `apps/web/pages/v2/workflows/[workflow].tsx` + */ import { GetStaticPaths, GetStaticProps } from "next"; import { z } from "zod"; diff --git a/apps/web/public/static/locales/de/common.json b/apps/web/public/static/locales/de/common.json index d5de56f999..bae68c2395 100644 --- a/apps/web/public/static/locales/de/common.json +++ b/apps/web/public/static/locales/de/common.json @@ -969,8 +969,8 @@ "web_conference": "Webkonferenz", "number_for_sms_reminders": "Telefonnummer (für SMS-Erinnerungen)", "requires_confirmation": "Erfordert Bestätigung", - "nr_event_type_one": "{{count}} | Termin-Typ", - "nr_event_type_other": "{{count}} | Ereignis-Typen", + "nr_event_type_one": "{{count}} Ereignistyp", + "nr_event_type_other": "{{count}} Ereignistypen", "add_action": "Aktion hinzufügen", "set_whereby_link": "Whereby Link festlegen", "invalid_whereby_link": "Bitte geben Sie einen gültigen Whereby Link ein", @@ -1010,8 +1010,8 @@ "new_seat_title": "Jemand hat sich zu einem Ereignis hinzugefügt", "variable": "Variable", "event_name_workflow": "Event Name", - "organizer_name_workflow": "Organisator Name", - "attendee_name_workflow": "Teilnehmer Name", + "organizer_name_workflow": "Organisator", + "attendee_name_workflow": "Teilnehmer", "event_date_workflow": "Event Datum", "event_time_workflow": "Event Zeit", "location_workflow": "Ort", diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 6d5698d71b..dbbbef9ddf 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -941,7 +941,7 @@ "sms_attendee_action": "send SMS to attendee", "sms_number_action": "send SMS to a specific number", "workflows": "Workflows", - "new_workflow_btn": "New workflow", + "new_workflow_btn": "New Workflow", "add_new_workflow": "Add a new workflow", "trigger": "Trigger", "triggers": "Triggers", @@ -955,9 +955,9 @@ "confirm_delete_workflow": "Yes, delete workflow", "workflow_deleted_successfully": "Workflow deleted successfully", "how_long_before": "How long before event starts?", - "day_timeUnit": "Days", - "hour_timeUnit": "Hours", - "minute_timeUnit": "Minutes", + "day_timeUnit": "days", + "hour_timeUnit": "hours", + "minute_timeUnit": "mins", "new_workflow_heading": "Create your first workflow", "new_workflow_description": "Workflows enable you to automate sending reminders and notifications.", "active_on": "Active on", @@ -978,8 +978,8 @@ "web_conference": "Web conference", "number_for_sms_reminders": "Phone number (for SMS reminders)", "requires_confirmation": "Requires confirmation", - "nr_event_type_one": "{{count}} Event Type", - "nr_event_type_other": "{{count}} Event Types", + "nr_event_type_one": "{{count}} event type", + "nr_event_type_other": "{{count}} event types", "add_action": "Add action", "set_whereby_link": "Set Whereby link", "invalid_whereby_link": "Please enter a valid Whereby Link", @@ -991,7 +991,7 @@ "add_exchange2013": "Connect Exchange 2013 Server", "add_exchange2016": "Connect Exchange 2016 Server", "custom_template": "Custom template", - "email_body": "Body", + "email_body": "Email body", "subject": "Subject", "text_message": "Text message", "specific_issue": "Have a specific issue?", @@ -1019,8 +1019,8 @@ "new_seat_title": "Someone has added themselves to an event", "variable": "Variable", "event_name_workflow": "Event name", - "organizer_name_workflow": "Organizer name", - "attendee_name_workflow": "Attendee name", + "organizer_name_workflow": "Organizer", + "attendee_name_workflow": "Attendee", "event_date_workflow": "Event date", "event_time_workflow": "Event time", "location_workflow": "Location", @@ -1038,13 +1038,13 @@ "current_username": "Current username", "example_1": "Example 1", "example_2": "Example 2", - "additional_input_label": "Additional input label", + "additional_input_label": "Additional Input Label", "company_size": "Company size", "what_help_needed": "What do you need help with?", "variable_format": "Variable format", "webhook_subscriber_url_reserved": "Webhook subscriber url is already defined", "custom_input_as_variable_info": "Ignore all special characters of the additional input label (use only letters and numbers), use uppercase for all letters and replace whitespaces with underscores.", - "using_additional_inputs_as_variables": "How to use additional inputs as variables?", + "using_additional_inputs_as_variables": "How do I use Additional Inputs as variables?", "download_desktop_app": "Download desktop app", "set_ping_link": "Set Ping link", "rate_limit_exceeded": "Rate limit exceeded", @@ -1068,6 +1068,11 @@ "select_which_cal":"Select which calendar to add bookings to", "custom_event_name":"Custom event name", "custom_event_name_description":"Create customised event names to display on calendar event", + "which_event_type_apply": "Which event type will this apply to?", + "no_workflows_description": "Workflows enable simple automation to send notifications & reminders enabling you to build processes around your events.", + "create_workflow": "Create a workflow", + "do_this": "Do this", + "turn_off": "Turn off", "settings_updated_successfully": "Settings updated successfully", "error_updating_settings":"Error updating settings", "personal_cal_url": "My personal Cal URL", diff --git a/packages/features/ee/common/components/LicenseRequired.tsx b/packages/features/ee/common/components/LicenseRequired.tsx index 7ed265a930..1ab43ce8a3 100644 --- a/packages/features/ee/common/components/LicenseRequired.tsx +++ b/packages/features/ee/common/components/LicenseRequired.tsx @@ -1,3 +1,8 @@ +/** + * @deprecated file + * All new changes should be made to the V2 file in + * `/packages/features/ee/common/components/v2/LicenseRequired.tsx` + */ import { useSession } from "next-auth/react"; import React, { AriaRole, ComponentType, Fragment } from "react"; @@ -13,6 +18,9 @@ type LicenseRequiredProps = { }; /** + * @deprecated file + * All new changes should be made to the V2 file in + * `/packages/features/ee/common/components/v2/LicenseRequired.tsx` * This component will only render it's children if the installation has a valid * license. */ diff --git a/packages/features/ee/common/components/v2/LicenseRequired.tsx b/packages/features/ee/common/components/v2/LicenseRequired.tsx new file mode 100644 index 0000000000..a453de04ea --- /dev/null +++ b/packages/features/ee/common/components/v2/LicenseRequired.tsx @@ -0,0 +1,60 @@ +import { useSession } from "next-auth/react"; +import React, { AriaRole, ComponentType, Fragment } from "react"; + +import { CONSOLE_URL } from "@calcom/lib/constants"; +import { Icon } from "@calcom/ui/Icon"; +import { EmptyScreen } from "@calcom/ui/v2"; + +type LicenseRequiredProps = { + as?: keyof JSX.IntrinsicElements | ""; + className?: string; + role?: AriaRole | undefined; + children: React.ReactNode; +}; + +/** + * This component will only render it's children if the installation has a valid + * license. + */ +const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) => { + const session = useSession(); + const Component = as || Fragment; + return ( + + {session.data?.hasValidLicense ? ( + children + ) : ( + + To enable this feature, get a deployment key at{" "} + + Cal.com console + {" "} + and add it to your .env as CALCOM_LICENSE_KEY. If your team already has a license, + please contact{" "} + + peer@cal.com + {" "} + for help. + + } + /> + )} + + ); +}; + +export const withLicenseRequired = + (Component: ComponentType) => + // eslint-disable-next-line react/display-name + (hocProps: T) => + ( + + + + ); + +export default LicenseRequired; diff --git a/packages/features/ee/workflows/components/AddActionDialog.tsx b/packages/features/ee/workflows/components/AddActionDialog.tsx index ef184cf0b4..6ba545eef2 100644 --- a/packages/features/ee/workflows/components/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/AddActionDialog.tsx @@ -1,3 +1,8 @@ +/** + * @deprecated file + * All new changes should be made to the V2 file in + * `/packages/features/ee/workflows/components/v2/AddActionDialog.tsx` + */ import { zodResolver } from "@hookform/resolvers/zod"; import { WorkflowActions } from "@prisma/client"; import { isValidPhoneNumber } from "libphonenumber-js"; @@ -26,6 +31,11 @@ type AddActionFormValues = { sendTo?: string; }; +/** + * @deprecated file + * All new changes should be made to the V2 file in + * `/packages/features/ee/workflows/components/v2/AddActionDialog.tsx` + */ export const AddActionDialog = (props: IAddActionDialog) => { const { t } = useLocale(); const { isOpenDialog, setIsOpenDialog, addAction } = props; diff --git a/packages/features/ee/workflows/components/AddVariablesDropdown.tsx b/packages/features/ee/workflows/components/AddVariablesDropdown.tsx index 0866becfe9..3200529a43 100644 --- a/packages/features/ee/workflows/components/AddVariablesDropdown.tsx +++ b/packages/features/ee/workflows/components/AddVariablesDropdown.tsx @@ -1,3 +1,8 @@ +/** + * @deprecated file + * All new changes should be made to the V2 file in + * `/packages/features/ee/workflows/components/v2/AddVariablesDropdown.tsx` + */ import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Dropdown, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown"; @@ -18,6 +23,11 @@ const variables = [ "additional_notes", ]; +/** + * @deprecated file + * All new changes should be made to the V2 file in + * `/packages/features/ee/workflows/components/v2/AddVariablesDropdown.tsx` + */ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => { const { t } = useLocale(); @@ -38,7 +48,7 @@ export const AddVariablesDropdown = (props: IAddVariablesDropdown) => { key={variable} type="button" className="px-5 py-1" - onClick={() => props.addVariable(props.isEmailSubject, variable)}> + onClick={() => props.addVariable(props.isEmailSubject, t(`${variable}_workflow`))}> {t(`${variable}_workflow`)} diff --git a/packages/features/ee/workflows/components/WorkflowListPage.tsx b/packages/features/ee/workflows/components/WorkflowListPage.tsx index e5a1a75170..8b9d791da3 100644 --- a/packages/features/ee/workflows/components/WorkflowListPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowListPage.tsx @@ -4,7 +4,6 @@ import { useState } from "react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import showToast from "@calcom/lib/notification"; -import { EventType, Workflow, WorkflowsOnEventTypes } from "@calcom/prisma/client"; import { trpc } from "@calcom/trpc/react"; import { Button, Tooltip } from "@calcom/ui"; import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent"; @@ -13,6 +12,8 @@ import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } import EmptyScreen from "@calcom/ui/EmptyScreen"; import { Icon } from "@calcom/ui/Icon"; +import { WorkflowType } from "./v2/WorkflowListPage"; + const CreateFirstWorkflowView = () => { const { t } = useLocale(); @@ -26,11 +27,7 @@ const CreateFirstWorkflowView = () => { }; interface Props { - workflows: - | (Workflow & { - activeOn: (WorkflowsOnEventTypes & { eventType: EventType })[]; - })[] - | undefined; + workflows: WorkflowType[] | undefined; } export default function WorkflowListPage({ workflows }: Props) { const { t } = useLocale(); diff --git a/packages/features/ee/workflows/components/v2/AddActionDialog.tsx b/packages/features/ee/workflows/components/v2/AddActionDialog.tsx new file mode 100644 index 0000000000..10045e3e47 --- /dev/null +++ b/packages/features/ee/workflows/components/v2/AddActionDialog.tsx @@ -0,0 +1,147 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowActions } from "@prisma/client"; +import { isValidPhoneNumber } from "libphonenumber-js"; +import { Dispatch, SetStateAction, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +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 { WORKFLOW_ACTIONS } from "../../lib/constants"; +import { getWorkflowActionOptions } from "../../lib/getOptions"; + +interface IAddActionDialog { + isOpenDialog: boolean; + setIsOpenDialog: Dispatch>; + addAction: (action: WorkflowActions, sendTo?: string) => void; +} + +type AddActionFormValues = { + action: WorkflowActions; + sendTo?: string; +}; + +export const AddActionDialog = (props: IAddActionDialog) => { + const { t } = useLocale(); + const { isOpenDialog, setIsOpenDialog, addAction } = props; + const [isPhoneNumberNeeded, setIsPhoneNumberNeeded] = useState(false); + const actionOptions = getWorkflowActionOptions(t); + + const formSchema = z.object({ + action: z.enum(WORKFLOW_ACTIONS), + sendTo: z + .string() + .refine((val) => isValidPhoneNumber(val)) + .optional(), + }); + + const form = useForm({ + mode: "onSubmit", + defaultValues: { + action: WorkflowActions.EMAIL_HOST, + }, + resolver: zodResolver(formSchema), + }); + + return ( + + +
+
+ +
{ + addAction(values.action, values.sendTo); + form.unregister("sendTo"); + form.unregister("action"); + setIsOpenDialog(false); + setIsPhoneNumberNeeded(false); + }}> +
+ + { + return ( + { + if (val) { + form.setValue("trigger", val.value); + if (val.value === WorkflowTriggerEvents.BEFORE_EVENT) { + setShowTimeSection(true); + form.setValue("time", 24); + form.setValue("timeUnit", TimeUnit.HOUR); + } else { + setShowTimeSection(false); + form.unregister("time"); + form.unregister("timeUnit"); + } + } + }} + defaultValue={selectedTrigger} + options={triggerOptions} + /> + ); + }} + /> + {showTimeSection && ( +
+ + +
+ )} +
+
+ + ); + } + + if (step && step.action) { + const selectedAction = { label: t(`${step.action.toLowerCase()}_action`), value: step.action }; + const selectedTemplate = { label: t(`${step.template.toLowerCase()}`), value: step.template }; + + return ( + <> +
+ +
+
+
+
+
+
+
+
+ {step.stepNumber + 1} +
+
+
{t("action")}
+
{t("action_is_performed")}
+
+
+
+
+ + + + + + +
+
+
+
+ + { + return ( + { + if (val) { + form.setValue(`steps.${step.stepNumber - 1}.template`, val.value); + const isCustomTemplate = val.value === WorkflowTemplates.CUSTOM; + setIsCustomReminderBodyNeeded(isCustomTemplate); + } + }} + defaultValue={selectedTemplate} + options={templateOptions} + /> + ); + }} + /> +
+ {isCustomReminderBodyNeeded && ( +
+ {isEmailSubjectNeeded && ( + <> +
+ +
+ +
+
+