Skip to content

Commit

Permalink
feat: basic platform admin panel for checking onboarding status & ava…
Browse files Browse the repository at this point in the history
…ilable integrations (#3017)
  • Loading branch information
jessicamcinchak authored Apr 16, 2024
1 parent c772c4b commit c57f7a5
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 56 deletions.
104 changes: 54 additions & 50 deletions editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,63 +20,67 @@ const FIND_PROPERTY_DT = "Property address";
const DRAW_BOUNDARY_DT = "Location plan";

export const SummaryListTable = styled("dl", {
shouldForwardProp: (prop) => prop !== "showChangeButton",
})<{ showChangeButton?: boolean }>(({ theme, showChangeButton }) => ({
display: "grid",
gridTemplateColumns: showChangeButton ? "1fr 2fr 100px" : "1fr 2fr",
gridRowGap: "10px",
marginTop: theme.spacing(2),
marginBottom: theme.spacing(4),
"& > *": {
borderBottom: `1px solid ${theme.palette.border.main}`,
paddingBottom: theme.spacing(2),
paddingTop: theme.spacing(2),
verticalAlign: "top",
margin: 0,
},
"& ul": {
listStylePosition: "inside",
padding: 0,
margin: 0,
},
"& dt": {
// left column
fontWeight: FONT_WEIGHT_SEMI_BOLD,
},
"& dd:nth-of-type(n)": {
// middle column
paddingLeft: "10px",
},
"& dd:nth-of-type(2n)": {
// right column
textAlign: showChangeButton ? "right" : "left",
},
[theme.breakpoints.down("sm")]: {
display: "flex",
flexDirection: "column",
shouldForwardProp: (prop) =>
!["showChangeButton", "dense"].includes(prop as string),
})<{ showChangeButton?: boolean; dense?: boolean }>(
({ theme, showChangeButton, dense }) => ({
display: "grid",
gridTemplateColumns: showChangeButton ? "1fr 2fr 100px" : "1fr 2fr",
gridRowGap: "10px",
marginTop: dense ? theme.spacing(1) : theme.spacing(2),
marginBottom: dense ? theme.spacing(2) : theme.spacing(4),
fontSize: dense ? theme.typography.body2.fontSize : "inherit",
"& > *": {
borderBottom: `1px solid ${theme.palette.border.main}`,
paddingBottom: dense ? theme.spacing(1) : theme.spacing(2),
paddingTop: dense ? theme.spacing(1) : theme.spacing(2),
verticalAlign: "top",
margin: 0,
},
"& ul": {
listStylePosition: "inside",
padding: 0,
margin: 0,
},
"& dt": {
// top row
paddingLeft: theme.spacing(1),
paddingTop: theme.spacing(2),
marginTop: theme.spacing(1),
borderTop: `1px solid ${theme.palette.border.main}`,
borderBottom: "none",
// left column
fontWeight: FONT_WEIGHT_SEMI_BOLD,
},
"& dd:nth-of-type(n)": {
// middle row
textAlign: "left",
paddingTop: 0,
paddingBottom: 0,
margin: 0,
borderBottom: "none",
// middle column
paddingLeft: "10px",
},
"& dd:nth-of-type(2n)": {
// bottom row
textAlign: "left",
// right column
textAlign: showChangeButton ? "right" : "left",
},
[theme.breakpoints.down("sm")]: {
display: "flex",
flexDirection: "column",
"& dt": {
// top row
paddingLeft: theme.spacing(1),
paddingTop: dense ? theme.spacing(1) : theme.spacing(2),
marginTop: theme.spacing(1),
borderTop: `1px solid ${theme.palette.border.main}`,
borderBottom: "none",
fontWeight: FONT_WEIGHT_SEMI_BOLD,
},
"& dd:nth-of-type(n)": {
// middle row
textAlign: "left",
paddingTop: 0,
paddingBottom: 0,
margin: 0,
borderBottom: "none",
},
"& dd:nth-of-type(2n)": {
// bottom row
textAlign: "left",
},
},
},
}));
}),
);

