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 <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
This commit is contained in:
Anik Dhabal Babu
2024-06-11 09:27:07 +00:00
committed by GitHub
co-authored by Keith Williams Udit Takkar Lauris Skraucis
parent cd888be61e
commit 33d7da88bf
5 changed files with 106 additions and 4 deletions
@@ -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<SetStateAction<boolean>>;
pendingRoute: string;
leaveWithoutAssigningHosts: MutableRefObject<boolean>;
id: number;
}
const AssignmentWarningDialog = (props: AssignmentWarningDialogProps) => {
const { t } = useLocale();
const {
isOpenAssignmentWarnDialog,
setIsOpenAssignmentWarnDialog,
pendingRoute,
leaveWithoutAssigningHosts,
id,
} = props;
const router = useRouter();
return (
<Dialog open={isOpenAssignmentWarnDialog} onOpenChange={setIsOpenAssignmentWarnDialog}>
<DialogContent title={t("leave_without_assigning_anyone")} Icon="circle-alert" type="confirmation">
<div className="text-subtle text-sm">
<p className="mt-3">{t("leave_without_adding_attendees")}</p>
<p className="mt-4">{t("no_availability_shown_to_bookers")}</p>
</div>
<DialogFooter className="mt-6">
<Button
onClick={(e) => {
e.preventDefault();
setIsOpenAssignmentWarnDialog(false);
router.replace(`/event-types/${id}?tabName=team`);
}}
color="minimal">
{t("go_back_and_assign")}
</Button>
<Button
onClick={(e) => {
e.preventDefault();
setIsOpenAssignmentWarnDialog(false);
leaveWithoutAssigningHosts.current = true;
router.replace(pendingRoute);
}}>
{t("leave_without_assigning")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export default AssignmentWarningDialog;
@@ -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<boolean>(false);
const [pendingRoute, setPendingRoute] = useState("");
const leaveWithoutAssigningHosts = useRef(false);
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
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) => {
<div ref={animationParentRef}>{tabMap[tabName]}</div>
</Form>
</EventTypeSingleLayout>
{slugExistsChildrenDialogOpen.length ? (
<ManagedEventTypeDialog
slugExistsChildrenDialogOpen={slugExistsChildrenDialogOpen}
@@ -791,10 +823,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}}
/>
) : null}
<AssignmentWarningDialog
isOpenAssignmentWarnDialog={isOpenAssignmentWarnDialog}
setIsOpenAssignmentWarnDialog={setIsOpenAssignmentWarnDialog}
pendingRoute={pendingRoute}
leaveWithoutAssigningHosts={leaveWithoutAssigningHosts}
id={eventType.id}
/>
;
</>
);
};
const EventTypePageWrapper: React.FC<PageProps> & {
PageWrapper?: AppProps["Component"]["PageWrapper"];
getLayout?: AppProps["Component"]["getLayout"];
@@ -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();
@@ -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</1> and <2>Privacy Policy</2>.",
"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</0> and <1>Privacy Policy</1>.",
"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",
@@ -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,