From e225d8665473377754a16a6351bf55daa141564d Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Thu, 1 Jun 2023 11:40:04 -0400 Subject: [PATCH] EVG-16959: Reintroduce navigation warning modal (#1851) --- .../projectSettings/project_settings.ts | 5 +- src/pages/projectSettings/Context.tsx | 26 +++++++- src/pages/projectSettings/NavigationModal.tsx | 59 +++++++++++++++++++ src/pages/projectSettings/Tabs.tsx | 2 + 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/pages/projectSettings/NavigationModal.tsx diff --git a/cypress/integration/projectSettings/project_settings.ts b/cypress/integration/projectSettings/project_settings.ts index 5a2b59b2ff..4b03c7d0d9 100644 --- a/cypress/integration/projectSettings/project_settings.ts +++ b/cypress/integration/projectSettings/project_settings.ts @@ -603,9 +603,12 @@ describe( cy.dataCy("display-name-input").should("not.have.attr", "placeholder"); }); - it.skip("Shows a navigation warning modal when navigating away from project settings", () => { + it("Shows a navigation warning modal that lists the general page when navigating away from project settings", () => { cy.contains("My Patches").click(); cy.dataCy("navigation-warning-modal").should("be.visible"); + cy.dataCy("unsaved-pages").within(() => { + cy.get("li").should("have.length", 1); + }); cy.get("body").type("{esc}"); }); diff --git a/src/pages/projectSettings/Context.tsx b/src/pages/projectSettings/Context.tsx index d8fdf51527..c43377600f 100644 --- a/src/pages/projectSettings/Context.tsx +++ b/src/pages/projectSettings/Context.tsx @@ -203,6 +203,25 @@ const usePopulateForm = ( }, [formData]); // eslint-disable-line react-hooks/exhaustive-deps }; +const useHasUnsavedTab = (): { + hasUnsaved: boolean; + unsavedTabs: ProjectSettingsTabRoutes[]; +} => { + const { tabs } = useProjectSettingsContext(); + const unsavedTabs = useMemo( + () => + Object.entries(tabs) + .filter(([, tabData]) => tabData.hasChanges) + .map(([tab]) => tab as ProjectSettingsTabRoutes), + [tabs] + ); + + return { + unsavedTabs, + hasUnsaved: !!unsavedTabs.length, + }; +}; + const getDefaultTabState = ( defaultValue: T ): Record => @@ -213,4 +232,9 @@ const getDefaultTabState = ( })) ); -export { ProjectSettingsProvider, usePopulateForm, useProjectSettingsContext }; +export { + ProjectSettingsProvider, + useHasUnsavedTab, + usePopulateForm, + useProjectSettingsContext, +}; diff --git a/src/pages/projectSettings/NavigationModal.tsx b/src/pages/projectSettings/NavigationModal.tsx new file mode 100644 index 0000000000..3480ada29f --- /dev/null +++ b/src/pages/projectSettings/NavigationModal.tsx @@ -0,0 +1,59 @@ +import { Body } from "@leafygreen-ui/typography"; +import { + matchPath, + unstable_useBlocker as useBlocker, + useParams, +} from "react-router-dom"; +import { ConfirmationModal } from "components/ConfirmationModal"; +import { getProjectSettingsRoute, routes } from "constants/routes"; +import { useHasUnsavedTab } from "./Context"; +import { getTabTitle } from "./getTabTitle"; + +export const NavigationModal: React.VFC = () => { + const { hasUnsaved, unsavedTabs } = useHasUnsavedTab(); + const { projectIdentifier } = useParams(); + + const shouldConfirmNavigation = ({ nextLocation }): boolean => { + const isProjectSettingsRoute = + nextLocation && + !!matchPath(`${routes.projectSettings}/*`, nextLocation.pathname); + if (!isProjectSettingsRoute) { + return hasUnsaved; + } + + /* Identify if the user is navigating to a new project's settings via project select dropdown */ + const currentProjectRoute = getProjectSettingsRoute(projectIdentifier); + const isNewProjectSettingsRoute = !matchPath( + `${currentProjectRoute}/*`, + nextLocation.pathname + ); + if (isNewProjectSettingsRoute) { + return hasUnsaved; + } + + return false; + }; + + const blocker = useBlocker(shouldConfirmNavigation); + + return ( + blocker.state === "blocked" && ( + blocker.reset?.()} + onConfirm={() => blocker.proceed?.()} + title="You have unsaved changes that will be discarded. Are you sure you want to leave?" + variant="danger" + > + Unsaved changes are present on the following pages: +
    + {unsavedTabs.map((tab) => ( +
  1. {getTabTitle(tab).title}
  2. + ))} +
+
+ ) + ); +}; diff --git a/src/pages/projectSettings/Tabs.tsx b/src/pages/projectSettings/Tabs.tsx index f150887087..719e363c6f 100644 --- a/src/pages/projectSettings/Tabs.tsx +++ b/src/pages/projectSettings/Tabs.tsx @@ -6,6 +6,7 @@ import { ProjectSettingsQuery, RepoSettingsQuery } from "gql/generated/types"; import { isProduction } from "utils/environmentVariables"; import { useProjectSettingsContext } from "./Context"; import { Header } from "./Header"; +import { NavigationModal } from "./NavigationModal"; import { AccessTab, ContainersTab, @@ -57,6 +58,7 @@ export const ProjectSettingsTabs: React.VFC = ({ return ( +