From 39e47b23351a3a427daac3a2d7ead1bb8fcde7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 4 Jul 2024 18:50:28 +0100 Subject: [PATCH] feat: Single `EditorNavMenu` instance with wired up routing (#3370) --- .../src/components/EditorNavMenu.tsx | 164 ++++++++++-------- .../FlowEditor/components/Settings/index.tsx | 10 +- .../components/Team/TeamMembers.tsx | 57 +++--- editor.planx.uk/src/pages/GlobalSettings.tsx | 7 +- .../src/pages/PlatformAdminPanel.tsx | 29 ++-- editor.planx.uk/src/pages/Team.tsx | 107 ++++++------ editor.planx.uk/src/pages/Teams.tsx | 47 +++-- .../src/pages/layout/AuthenticatedLayout.tsx | 32 +++- .../src/pages/layout/FlowEditorLayout.tsx | 7 +- editor.planx.uk/src/routes/team.tsx | 30 ++-- editor.planx.uk/src/ui/editor/Dashboard.tsx | 34 ---- 11 files changed, 251 insertions(+), 273 deletions(-) delete mode 100644 editor.planx.uk/src/ui/editor/Dashboard.tsx diff --git a/editor.planx.uk/src/components/EditorNavMenu.tsx b/editor.planx.uk/src/components/EditorNavMenu.tsx index 409d8263d1..f6a27c81e0 100644 --- a/editor.planx.uk/src/components/EditorNavMenu.tsx +++ b/editor.planx.uk/src/components/EditorNavMenu.tsx @@ -10,9 +10,9 @@ import IconButton from "@mui/material/IconButton"; import { styled } from "@mui/material/styles"; import Tooltip, { tooltipClasses, TooltipProps } from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; +import { useStore } from "pages/FlowEditor/lib/store"; import React from "react"; import { useCurrentRoute, useNavigation } from "react-navi"; -import { rootFlowPath } from "routes/utils"; import { FONT_WEIGHT_SEMI_BOLD } from "theme"; import EditorIcon from "ui/icons/Editor"; @@ -22,74 +22,12 @@ interface Route { route: string; } -interface EditorNavMenuProps { - routes: Route[]; - compact?: boolean; -} - -const globalLayoutRoutes: Route[] = [ - { - title: "Select a team", - Icon: FormatListBulletedIcon, - route: "/", - }, - { - title: "Admin panel", - Icon: AdminPanelSettingsIcon, - route: "admin-panel", - }, - { - title: "Global settings", - Icon: TuneIcon, - route: "global-settings", - }, -]; - -const teamLayoutRoutes: Route[] = [ - { - title: "Services", - Icon: FormatListBulletedIcon, - route: "/", - }, - { - title: "Team members", - Icon: GroupIcon, - route: "/members", - }, - { - title: "Design", - Icon: PaletteIcon, - route: "/settings/design", - }, -]; - -const flowLayoutRoutes: Route[] = [ - { - title: "Editor", - Icon: EditorIcon, - route: "", - }, - { - title: "Service settings", - Icon: TuneIcon, - route: "/service", - }, - { - title: "Submissions log", - Icon: FactCheckIcon, - route: "/submissions-log", - }, - { - title: "Feedback", - Icon: RateReviewIcon, - route: "/feedback", - }, -]; - const MENU_WIDTH_COMPACT = "52px"; const MENU_WIDTH_FULL = "178px"; -const Root = styled(Box)<{ compact?: boolean }>(({ theme, compact }) => ({ +const Root = styled(Box, { + shouldForwardProp: (prop) => prop !== "compact", +})<{ compact?: boolean }>(({ theme, compact }) => ({ width: compact ? MENU_WIDTH_COMPACT : MENU_WIDTH_FULL, flexShrink: 0, background: theme.palette.background.paper, @@ -152,22 +90,97 @@ const MenuButton = styled(IconButton, { }, })); -function EditorNavMenu({ routes, compact }: EditorNavMenuProps) { +function EditorNavMenu() { const { navigate } = useNavigation(); const { url } = useCurrentRoute(); - const rootPath = rootFlowPath(); + const [teamSlug, flowSlug] = useStore((state) => [ + state.teamSlug, + state.flowSlug, + ]); - const isActive = (route: string) => { - const currentPath = url.pathname.replace(rootPath, ""); - return currentPath === route; - }; + const isActive = (route: string) => url.href.endsWith(route); const handleClick = (route: string) => { - if (!isActive(route)) { - navigate(rootPath + route); - } + if (isActive(route)) return; + navigate(route); + }; + + const globalLayoutRoutes: Route[] = [ + { + title: "Select a team", + Icon: FormatListBulletedIcon, + route: "/", + }, + { + title: "Admin panel", + Icon: AdminPanelSettingsIcon, + route: "admin-panel", + }, + { + title: "Global settings", + Icon: TuneIcon, + route: "global-settings", + }, + ]; + + const teamLayoutRoutes: Route[] = [ + { + title: "Services", + Icon: FormatListBulletedIcon, + route: `/${teamSlug}`, + }, + { + title: "Team members", + Icon: GroupIcon, + route: `/${teamSlug}/members`, + }, + { + title: "Design", + Icon: PaletteIcon, + route: `/${teamSlug}/design`, + }, + { + title: "Settings", + Icon: TuneIcon, + route: `/${teamSlug}/general-settings`, + }, + ]; + + const flowLayoutRoutes: Route[] = [ + { + title: "Editor", + Icon: EditorIcon, + route: `/${teamSlug}/${flowSlug}`, + }, + { + title: "Service settings", + Icon: TuneIcon, + route: `/${teamSlug}/${flowSlug}/service`, + }, + { + title: "Submissions log", + Icon: FactCheckIcon, + route: `/${teamSlug}/${flowSlug}/submissions-log`, + }, + { + title: "Feedback", + Icon: RateReviewIcon, + route: `/${teamSlug}/${flowSlug}/feedback`, + }, + ]; + + const getRoutesForUrl = ( + url: string, + ): { routes: Route[]; compact: boolean } => { + if (flowSlug && url.includes(flowSlug)) + return { routes: flowLayoutRoutes, compact: true }; + if (teamSlug && url.includes(teamSlug)) + return { routes: teamLayoutRoutes, compact: false }; + return { routes: globalLayoutRoutes, compact: false }; }; + const { routes, compact } = getRoutesForUrl(url.href); + return ( @@ -192,5 +205,4 @@ function EditorNavMenu({ routes, compact }: EditorNavMenuProps) { ); } -export { flowLayoutRoutes, globalLayoutRoutes, teamLayoutRoutes }; export default EditorNavMenu; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx index 05465e7244..86a9966b2a 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx @@ -6,12 +6,9 @@ import IconButton from "@mui/material/IconButton"; import { styled } from "@mui/material/styles"; import Tab from "@mui/material/Tab"; import Tabs from "@mui/material/Tabs"; -import EditorNavMenu, { teamLayoutRoutes } from "components/EditorNavMenu"; import { HEADER_HEIGHT } from "components/Header"; import React from "react"; import { Link, useNavigation } from "react-navi"; -import Dashboard from "ui/editor/Dashboard"; - export interface SettingsTab { route: string; name: string; @@ -40,12 +37,7 @@ function TabPanel(props: TabPanelProps) { aria-labelledby={`nav-tab-${index}`} {...other} > - {value === index && ( - - - {children} - - )} + {value === index ? children : null} ); } diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Team/TeamMembers.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Team/TeamMembers.tsx index 6bf89a6445..c48c680317 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Team/TeamMembers.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Team/TeamMembers.tsx @@ -10,11 +10,9 @@ import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Typography from "@mui/material/Typography"; import { Role, User } from "@opensystemslab/planx-core/types"; -import EditorNavMenu, { teamLayoutRoutes } from "components/EditorNavMenu"; import React from "react"; import { FONT_WEIGHT_SEMI_BOLD } from "theme"; import SettingsSection from "ui/editor/SettingsSection"; -import Dashboard from "ui/editor/Dashboard"; const StyledAvatar = styled(Avatar)(({ theme }) => ({ background: theme.palette.background.dark, @@ -128,40 +126,37 @@ export const TeamMembers: React.FC = ({ teamMembersByRole }) => { ); return ( - - - + + + + Team editors + + + Editors have access to edit your services. + + + + + + Admins + + + Admins have editor access across all teams. + + + + {archivedMembers.length > 0 && ( - Team editors + Archived team editors - Editors have access to edit your services. + Past team members who no longer have access to the Editor, but may + be part of the edit history of your services. - + - - - Admins - - - Admins have editor access across all teams. - - - - {archivedMembers.length > 0 && ( - - - Archived team editors - - - Past team members who no longer have access to the Editor, but may - be part of the edit history of your services. - - - - )} - - + )} + ); }; diff --git a/editor.planx.uk/src/pages/GlobalSettings.tsx b/editor.planx.uk/src/pages/GlobalSettings.tsx index 9149ceae57..13c1f60211 100644 --- a/editor.planx.uk/src/pages/GlobalSettings.tsx +++ b/editor.planx.uk/src/pages/GlobalSettings.tsx @@ -4,12 +4,10 @@ import Button from "@mui/material/Button"; import Container from "@mui/material/Container"; import Snackbar from "@mui/material/Snackbar"; import Typography from "@mui/material/Typography"; -import EditorNavMenu, { globalLayoutRoutes } from "components/EditorNavMenu"; import { useFormik } from "formik"; import { useStore } from "pages/FlowEditor/lib/store"; import React, { useState } from "react"; import type { TextContent } from "types"; -import Dashboard from "ui/editor/Dashboard"; import InputGroup from "ui/editor/InputGroup"; import InputLegend from "ui/editor/InputLegend"; import ListManager from "ui/editor/ListManager"; @@ -67,8 +65,7 @@ function Component() { }; return ( - - + <>
@@ -118,7 +115,7 @@ function Component() { Footer settings updated successfully - + ); } diff --git a/editor.planx.uk/src/pages/PlatformAdminPanel.tsx b/editor.planx.uk/src/pages/PlatformAdminPanel.tsx index 7afb408063..05d384c100 100644 --- a/editor.planx.uk/src/pages/PlatformAdminPanel.tsx +++ b/editor.planx.uk/src/pages/PlatformAdminPanel.tsx @@ -9,12 +9,10 @@ import Grid from "@mui/material/Grid"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; import { SummaryListTable } from "@planx/components/shared/Preview/SummaryList"; -import EditorNavMenu, { globalLayoutRoutes } from "components/EditorNavMenu"; import { useStore } from "pages/FlowEditor/lib/store"; import React from "react"; import useSWR from "swr"; import { AdminPanelData } from "types"; -import Dashboard from "ui/editor/Dashboard"; import Caret from "ui/icons/Caret"; const StyledTeamAccordion = styled(Accordion, { @@ -37,22 +35,17 @@ function Component() { const adminPanelData = useStore((state) => state.adminPanelData); return ( - - - - - Platform Admin Panel - - {`This is an overview of each team's integrations and settings for the `} - {process.env.REACT_APP_ENV} - {` environment`} - - {adminPanelData?.map((team) => ( - - ))} - - - + + + Platform Admin Panel + + {`This is an overview of each team's integrations and settings for the `} + {process.env.REACT_APP_ENV} + {` environment`} + + {adminPanelData?.map((team) => )} + + ); } diff --git a/editor.planx.uk/src/pages/Team.tsx b/editor.planx.uk/src/pages/Team.tsx index b776f7f5a3..d3851ed023 100644 --- a/editor.planx.uk/src/pages/Team.tsx +++ b/editor.planx.uk/src/pages/Team.tsx @@ -13,12 +13,10 @@ import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import EditorNavMenu, { teamLayoutRoutes } from "components/EditorNavMenu"; import React, { useCallback, useEffect, useState } from "react"; import { Link, useNavigation } from "react-navi"; import { FONT_WEIGHT_SEMI_BOLD } from "theme"; import { borderedFocusStyle } from "theme"; -import Dashboard from "ui/editor/Dashboard"; import { slugify } from "utils"; import { client } from "../lib/graphql"; @@ -295,70 +293,67 @@ const Team: React.FC = () => { }, [fetchFlows]); return ( - - - + + - - - Services - - {useStore.getState().canUserEditTeam(slug) ? ( - - ) : ( - - )} - - {useStore.getState().canUserEditTeam(slug) && ( - { - const newFlowName = prompt("Service name"); - if (newFlowName) { - const newFlowSlug = slugify(newFlowName); - useStore - .getState() - .createFlow(teamId, newFlowSlug, newFlowName) - .then((newId: string) => { - navigation.navigate(`/${slug}/${newId}`); - }); - } - }} - > - Add a new service - + + Services + + {useStore.getState().canUserEditTeam(slug) ? ( + + ) : ( + )} - {flows && ( - - {flows.map((flow: any) => ( - { - fetchFlows(); - }} - /> - ))} - + {useStore.getState().canUserEditTeam(slug) && ( + { + const newFlowName = prompt("Service name"); + if (newFlowName) { + const newFlowSlug = slugify(newFlowName); + useStore + .getState() + .createFlow(teamId, newFlowSlug, newFlowName) + .then((newId: string) => { + navigation.navigate(`/${slug}/${newId}`); + }); + } + }} + > + Add a new service + )} - - + + {flows && ( + + {flows.map((flow: any) => ( + { + fetchFlows(); + }} + /> + ))} + + )} + ); }; diff --git a/editor.planx.uk/src/pages/Teams.tsx b/editor.planx.uk/src/pages/Teams.tsx index 0b6197fd6a..38649d671b 100644 --- a/editor.planx.uk/src/pages/Teams.tsx +++ b/editor.planx.uk/src/pages/Teams.tsx @@ -4,11 +4,9 @@ import Container from "@mui/material/Container"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; import { Team } from "@opensystemslab/planx-core/types"; -import EditorNavMenu, { globalLayoutRoutes } from "components/EditorNavMenu"; import React from "react"; import { Link } from "react-navi"; import { borderedFocusStyle } from "theme"; -import Dashboard from "ui/editor/Dashboard"; import { useStore } from "./FlowEditor/lib/store"; @@ -73,31 +71,28 @@ const Teams: React.FC = ({ teams, teamTheme }) => { ); }); return ( - - - - - Select a team - - {editableTeams.length > 0 && ( - <> - - My teams - - {renderTeams(editableTeams)} - - )} + + + Select a team + + {editableTeams.length > 0 && ( + <> + + My teams + + {renderTeams(editableTeams)} + + )} - {viewOnlyTeams.length > 0 && ( - <> - - Other teams (view only) - - {renderTeams(viewOnlyTeams)} - - )} - - + {viewOnlyTeams.length > 0 && ( + <> + + Other teams (view only) + + {renderTeams(viewOnlyTeams)} + + )} + ); }; diff --git a/editor.planx.uk/src/pages/layout/AuthenticatedLayout.tsx b/editor.planx.uk/src/pages/layout/AuthenticatedLayout.tsx index 028fb10a4c..8b0f25e2c3 100644 --- a/editor.planx.uk/src/pages/layout/AuthenticatedLayout.tsx +++ b/editor.planx.uk/src/pages/layout/AuthenticatedLayout.tsx @@ -1,13 +1,43 @@ +import Box from "@mui/material/Box"; +import { containerClasses } from "@mui/material/Container"; +import { styled } from "@mui/material/styles"; +import EditorNavMenu from "components/EditorNavMenu"; +import { HEADER_HEIGHT } from "components/Header"; import React, { PropsWithChildren } from "react"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import Header from "../../components/Header"; +const DashboardWrap = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + width: "100%", + display: "flex", + flexGrow: 1, +})); + +const DashboardContainer = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + display: "flex", + flexDirection: "row", + width: "100%", + minHeight: `calc(100vh - ${HEADER_HEIGHT}px)`, + [`& > .${containerClasses.root}`]: { + paddingTop: theme.spacing(5), + paddingBottom: theme.spacing(5), + }, +})); + const Layout: React.FC = ({ children }) => ( <>
- {children} + + + + {children} + + ); diff --git a/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx b/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx index 98a3ee97df..bb66ae8e20 100644 --- a/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx +++ b/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx @@ -1,14 +1,9 @@ -import EditorNavMenu, { flowLayoutRoutes } from "components/EditorNavMenu"; import ErrorFallback from "components/ErrorFallback"; import React, { PropsWithChildren } from "react"; import { ErrorBoundary } from "react-error-boundary"; -import Dashboard from "ui/editor/Dashboard"; const FlowEditorLayout: React.FC = ({ children }) => ( - - - {children} - + {children} ); export default FlowEditorLayout; diff --git a/editor.planx.uk/src/routes/team.tsx b/editor.planx.uk/src/routes/team.tsx index 2d956b14a6..2a482e6345 100644 --- a/editor.planx.uk/src/routes/team.tsx +++ b/editor.planx.uk/src/routes/team.tsx @@ -1,15 +1,7 @@ -import { FlowGraph } from "@opensystemslab/planx-core/types"; -import axios from "axios"; import gql from "graphql-tag"; -import { - compose, - lazy, - mount, - NotFoundError, - route, - withData, - withView, -} from "navi"; +import { compose, lazy, mount, route, withData, withView } from "navi"; +import DesignSettings from "pages/FlowEditor/components/Settings/DesignSettings"; +import GeneralSettings from "pages/FlowEditor/components/Settings/GeneralSettings"; import React from "react"; import { client } from "../lib/graphql"; @@ -85,6 +77,22 @@ const routes = compose( }), "/members": lazy(() => import("./teamMembers")), + "/design": compose( + route(async (req) => ({ + title: makeTitle( + [req.params.team, req.params.flow, "design"].join("/"), + ), + view: DesignSettings, + })), + ), + "/general-settings": compose( + route(async (req) => ({ + title: makeTitle( + [req.params.team, req.params.flow, "settings"].join("/"), + ), + view: GeneralSettings, + })), + ), }), ); diff --git a/editor.planx.uk/src/ui/editor/Dashboard.tsx b/editor.planx.uk/src/ui/editor/Dashboard.tsx deleted file mode 100644 index e5b42dbf0b..0000000000 --- a/editor.planx.uk/src/ui/editor/Dashboard.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import Box from "@mui/material/Box"; -import { containerClasses } from "@mui/material/Container"; -import { styled } from "@mui/material/styles"; -import { HEADER_HEIGHT } from "components/Header"; -import React, { PropsWithChildren } from "react"; - -const DashboardWrap = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.default, - width: "100%", - display: "flex", - alignItems: "flex-start", - flexGrow: 1, -})); - -const DashboardContainer = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, - display: "flex", - flexDirection: "row", - width: "100%", - minHeight: `calc(100vh - ${HEADER_HEIGHT}px)`, - [`& > .${containerClasses.root}`]: { - paddingTop: theme.spacing(5), - paddingBottom: theme.spacing(5), - }, -})); - -export default function Dashboard(props: PropsWithChildren) { - return ( - - {props.children} - - ); -}