diff --git a/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts index 3948a7338f..d30b6f269d 100644 --- a/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts @@ -207,9 +207,7 @@ test.describe("Navigation", () => { await page.goto(`/${context.team.slug}/${serviceProps.slug}`); // Open flow settings - // TODO: Access via sidebar when EDITOR_NAVIGATION flag is removed - page.getByLabel("Toggle Menu").click(); - page.getByText("Flow Settings").click(); + page.locator('[aria-label="Service settings"]').click(); // Toggle flow online page.getByLabel("Offline").click(); @@ -219,7 +217,7 @@ test.describe("Navigation", () => { ).toBeVisible(); // Exit back to main Editor page - page.getByRole("link", { name: "Close" }).click(); + page.locator('[aria-label="Editor"]').click(); const previewLink = page.getByRole("link", { name: "Open published service", diff --git a/editor.planx.uk/src/components/EditorNavMenu.tsx b/editor.planx.uk/src/components/EditorNavMenu.tsx new file mode 100644 index 0000000000..0471ebdc55 --- /dev/null +++ b/editor.planx.uk/src/components/EditorNavMenu.tsx @@ -0,0 +1,232 @@ +import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings"; +import FactCheckIcon from "@mui/icons-material/FactCheck"; +import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; +import GroupIcon from "@mui/icons-material/Group"; +import PaletteIcon from "@mui/icons-material/Palette"; +import RateReviewIcon from "@mui/icons-material/RateReview"; +import TuneIcon from "@mui/icons-material/Tune"; +import Box from "@mui/material/Box"; +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 { Role } from "@opensystemslab/planx-core/types"; +import { useStore } from "pages/FlowEditor/lib/store"; +import React from "react"; +import { useCurrentRoute, useNavigation } from "react-navi"; +import { FONT_WEIGHT_SEMI_BOLD } from "theme"; +import EditorIcon from "ui/icons/Editor"; + +interface Route { + title: string; + Icon: React.ElementType; + route: string; + accessibleBy: Role[]; +} + +const MENU_WIDTH_COMPACT = "52px"; +const MENU_WIDTH_FULL = "178px"; + +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, + borderRight: `1px solid ${theme.palette.border.light}`, +})); + +const MenuWrap = styled("ul")(({ theme }) => ({ + listStyle: "none", + margin: 0, + padding: theme.spacing(1, 0.4, 0, 0.4), + position: "sticky", + top: 0, +})); + +const MenuItem = styled("li")(({ theme }) => ({ + margin: theme.spacing(0.75, 0), + padding: 0, +})); + +const MenuTitle = styled(Typography)(({ theme }) => ({ + fontWeight: FONT_WEIGHT_SEMI_BOLD, + paddingLeft: theme.spacing(0.5), + textAlign: "left", +})) as typeof Typography; + +const TooltipWrap = styled(({ className, ...props }: TooltipProps) => ( + +))(() => ({ + [`& .${tooltipClasses.arrow}`]: { + color: "#2c2c2c", + }, + [`& .${tooltipClasses.tooltip}`]: { + backgroundColor: "#2c2c2c", + left: "-5px", + fontSize: "0.8em", + borderRadius: 0, + fontWeight: FONT_WEIGHT_SEMI_BOLD, + }, +})); + +const MenuButton = styled(IconButton, { + shouldForwardProp: (prop) => prop !== "isActive", +})<{ isActive: boolean }>(({ theme, isActive }) => ({ + color: theme.palette.text.primary, + width: "100%", + border: "1px solid transparent", + justifyContent: "flex-start", + borderRadius: "3px", + "&:hover": { + background: "white", + borderColor: theme.palette.border.light, + }, + ...(isActive && { + background: theme.palette.common.white, + color: theme.palette.text.primary, + border: `1px solid ${theme.palette.border.main}`, + }), + "& > svg": { + opacity: 0.8, + }, +})); + +function EditorNavMenu() { + const { navigate } = useNavigation(); + const { url } = useCurrentRoute(); + const [teamSlug, flowSlug, user, canUserEditTeam] = useStore((state) => [ + state.teamSlug, + state.flowSlug, + state.user, + state.canUserEditTeam, + ]); + + const isActive = (route: string) => url.href.endsWith(route); + + const handleClick = (route: string) => { + if (isActive(route)) return; + navigate(route); + }; + + const globalLayoutRoutes: Route[] = [ + { + title: "Select a team", + Icon: FormatListBulletedIcon, + route: "/", + accessibleBy: ["platformAdmin", "teamEditor", "teamViewer"], + }, + { + title: "Admin panel", + Icon: AdminPanelSettingsIcon, + route: "admin-panel", + accessibleBy: ["platformAdmin"], + }, + { + title: "Global settings", + Icon: TuneIcon, + route: "global-settings", + accessibleBy: ["platformAdmin"], + }, + ]; + + const teamLayoutRoutes: Route[] = [ + { + title: "Services", + Icon: FormatListBulletedIcon, + route: `/${teamSlug}`, + accessibleBy: ["platformAdmin", "teamEditor", "teamViewer"], + }, + { + title: "Team members", + Icon: GroupIcon, + route: `/${teamSlug}/members`, + accessibleBy: ["platformAdmin", "teamEditor"], + }, + { + title: "Design", + Icon: PaletteIcon, + route: `/${teamSlug}/design`, + accessibleBy: ["platformAdmin", "teamEditor"], + }, + { + title: "Settings", + Icon: TuneIcon, + route: `/${teamSlug}/general-settings`, + accessibleBy: ["platformAdmin", "teamEditor"], + }, + ]; + + const flowLayoutRoutes: Route[] = [ + { + title: "Editor", + Icon: EditorIcon, + route: `/${teamSlug}/${flowSlug}`, + accessibleBy: ["platformAdmin", "teamEditor", "teamViewer"], + }, + { + title: "Service settings", + Icon: TuneIcon, + route: `/${teamSlug}/${flowSlug}/service`, + accessibleBy: ["platformAdmin", "teamEditor"], + }, + { + title: "Submissions log", + Icon: FactCheckIcon, + route: `/${teamSlug}/${flowSlug}/submissions-log`, + accessibleBy: ["platformAdmin", "teamEditor"], + }, + { + title: "Feedback", + Icon: RateReviewIcon, + route: `/${teamSlug}/${flowSlug}/feedback`, + accessibleBy: ["platformAdmin", "teamEditor"], + }, + ]; + + 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); + + const visibleRoutes = routes.filter(({ accessibleBy }) => { + if (user?.isPlatformAdmin) return accessibleBy.includes("platformAdmin"); + if (canUserEditTeam(teamSlug)) return accessibleBy.includes("teamEditor"); + return accessibleBy.includes("teamViewer"); + }); + + // Hide menu if the user does not have a selection of items + if (visibleRoutes.length < 2) return null; + + return ( + + + {visibleRoutes.map(({ title, Icon, route }) => ( + handleClick(route)} key={title}> + {compact ? ( + + + + + + ) : ( + + + {title} + + )} + + ))} + + + ); +} + +export default EditorNavMenu; diff --git a/editor.planx.uk/src/components/Header.tsx b/editor.planx.uk/src/components/Header.tsx index 0210e2b98d..16947e9bc0 100644 --- a/editor.planx.uk/src/components/Header.tsx +++ b/editor.planx.uk/src/components/Header.tsx @@ -41,7 +41,7 @@ import Permission from "ui/editor/Permission"; import Reset from "ui/icons/Reset"; import { useStore } from "../pages/FlowEditor/lib/store"; -import { rootFlowPath, rootTeamPath } from "../routes/utils"; +import { rootFlowPath } from "../routes/utils"; import AnalyticsDisabledBanner from "./AnalyticsDisabledBanner"; import { ConfirmationDialog } from "./ConfirmationDialog"; import TestEnvironmentBanner from "./TestEnvironmentBanner"; @@ -63,7 +63,7 @@ const BreadcrumbsRoot = styled(Box)(() => ({ const BreadcrumbsLink = styled(Link)(({ theme }) => ({ color: theme.palette.common.white, textDecoration: "none", - borderBottom: "1px solid currentColor", + borderBottom: "1px solid rgba(255, 255, 255, 0.75)", })) as typeof Link; const StyledToolbar = styled(MuiToolbar)(() => ({ @@ -436,13 +436,6 @@ const EditorToolbar: React.FC<{ setOpen(!open); }; - const isFlowSettingsVisible = route.data.flow && canUserEditTeam(team.slug); - - const isTeamSettingsVisible = - route.data.team && !route.data.flow && canUserEditTeam(team.slug); - - const isGlobalSettingsVisible = !route.data.team && user?.isPlatformAdmin; - return ( <> @@ -488,7 +481,7 @@ const EditorToolbar: React.FC<{ {user.lastName[0]} - Menu + Account @@ -520,73 +513,6 @@ const EditorToolbar: React.FC<{ {user.email} - {(user.isPlatformAdmin || user.teams.length > 0) && ( - - - - - - {user.isPlatformAdmin - ? `All teams` - : user.teams.map((team) => team.team.name).join(", ")} - - - )} - - - - - - All teams - - - - {/* Only show team settings link if inside a team route */} - {isTeamSettingsVisible && ( - <> - navigate(`${rootTeamPath()}/settings`)} - > - Team Settings - - navigate(`${rootTeamPath()}/members`)}> - Team Members - - - )} - - {/* Only show flow settings link if inside a flow route */} - {isFlowSettingsVisible && ( - <> - - navigate([rootFlowPath(true), "settings"].join("/")) - } - > - Flow Settings - - - navigate([rootFlowPath(true), "feedback"].join("/")) - } - > - Feedback - - - )} - - {/* Only show global settings & admin panel links from top-level view */} - {isGlobalSettingsVisible && ( - <> - navigate("/global-settings")}> - Global Settings - - navigate("/admin-panel")}> - Admin Panel - - - )} - navigate("/logout")}>Log out diff --git a/editor.planx.uk/src/lib/featureFlags.ts b/editor.planx.uk/src/lib/featureFlags.ts index 022965a276..31cbfdf23c 100644 --- a/editor.planx.uk/src/lib/featureFlags.ts +++ b/editor.planx.uk/src/lib/featureFlags.ts @@ -1,5 +1,5 @@ // add/edit/remove feature flags in array below -const AVAILABLE_FEATURE_FLAGS = ["EDITOR_NAVIGATION"] as const; +const AVAILABLE_FEATURE_FLAGS = [] as const; type FeatureFlag = (typeof AVAILABLE_FEATURE_FLAGS)[number]; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/EditorMenu.tsx b/editor.planx.uk/src/pages/FlowEditor/components/EditorMenu.tsx deleted file mode 100644 index e99fb24bd9..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/components/EditorMenu.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import FactCheckIcon from "@mui/icons-material/FactCheck"; -import RateReviewIcon from "@mui/icons-material/RateReview"; -import TuneIcon from "@mui/icons-material/Tune"; -import Box from "@mui/material/Box"; -import IconButton from "@mui/material/IconButton"; -import { styled } from "@mui/material/styles"; -import Tooltip, { tooltipClasses, TooltipProps } from "@mui/material/Tooltip"; -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"; - -const MENU_WIDTH = "46px"; - -const Root = styled(Box)(({ theme }) => ({ - width: MENU_WIDTH, - flexShrink: 0, - background: theme.palette.background.paper, - borderRight: `1px solid ${theme.palette.border.main}`, -})); - -const MenuWrap = styled("ul")(({ theme }) => ({ - listStyle: "none", - margin: 0, - padding: theme.spacing(4, 0, 0, 0), -})); - -const MenuItem = styled("li")(({ theme }) => ({ - margin: theme.spacing(0.75, 0), - padding: 0, -})); - -const TooltipWrap = styled(({ className, ...props }: TooltipProps) => ( - -))(() => ({ - [`& .${tooltipClasses.arrow}`]: { - color: "#2c2c2c", - }, - [`& .${tooltipClasses.tooltip}`]: { - backgroundColor: "#2c2c2c", - left: "-5px", - fontSize: "0.8em", - borderRadius: 0, - fontWeight: FONT_WEIGHT_SEMI_BOLD, - }, -})); - -const MenuButton = styled(IconButton, { - shouldForwardProp: (prop) => prop !== "isActive", -})<{ isActive: boolean }>(({ theme, isActive }) => ({ - color: theme.palette.primary.main, - width: MENU_WIDTH, - height: MENU_WIDTH, - border: "1px solid transparent", - borderRightColor: theme.palette.border.main, - "&:hover": { - background: "white", - borderTopColor: theme.palette.border.light, - borderBottomColor: theme.palette.border.light, - }, - ...(isActive && { - background: theme.palette.common.white, - color: theme.palette.text.primary, - border: `1px solid ${theme.palette.border.main}`, - borderRightColor: "transparent", - }), -})); - -function EditorMenu() { - const { navigate } = useNavigation(); - const { url } = useCurrentRoute(); - const rootPath = rootFlowPath(); - - const isActive = (route: string) => url.pathname.endsWith(route); - const handleClick = (route: string) => - !isActive(route) && navigate(rootPath + route); - - const routes = [ - { - 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", - }, - ]; - - return ( - - - {routes.map(({ title, Icon, route }) => ( - handleClick(route)} key={title}> - - - - - - - ))} - - - ); -} - -export default EditorMenu; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Flow/FeedbackPage.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Flow/FeedbackPage.tsx index 04c7307dcf..48c42e4fd0 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Flow/FeedbackPage.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Flow/FeedbackPage.tsx @@ -155,55 +155,53 @@ export const FeedbackPage: React.FC = ({ feedback }) => { return ( - - - - Feedback log - - - Feedback from users about this service. - - - - {feedback.length === 0 ? ( - - ) : ( - - - - *": { borderBottomColor: "black !important" } }} - > - - Type - - - Date - - - Comment - - - - - - {feedback.map((item) => ( - - ))} - -
-
- )} -
-
+ + + Feedback log + + + Feedback from users about this service. + + + + {feedback.length === 0 ? ( + + ) : ( + + + + *": { borderBottomColor: "black !important" } }} + > + + Type + + + Date + + + Comment + + + + + + {feedback.map((item) => ( + + ))} + +
+
+ )} +
); }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DataManagerSettings.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DataManagerSettings.tsx index 431ba6cb61..c5899d05e2 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DataManagerSettings.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DataManagerSettings.tsx @@ -1,4 +1,4 @@ -import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; import Typography from "@mui/material/Typography"; import React from "react"; import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; @@ -6,7 +6,7 @@ import SettingsSection from "ui/editor/SettingsSection"; const DataManagerSettings: React.FC = () => { return ( - + Data Manager @@ -19,7 +19,7 @@ const DataManagerSettings: React.FC = () => { - + ); }; export default DataManagerSettings; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx index a5071ba261..9215f9b95b 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/DesignSettings/index.tsx @@ -1,5 +1,6 @@ import Alert from "@mui/material/Alert"; import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; import Snackbar from "@mui/material/Snackbar"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; @@ -75,7 +76,7 @@ const DesignSettings: React.FC = () => { const onSuccess = () => setOpen(true); return ( - + Design @@ -97,7 +98,7 @@ const DesignSettings: React.FC = () => { Theme updated successfully - + ); }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/GeneralSettings/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/GeneralSettings/index.tsx index b57672a2c5..71ec6eeca2 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/GeneralSettings/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/GeneralSettings/index.tsx @@ -1,5 +1,5 @@ import Alert from "@mui/material/Alert"; -import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; import Snackbar from "@mui/material/Snackbar"; import Typography from "@mui/material/Typography"; import { TeamSettings } from "@opensystemslab/planx-core/types"; @@ -59,13 +59,13 @@ const GeneralSettings: React.FC = () => { const onSuccess = () => setOpen(true); return ( - + General - Important links and settings for how your users connect with you + Important links and settings for how your users connect with you. {formikConfig && ( @@ -79,7 +79,7 @@ const GeneralSettings: React.FC = () => { {updateMessage} - + ); }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceFlags.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceFlags.tsx index 12954c5d67..d8165924c1 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceFlags.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceFlags.tsx @@ -1,4 +1,4 @@ -import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; import Typography from "@mui/material/Typography"; import React from "react"; import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; @@ -6,7 +6,7 @@ import SettingsSection from "ui/editor/SettingsSection"; const ServiceFlags: React.FC = () => { return ( - + Service flags @@ -19,7 +19,7 @@ const ServiceFlags: React.FC = () => { - + ); }; export default ServiceFlags; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings.tsx index 06abc3430d..0e4ef6f895 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings.tsx @@ -1,6 +1,7 @@ import Alert from "@mui/material/Alert"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; +import Container from "@mui/material/Container"; import FormControlLabel, { formControlLabelClasses, } from "@mui/material/FormControlLabel"; @@ -143,7 +144,7 @@ const ServiceSettings: React.FC = () => { }); return ( - + @@ -309,7 +310,7 @@ const ServiceSettings: React.FC = () => { Service settings updated successfully - + ); }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/index.tsx index 388d700279..73d1e4c247 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/Submissions/index.tsx @@ -1,5 +1,4 @@ import { gql, useQuery } from "@apollo/client"; -import Box from "@mui/material/Box"; import Container from "@mui/material/Container"; import Typography from "@mui/material/Typography"; import React, { useMemo } from "react"; @@ -74,23 +73,17 @@ const Submissions: React.FC = () => { return ( - - - - Submissions - - - Feed of payment and submission events for this service - - - - - - + + + Submissions + + + Feed of payment and submission events for this service + + + + + ); }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/TeamSettings.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/TeamSettings.tsx index 9d590cfbcf..b54f0a76ee 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/TeamSettings.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/TeamSettings.tsx @@ -1,4 +1,4 @@ -import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; import Typography from "@mui/material/Typography"; import React from "react"; import { FeaturePlaceholder } from "ui/editor/FeaturePlaceholder"; @@ -6,7 +6,7 @@ import SettingsSection from "ui/editor/SettingsSection"; const Team: React.FC = () => { return ( - + Team @@ -30,7 +30,7 @@ const Team: React.FC = () => { - + ); }; export default Team; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx deleted file mode 100644 index fe05eeb961..0000000000 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import Close from "@mui/icons-material/Close"; -import AppBar from "@mui/material/AppBar"; -import Box from "@mui/material/Box"; -import Container from "@mui/material/Container"; -import Grid from "@mui/material/Grid"; -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 { HEADER_HEIGHT } from "components/Header"; -import React from "react"; -import { Link, useNavigation } from "react-navi"; - -export interface SettingsTab { - route: string; - name: string; - Component: React.FC; -} - -interface SettingsProps { - currentTab: string; - tabs: SettingsTab[]; -} - -interface TabPanelProps { - children?: React.ReactNode; - index: number; - value: number; -} - -function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - return ( - - ); -} - -function a11yProps(index: number) { - return { - id: `nav-tab-${index}`, - "aria-controls": `nav-tabpanel-${index}`, - }; -} - -interface LinkTabProps { - label?: string; - href?: string; -} - -const StyledTab = styled(Tab)(({ theme }) => ({ - position: "relative", - zIndex: 1, - textTransform: "none", - background: theme.palette.background.default, -})) as typeof Tab; - -function LinkTab(props: LinkTabProps) { - return ( - { - if (!event.metaKey) { - event.preventDefault(); - } - }} - {...props} - /> - ); -} - -const PREFIX = "Settings"; - -const classes = { - tabs: `${PREFIX}-tabs`, - tabIndicator: `${PREFIX}-tabIndicator`, -}; - -const Root = styled(Box)(({ theme }) => ({ - flexGrow: 1, - backgroundColor: theme.palette.background.default, - position: "absolute", - top: HEADER_HEIGHT, - left: 0, - right: 0, - minHeight: `calc(100% - ${HEADER_HEIGHT}px)`, - // Ensure settings panels sit above editor content with explicit z-index set, will be redundent when we move to side-tabbed settings - zIndex: theme.zIndex.appBar, - [`& .${classes.tabs}`]: { - backgroundColor: theme.palette.border.main, - }, - [`& .${classes.tabIndicator}`]: { - height: "100%", - backgroundColor: "#f2f2f2", - zIndex: 0, - }, -})); - -const Settings: React.FC = ({ currentTab, tabs }) => { - const { navigate } = useNavigation(); - - const handleChange = (event: React.ChangeEvent) => { - navigate(event.currentTarget.pathname); - }; - - const value = tabs.findIndex(({ route }) => route === currentTab); - - return ( - - - - - - {tabs.map(({ name, route }, index) => ( - - ))} - - - - - - - - - - {tabs.map(({ name, Component }, index) => ( - - - - ))} - - ); -}; - -export default Settings; 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 445a094839..c48c680317 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Team/TeamMembers.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Team/TeamMembers.tsx @@ -1,5 +1,4 @@ import Avatar from "@mui/material/Avatar"; -import Box from "@mui/material/Box"; import Chip from "@mui/material/Chip"; import Container from "@mui/material/Container"; import { styled } from "@mui/material/styles"; @@ -128,38 +127,36 @@ 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/FlowEditor/index.tsx b/editor.planx.uk/src/pages/FlowEditor/index.tsx index 5d3aa6cdfa..c712f3c1e2 100644 --- a/editor.planx.uk/src/pages/FlowEditor/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/index.tsx @@ -1,8 +1,8 @@ -import "./components/Settings"; import "./floweditor.scss"; import Box from "@mui/material/Box"; import { styled } from "@mui/material/styles"; +import { HEADER_HEIGHT } from "components/Header"; import React, { useRef } from "react"; import { rootFlowPath } from "../../routes/utils"; @@ -10,11 +10,13 @@ import Flow from "./components/Flow"; import Sidebar from "./components/Sidebar"; import { useStore } from "./lib/store"; import useScrollControlsAndRememberPosition from "./lib/useScrollControlsAndRememberPosition"; + const EditorContainer = styled(Box)(() => ({ display: "flex", alignItems: "stretch", overflow: "hidden", flexGrow: 1, + maxHeight: `calc(100vh - ${HEADER_HEIGHT}px)`, })); const FlowEditor: React.FC = ({ flow, breadcrumbs }) => { diff --git a/editor.planx.uk/src/pages/GlobalSettings.tsx b/editor.planx.uk/src/pages/GlobalSettings.tsx index 5b44b151b9..13c1f60211 100644 --- a/editor.planx.uk/src/pages/GlobalSettings.tsx +++ b/editor.planx.uk/src/pages/GlobalSettings.tsx @@ -8,7 +8,6 @@ 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"; @@ -66,7 +65,7 @@ function Component() { }; return ( - + <>
@@ -116,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 540fc44abe..05d384c100 100644 --- a/editor.planx.uk/src/pages/PlatformAdminPanel.tsx +++ b/editor.planx.uk/src/pages/PlatformAdminPanel.tsx @@ -4,6 +4,7 @@ import Accordion from "@mui/material/Accordion"; import AccordionDetails from "@mui/material/AccordionDetails"; import AccordionSummary from "@mui/material/AccordionSummary"; import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; import Grid from "@mui/material/Grid"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; @@ -34,15 +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 fc6ee62a4c..d3851ed023 100644 --- a/editor.planx.uk/src/pages/Team.tsx +++ b/editor.planx.uk/src/pages/Team.tsx @@ -17,7 +17,6 @@ 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"; @@ -25,14 +24,6 @@ import SimpleMenu from "../ui/editor/SimpleMenu"; import { useStore } from "./FlowEditor/lib/store"; import { formatLastEditMessage } from "./FlowEditor/utils"; -const Root = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.default, - width: "100%", - display: "flex", - alignItems: "flex-start", - flexGrow: 1, -})); - const DashboardList = styled("ul")(({ theme }) => ({ padding: theme.spacing(0, 0, 3), borderBottom: "1px solid #fff", @@ -302,71 +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}`); + }); + } }} > - + )} + + {flows && ( + + {flows.map((flow: any) => ( + { + fetchFlows(); }} - > - - 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 - - )} - - {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 f8208dd2c9..38649d671b 100644 --- a/editor.planx.uk/src/pages/Teams.tsx +++ b/editor.planx.uk/src/pages/Teams.tsx @@ -7,7 +7,6 @@ import { Team } from "@opensystemslab/planx-core/types"; 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"; @@ -21,14 +20,6 @@ interface Props { teamTheme: Array; } -export const Root = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.default, - width: "100%", - display: "flex", - alignItems: "flex-start", - flexGrow: 1, -})); - const StyledLink = styled(Link)(() => ({ textDecoration: "none", "&:focus-within > div": { @@ -80,32 +71,28 @@ const Teams: React.FC = ({ teams, teamTheme }) => { ); }); return ( - - - - - Select a team + + + Select a team + + {editableTeams.length > 0 && ( + <> + + My teams - {editableTeams.length > 0 && ( - <> - - My teams - - {renderTeams(editableTeams)} - - )} + {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..ae94fef872 100644 --- a/editor.planx.uk/src/pages/layout/AuthenticatedLayout.tsx +++ b/editor.planx.uk/src/pages/layout/AuthenticatedLayout.tsx @@ -1,13 +1,44 @@ +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)`, + overflow: "hidden", + [`& > .${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 1c95b66633..bb66ae8e20 100644 --- a/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx +++ b/editor.planx.uk/src/pages/layout/FlowEditorLayout.tsx @@ -1,23 +1,9 @@ -import Box from "@mui/material/Box"; -import { styled } from "@mui/material/styles"; import ErrorFallback from "components/ErrorFallback"; -import { hasFeatureFlag } from "lib/featureFlags"; -import EditorMenu from "pages/FlowEditor/components/EditorMenu"; import React, { PropsWithChildren } from "react"; import { ErrorBoundary } from "react-error-boundary"; -const Root = styled(Box)(() => ({ - display: "flex", - alignItems: "stretch", - overflow: "hidden", - flexGrow: 1, -})); - const FlowEditorLayout: React.FC = ({ children }) => ( - - {hasFeatureFlag("EDITOR_NAVIGATION") && } - {children} - + {children} ); export default FlowEditorLayout; diff --git a/editor.planx.uk/src/routes/flow.tsx b/editor.planx.uk/src/routes/flow.tsx index f6d60051b1..1bb284969a 100644 --- a/editor.planx.uk/src/routes/flow.tsx +++ b/editor.planx.uk/src/routes/flow.tsx @@ -1,6 +1,8 @@ import { gql } from "@apollo/client"; -import Box from "@mui/material/Box"; -import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; +import { + ComponentType as TYPES, + FlowStatus, +} from "@opensystemslab/planx-core/types"; import natsort from "natsort"; import { compose, @@ -8,6 +10,7 @@ import { map, Matcher, mount, + NaviRequest, redirect, route, withData, @@ -27,8 +30,7 @@ import components from "../pages/FlowEditor/components/forms"; import FormModal from "../pages/FlowEditor/components/forms/FormModal"; import { SLUGS } from "../pages/FlowEditor/data/types"; import { useStore } from "../pages/FlowEditor/lib/store"; -import type { Flow } from "../types"; -import { getFlowSettings } from "./flowSettings"; +import type { Flow, FlowSettings } from "../types"; import { makeTitle } from "./utils"; import { flowEditorView } from "./views/flowEditor"; @@ -177,11 +179,43 @@ const nodeRoutes = mount({ "/:parent/nodes/:id/edit": editNode, }); -const SettingsContainer = () => ( - - - -); +const SettingsContainer = () => ; + +interface GetFlowSettings { + flows: { + id: string; + settings: FlowSettings; + status: FlowStatus; + }[]; +} + +export const getFlowSettings = async (req: NaviRequest) => { + const { + data: { + flows: [{ settings, status }], + }, + } = await client.query({ + query: gql` + query GetFlow($slug: String!, $team_slug: String!) { + flows( + limit: 1 + where: { slug: { _eq: $slug }, team: { slug: { _eq: $team_slug } } } + ) { + id + settings + status + } + } + `, + variables: { + slug: req.params.flow, + team_slug: req.params.team, + }, + }); + + useStore.getState().setFlowSettings(settings); + useStore.getState().setFlowStatus(status); +}; const routes = compose( withData((req) => ({ @@ -218,8 +252,6 @@ const routes = compose( nodeRoutes, ), - "/settings": lazy(() => import("./flowSettings")), - "/service": compose( withView(SettingsContainer), diff --git a/editor.planx.uk/src/routes/flowSettings.tsx b/editor.planx.uk/src/routes/flowSettings.tsx deleted file mode 100644 index 7201cf2ff7..0000000000 --- a/editor.planx.uk/src/routes/flowSettings.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { FlowStatus } from "@opensystemslab/planx-core/types"; -import gql from "graphql-tag"; -import { - compose, - map, - mount, - NaviRequest, - NotFoundError, - redirect, - route, - withData, -} from "navi"; -import DataManagerSettings from "pages/FlowEditor/components/Settings/DataManagerSettings"; -import ServiceFlags from "pages/FlowEditor/components/Settings/ServiceFlags"; -import ServiceSettings from "pages/FlowEditor/components/Settings/ServiceSettings"; -import Submissions from "pages/FlowEditor/components/Settings/Submissions"; -import { useStore } from "pages/FlowEditor/lib/store"; -import React from "react"; - -import { client } from "../lib/graphql"; -import Settings, { SettingsTab } from "../pages/FlowEditor/components/Settings"; -import type { FlowSettings } from "../types"; -import { makeTitle } from "./utils"; - -interface GetFlowSettings { - flows: { - id: string; - settings: FlowSettings; - status: FlowStatus; - }[]; -} - -export const getFlowSettings = async (req: NaviRequest) => { - const { - data: { - flows: [{ settings, status }], - }, - } = await client.query({ - query: gql` - query GetFlow($slug: String!, $team_slug: String!) { - flows( - limit: 1 - where: { slug: { _eq: $slug }, team: { slug: { _eq: $team_slug } } } - ) { - id - settings - status - } - } - `, - variables: { - slug: req.params.flow, - team_slug: req.params.team, - }, - }); - - useStore.getState().setFlowSettings(settings); - useStore.getState().setFlowStatus(status); -}; - -const tabs: SettingsTab[] = [ - { - name: "Service", - route: "service", - Component: ServiceSettings, - }, - { - name: "Service Flags", - route: "flags", - Component: ServiceFlags, - }, - { - name: "Data", - route: "data-manager", - Component: DataManagerSettings, - }, - { - name: "Submissions", - route: "submissions", - Component: Submissions, - }, -]; - -const flowSettingsRoutes = compose( - withData((req) => ({ - mountpath: req.mountpath, - })), - - mount({ - "/": redirect("./service"), - "/:tab": map(async (req) => { - const isAuthorised = useStore.getState().canUserEditTeam(req.params.team); - if (!isAuthorised) - throw new NotFoundError( - `User does not have access to ${req.originalUrl}`, - ); - - return route(async (req) => ({ - getData: await getFlowSettings(req), - title: makeTitle( - [req.params.team, req.params.flow, "Flow Settings"].join("/"), - ), - view: , - })); - }), - }), -); - -export default flowSettingsRoutes; diff --git a/editor.planx.uk/src/routes/team.tsx b/editor.planx.uk/src/routes/team.tsx index 2d956b14a6..ad89c46704 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"; @@ -36,8 +28,6 @@ const routes = compose( view: , })), - "/settings": lazy(() => import("./teamSettings")), - "/:flow": lazy(async (req) => { const [slug] = req.params.flow.split(","); @@ -85,6 +75,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/routes/teamSettings.tsx b/editor.planx.uk/src/routes/teamSettings.tsx deleted file mode 100644 index 8427b37501..0000000000 --- a/editor.planx.uk/src/routes/teamSettings.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { - compose, - map, - mount, - NotFoundError, - redirect, - route, - withData, -} from "navi"; -import DesignSettings from "pages/FlowEditor/components/Settings/DesignSettings"; -import GeneralSettings from "pages/FlowEditor/components/Settings/GeneralSettings"; -import TeamSettings from "pages/FlowEditor/components/Settings/TeamSettings"; -import { useStore } from "pages/FlowEditor/lib/store"; -import React from "react"; - -import Settings from "../pages/FlowEditor/components/Settings"; -import { makeTitle } from "./utils"; - -const teamSettingsRoutes = compose( - withData((req) => ({ - mountpath: req.mountpath, - })), - - mount({ - "/": redirect("./general"), - "/:tab": map(async (req) => { - const isAuthorised = useStore.getState().canUserEditTeam(req.params.team); - - if (!isAuthorised) - throw new NotFoundError( - `User does not have access to ${req.originalUrl}`, - ); - - return route(async (req) => ({ - title: makeTitle([req.params.team, "Team Settings"].join("/")), - view: ( - - ), - })); - }), - }), -); - -export default teamSettingsRoutes; 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 a18472da46..0000000000 --- a/editor.planx.uk/src/ui/editor/Dashboard.tsx +++ /dev/null @@ -1,22 +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 Root = 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(6), - paddingBottom: theme.spacing(6), - }, -})); - -export default function Dashboard(props: PropsWithChildren) { - return {props.children}; -}