const presentationalComponents: {
[key in TYPES]: React.FC<ComponentProps> | undefined;
Expand Down
13 changes: 9 additions & 4 deletions editor.planx.uk/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -554,11 +554,16 @@ const EditorToolbar: React.FC<{
</MenuItem>
)}

{/* Only show global settings link from top-level admin view */}
{/* Only show global settings & admin panel links from top-level view */}
{isGlobalSettingsVisible && (
<MenuItem onClick={() => navigate("/global-settings")}>
Global Settings
</MenuItem>
<>
<MenuItem onClick={() => navigate("/global-settings")}>
Global Settings
</MenuItem>
<MenuItem onClick={() => navigate("/admin-panel")}>
Admin Panel
</MenuItem>
</>
)}

<MenuItem onClick={() => navigate("/logout")}>Log out</MenuItem>
Expand Down
13 changes: 12 additions & 1 deletion editor.planx.uk/src/pages/FlowEditor/lib/store/settings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { gql } from "@apollo/client";
import camelcaseKeys from "camelcase-keys";
import { client } from "lib/graphql";
import { FlowSettings, GlobalSettings, TextContent } from "types";
import {
AdminPanelData,
FlowSettings,
GlobalSettings,
TextContent,
} from "types";
import type { StateCreator } from "zustand";

import { SharedStore } from "./shared";
Expand All @@ -14,6 +19,8 @@ export interface SettingsStore {
setGlobalSettings: (globalSettings: GlobalSettings) => void;
updateFlowSettings: (newSettings: FlowSettings) => Promise<number>;
updateGlobalSettings: (newSettings: { [key: string]: TextContent }) => void;
adminPanelData?: AdminPanelData[];
setAdminPanelData: (adminPanelData: AdminPanelData[]) => void;
}

export const settingsStore: StateCreator<
Expand Down Expand Up @@ -91,4 +98,8 @@ export const settingsStore: StateCreator<
},
});
},

adminPanelData: undefined,

