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:
co-authored by
Keith Williams
Udit Takkar
Lauris Skraucis
parent
cd888be61e
commit
33d7da88bf
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user