From 33d7da88bfda9375c17c4302a0c27b9f64a15d5d Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:57:07 +0530 Subject: [PATCH] feat: go to Assignment tab after creation and warn if existing without assigning (#14882) * feat: go to Assignment tab after creation and warn if existing without assigning * Update AssignmentWarningDialog.tsx * fix: some changes * fix: some updates * Update EventTeamTab.tsx * update * update * update test * some fixes * refactor one test and update * Update managed-event-types.e2e.ts --------- Co-authored-by: Keith Williams Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Lauris Skraucis --- .../eventtype/AssignmentWarningDialog.tsx | 56 +++++++++++++++++++ .../views/event-types-single-view.tsx | 45 ++++++++++++++- .../web/playwright/managed-event-types.e2e.ts | 1 + apps/web/public/static/locales/en/common.json | 6 ++ .../components/CreateEventTypeDialog.tsx | 2 +- 5 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 apps/web/components/eventtype/AssignmentWarningDialog.tsx diff --git a/apps/web/components/eventtype/AssignmentWarningDialog.tsx b/apps/web/components/eventtype/AssignmentWarningDialog.tsx new file mode 100644 index 0000000000..292018d220 --- /dev/null +++ b/apps/web/components/eventtype/AssignmentWarningDialog.tsx @@ -0,0 +1,56 @@ +import { useRouter } from "next/navigation"; +import type { Dispatch, SetStateAction } from "react"; +import type { MutableRefObject } from "react"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Dialog, DialogContent, Button, DialogFooter } from "@calcom/ui"; + +interface AssignmentWarningDialogProps { + isOpenAssignmentWarnDialog: boolean; + setIsOpenAssignmentWarnDialog: Dispatch>; + pendingRoute: string; + leaveWithoutAssigningHosts: MutableRefObject; + id: number; +} +const AssignmentWarningDialog = (props: AssignmentWarningDialogProps) => { + const { t } = useLocale(); + const { + isOpenAssignmentWarnDialog, + setIsOpenAssignmentWarnDialog, + pendingRoute, + leaveWithoutAssigningHosts, + id, + } = props; + const router = useRouter(); + return ( + + +
+

{t("leave_without_adding_attendees")}

+

{t("no_availability_shown_to_bookers")}

+
+ + + + +
+
+ ); +}; +export default AssignmentWarningDialog; diff --git a/apps/web/modules/event-types/views/event-types-single-view.tsx b/apps/web/modules/event-types/views/event-types-single-view.tsx index 8a407c3668..1aa0602fbd 100644 --- a/apps/web/modules/event-types/views/event-types-single-view.tsx +++ b/apps/web/modules/event-types/views/event-types-single-view.tsx @@ -5,7 +5,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { zodResolver } from "@hookform/resolvers/zod"; import { isValidPhoneNumber } from "libphonenumber-js"; import dynamic from "next/dynamic"; -import { useEffect, useMemo, useState } from "react"; +// eslint-disable-next-line @calcom/eslint/deprecated-imports-next-router +import { useRouter } from "next/router"; +import { useEffect, useMemo, useState, useRef } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -113,6 +115,8 @@ const EventAITab = dynamic(() => import("@components/eventtype/EventAITab").then const ManagedEventTypeDialog = dynamic(() => import("@components/eventtype/ManagedEventDialog")); +const AssignmentWarningDialog = dynamic(() => import("@components/eventtype/AssignmentWarningDialog")); + export type Host = { isFixed: boolean; userId: number; priority: number }; export type CustomInputParsed = typeof customInputSchema._output; @@ -154,6 +158,9 @@ const EventTypePage = (props: EventTypeSetupProps) => { }); const { eventType, locationOptions, team, teamMembers, currentUserMembership, destinationCalendar } = props; + const [isOpenAssignmentWarnDialog, setIsOpenAssignmentWarnDialog] = useState(false); + const [pendingRoute, setPendingRoute] = useState(""); + const leaveWithoutAssigningHosts = useRef(false); const [animationParentRef] = useAutoAnimate(); const updateMutation = trpc.viewer.eventTypes.update.useMutation({ onSuccess: async () => { @@ -196,6 +203,32 @@ const EventTypePage = (props: EventTypeSetupProps) => { }, }); + const router = useRouter(); + + useEffect(() => { + const handleRouteChange = (url: string) => { + const paths = url.split("/"); + if ( + !!team && + eventType.hosts.length === 0 && + !leaveWithoutAssigningHosts.current && + (url === "/event-types" || paths[1] !== "event-types") + ) { + setIsOpenAssignmentWarnDialog(true); + setPendingRoute(url); + router.events.emit( + "routeChangeError", + new Error(`Aborted route change to ${url} because none was assigned to team event`) + ); + throw "Aborted"; + } + }; + router.events.on("routeChangeStart", handleRouteChange); + return () => { + router.events.off("routeChangeStart", handleRouteChange); + }; + }, [router]); + const [periodDates] = useState<{ startDate: Date; endDate: Date }>({ startDate: new Date(eventType.periodStartDate || Date.now()), endDate: new Date(eventType.periodEndDate || Date.now()), @@ -774,7 +807,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
{tabMap[tabName]}
- {slugExistsChildrenDialogOpen.length ? ( { }} /> ) : null} + + ; ); }; - const EventTypePageWrapper: React.FC & { PageWrapper?: AppProps["Component"]["PageWrapper"]; getLayout?: AppProps["Component"]["getLayout"]; diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts index 317a39248e..c77a552f70 100644 --- a/apps/web/playwright/managed-event-types.e2e.ts +++ b/apps/web/playwright/managed-event-types.e2e.ts @@ -57,6 +57,7 @@ test.describe("Managed Event Types", () => { }); await test.step("Managed event type has unlocked fields for admin", async () => { + await page.getByTestId("vertical-tab-event_setup_tab_title").click(); await page.getByTestId("update-eventtype").waitFor(); await expect(page.locator('input[name="title"]')).toBeEditable(); await expect(page.locator('input[name="slug"]')).toBeEditable(); diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 7ffcf9f90f..5ac48d34e0 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2461,6 +2461,12 @@ "lowest_rated_members": "Members with lowest rated meetings", "csat_score": "CSAT Score", "lockedSMS": "Locked SMS", + "signing_up_terms": "By proceeding, you agree to our <1>Terms and <2>Privacy Policy.", + "leave_without_assigning_anyone": "Leave without assigning anyone?", + "leave_without_adding_attendees": "Are you sure want to leave this event without adding attendees?", + "no_availability_shown_to_bookers": "If you don't assign anyone for this event, there will be no availability shown to bookers.", + "go_back_and_assign": "Go back and Assign", + "leave_without_assigning": "Leave without Assigning", "signing_up_terms": "By proceeding, you agree to our <0>Terms and <1>Privacy Policy.", "always_show_x_days": "Always {{x}} days available", "unable_to_subscribe_to_the_platform": "An error occurred while trying to subscribe to the platform plan, please try again later", diff --git a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx index 0204168390..4bc0c79f0a 100644 --- a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx +++ b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx @@ -119,7 +119,7 @@ export default function CreateEventTypeDialog({ const createMutation = trpc.viewer.eventTypes.create.useMutation({ onSuccess: async ({ eventType }) => { await utils.viewer.eventTypes.getByViewer.invalidate(); - await router.replace(`/event-types/${eventType.id}`); + await router.replace(`/event-types/${eventType.id}${teamId ? "?tabName=team" : ""}`); showToast( t("event_type_created_successfully", { eventTypeTitle: eventType.title,