setAdminPanelData: (adminPanelData) => set({ adminPanelData }),
});
168 changes: 168 additions & 0 deletions editor.planx.uk/src/pages/PlatformAdminPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import Close from "@mui/icons-material/Close";
import Done from "@mui/icons-material/Done";
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 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 { useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { AdminPanelData } from "types";
import Caret from "ui/icons/Caret";

const StyledTeamAccordion = styled(Accordion, {
shouldForwardProp: (prop) => prop !== "primaryColour",
})<{ primaryColour?: string }>(({ theme, primaryColour }) => ({
borderTop: "none", // TODO figure out how to remove top border (box shadow?) when collapsed
borderLeft: `10px solid ${primaryColour}`,
backgroundColor: theme.palette.background.paper,
width: "100%",
position: "relative",
marginBottom: theme.spacing(2),
padding: theme.spacing(1),
"&::after": {
position: "absolute",
width: "100%",
},
}));

function Component() {
const adminPanelData = useStore((state) => state.adminPanelData);

return (
<Box p={3}>
<Typography variant="h1">Platform Admin Panel</Typography>
<Typography variant="body1" mb={3}>
{`This is an overview of each team's integrations and settings for the `}
<strong>{process.env.REACT_APP_ENV}</strong>
{` environment`}
</Typography>
{adminPanelData?.map((team) => <TeamData key={team.id} data={team} />)}
</Box>
);
}

interface TeamData {
data: AdminPanelData;
}

const NotConfigured: React.FC = () => <Close color="error" fontSize="small" />;

const Configured: React.FC = () => <Done color="success" fontSize="small" />;

const TeamData: React.FC<TeamData> = ({ data }) => {
return (
<StyledTeamAccordion primaryColour={data.primaryColour} elevation={0}>
<AccordionSummary
id={`${data.name}-header`}
aria-controls={`${data.name}-panel`}
expandIcon={<Caret />}
sx={{ pr: 1.5 }}
>
<Typography variant="h2">{data.name}</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid
container
direction="row"
justifyContent="flex-start"
alignItems="flex-start"
spacing={3}
>
<Grid item xs={4}>
<SummaryListTable dense={true}>
<>
<Box component="dt">{"Slug"}</Box>
<Box component="dd">
<code>
{`/`}
{data.slug}
</code>
</Box>
</>
<>
<Box component="dt">{"Homepage"}</Box>
<Box component="dd">{data.homepage || <NotConfigured />}</Box>
</>
<>
<Box component="dt">{"Logo"}</Box>
<Box component="dd">
{data.logo ? <Configured /> : <NotConfigured />}
</Box>
</>
<>
<Box component="dt">{"Favicon"}</Box>
<Box component="dd">
{data.favicon ? <Configured /> : <NotConfigured />}
</Box>
</>
</SummaryListTable>
</Grid>
<Grid item xs={4}>
<SummaryListTable dense={true}>
<>
<Box component="dt">{"Planning constraints"}</Box>
<Box component="dd">
{data.planningDataEnabled ? (
<Configured />
) : (
<NotConfigured />
)}
</Box>
</>
<>
<Box component="dt">{"Article 4s"}</Box>
<Box component="dd">{"?"}</Box>
</>
<>
<Box component="dt">{"Reference code"}</Box>
<Box component="dd">
{data.referenceCode || <NotConfigured />}
</Box>
</>
<>
<Box component="dt">{"Subdomain"}</Box>
<Box component="dd">{data.subdomain || <NotConfigured />}</Box>
</>
</SummaryListTable>
</Grid>
<Grid item xs={4}>
<SummaryListTable dense={true}>
<>
<Box component="dt">{"GOV.UK Notify"}</Box>
<Box component="dd">
{data.govnotifyPersonalisation?.helpEmail || (
<NotConfigured />
)}
</Box>
</>
<>
<Box component="dt">{"GOV.UK Pay"}</Box>
<Box component="dd">
{data.govpayEnabled ? <Configured /> : <NotConfigured />}
</Box>
</>
<>
<Box component="dt">{"Send to email"}</Box>
<Box component="dd">
{data.sendToEmailAddress || <NotConfigured />}
</Box>
</>
<>
<Box component="dt">{"BOPS"}</Box>
<Box component="dd">
{data.bopsSubmissionURL ? <Configured /> : <NotConfigured />}
</Box>
</>
</SummaryListTable>
</Grid>
</Grid>
</AccordionDetails>
</StyledTeamAccordion>
);
};

export default Component;
43 changes: 43 additions & 0 deletions editor.planx.uk/src/routes/authenticated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React from "react";

import { client } from "../lib/graphql";
import GlobalSettingsView from "../pages/GlobalSettings";
import AdminPanelView from "../pages/PlatformAdminPanel";
import Teams from "../pages/Teams";
import { makeTitle } from "./utils";
import { authenticatedView } from "./views/authenticated";
Expand Down Expand Up @@ -68,6 +69,48 @@ const editorRoutes = compose(
});
}),

"/admin-panel": map(async (req) => {
const isAuthorised = useStore.getState().user?.isPlatformAdmin;
if (!isAuthorised)
throw new NotFoundError(
`User does not have access to ${req.originalUrl}`,
);

return route(async () => {
const { data } = await client.query({
query: gql`
query {
adminPanel: teams_summary {
id
name
slug
referenceCode: reference_code
homepage
subdomain
planningDataEnabled: planning_data_enabled
article4sEnabled: article_4s_enabled
govnotifyPersonalisation: govnotify_personalisation
govpayEnabled: govpay_enabled
sendToEmailAddress: send_to_email_address
bopsSubmissionURL: bops_submission_url
logo
favicon
primaryColour: primary_colour
linkColour: link_colour
actionColour: action_colour
}
}
`,
});
useStore.getState().setAdminPanelData(data.adminPanel);

return {
title: makeTitle("Platform Admin Panel"),
view: <AdminPanelView />,
};
});
}),

"/:team": lazy(() => import("./team")),
}),
);
Expand Down
Loading

0 comments on commit c57f7a5

Please sign in to comment.