Skip to content

Instantly share code, notes, and snippets.

@dgrebb
Last active April 12, 2024 02:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dgrebb/9f0d065e9a2e21db7fefba460ef58a05 to your computer and use it in GitHub Desktop.
Save dgrebb/9f0d065e9a2e21db7fefba460ef58a05 to your computer and use it in GitHub Desktop.
Strapi CMS Custom Dashboard (Almost) Replacement Plugin

For brevity, filenames include parent directory(s). - substitutes /.

If installing Strapi fresh, and you're a filthy, untyped rotten scoundrel, remember to add compilerOptions.jsx = "react" as shown below in jsconfig.json to avoid the IDE SquiggleMonster.

  1. npx create-strapi-app@latest --quickstart --no-run strapi-dashbored
  2. npm run strapi generate
  3. choose "middleware"
  4. choose "Add middleware to root of project"
  5. edit src/middlewares/redirect.js
  6. edit config/middlewares.js
  7. npm run strapi generate
  8. choose "plugin"
  9. follow prompts
  10. edit/add files
  11. npm run build
  12. npm run develop
  13. done

Important

This redirect requires a page reload if you land on /admin with no redirection. Often in develop mode, and (sometimes) in production, there is a race condition. I have not been able to find a way to override Strapi's internal routes. Please share if you do!

Tip

Be aware of the navigate utility function in ContentBlocks.js on line 48.

Note

The example assumes you have two content-types of "Project" and "Post" variety, as well as a "Home" singleton. Adjust the links to fit your needs.

Full file paths:

  • config/middlewares.js
  • config/plugins.js
  • back/src/middlewares/redirect.js
  • src/plugins/dashbored/admin/src/components/PluginIcon/index.js
  • src/plugins/dashbored/admin/src/components/ContentBlocks.js
  • src/plugins/dashbored/admin/src/pages/HomePage/index.js
  • src/plugins/dashbored/admin/src/translations/en.json
  • jsconfig.json
import React from "react";
import { useHistory } from "react-router-dom";
import { Box, Flex, Grid, GridItem } from "@strapi/design-system";
import { ContentBox, useTracking } from "@strapi/helper-plugin";
import {
FeatherSquare,
InformationSquare,
ChartBubble,
Crown,
} from "@strapi/icons";
import { useIntl } from "react-intl";
import styled from "styled-components";
const BlockLink = styled.a`
text-decoration: none;
`;
const StyledChartBubble = styled(ChartBubble)`
path {
fill: #7289da !important;
}
`;
const StyledInformationSquare = styled(InformationSquare)`
path {
stroke: #7289da !important;
}
`;
const StyledCrown = styled(Crown)`
path {
fill: #7289da !important;
stroke: #7289da !important;
}
`;
const StyledFeatherSquare = styled(FeatherSquare)`
path {
stroke: #7289da !important;
}
`;
const ContentBlocks = () => {
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const { push } = useHistory();
const navigate = (e, url) => {
e.preventDefault();
push(url);
};
return (
<Flex direction="column" alignItems="stretch" gap={5}>
<Grid gap={5}>
<GridItem col={3}>
<BlockLink
href="#"
onClick={(e) => {
navigate(
e,
"/content-manager/collectionType/api::post.post?page=1&pageSize=100"
);
}}
>
<ContentBox
title={formatMessage({
id: "dashbored.posts.title",
defaultMessage: "Posts",
})}
subtitle={formatMessage({
id: "dashbored.posts.label",
defaultMessage: "Edit Posts",
})}
icon={<StyledFeatherSquare />}
iconBackground="primary100"
/>
</BlockLink>
</GridItem>
<GridItem col={3}>
<BlockLink
href="#"
onClick={(e) => {
navigate(
e,
"/content-manager/collectionType/api::project.project?page=1&pageSize=100"
);
}}
>
<ContentBox
title={formatMessage({
id: "dashbored.projects.title",
defaultMessage: "Edit Projects",
})}
subtitle={formatMessage({
id: "dashbored.projects.subtitle",
defaultMessage: "Add or Manage Projects",
})}
icon={<StyledCrown />}
iconBackground="primary100"
/>
</BlockLink>
</GridItem>
<GridItem col={3}>
<BlockLink
href="#"
onClick={(e) => {
navigate(e, "/content-manager/singleType/api::home.home");
}}
>
<ContentBox
title={formatMessage({
id: "app.components.HomePage.HomeContent.title",
defaultMessage: "Edit Projects",
})}
subtitle={formatMessage({
id: "app.components.HomePage.HomeContent.subtitle",
defaultMessage: "Edit Project Content",
})}
icon={<StyledInformationSquare />}
iconBackground="primary100"
/>
</BlockLink>
</GridItem>
<GridItem col={3}>
<BlockLink
href="https://analytics.com"
target="_blank"
rel="noopener noreferrer nofollow"
>
<ContentBox
title={formatMessage({
id: "app.components.HomePage.analytics.title",
defaultMessage: "Analytics",
})}
subtitle={formatMessage({
id: "app.components.HomePage.analytics.subtitle",
defaultMessage: "See the traffic",
})}
icon={<StyledChartBubble />}
iconBackground="primary100"
/>
</BlockLink>
</GridItem>
</Grid>
</Flex>
);
};
export default ContentBlocks;
/**
*
* PluginIcon
*
*/
import React from 'react';
import { Dashboard } from '@strapi/icons';
const PluginIcon = () => <Dashboard />;
export default PluginIcon;
module.exports = [
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
{ resolve: "./src/middlewares/redirect" },
];
module.exports = {
dashbored: {
enabled: true,
resolve: "./src/plugins/dashbored",
},
};
{
"compilerOptions": {
"jsx": "react",
"moduleResolution": "nodenext",
"target": "ES2021",
"checkJs": true,
"allowJs": true
}
}
/*
* HomePage
*
*/
import React from "react";
import { Box, Grid, GridItem, Layout, Main } from "@strapi/design-system";
import { Helmet } from "react-helmet";
import { FormattedMessage } from "react-intl";
import styled from "styled-components";
import cornerOrnamentPath from "./assets/corner-ornament.svg";
import ContentBlocks from "../../components/ContentBlocks";
const LogoContainer = styled(Box)`
position: absolute;
top: 0;
right: 0;
img {
width: ${150 / 16}rem;
}
`;
export const HomePageCE = () => {
return (
<Layout>
<FormattedMessage id="HomePage.helmet.title" defaultMessage="Homepage">
{(title) => <Helmet title={title[0]} />}
</FormattedMessage>
<Main>
<LogoContainer>
<img alt="" aria-hidden src={cornerOrnamentPath} />
</LogoContainer>
<Box padding={10}>
<Grid>
<GridItem col={12}>
<ContentBlocks />
</GridItem>
</Grid>
</Box>
</Main>
</Layout>
);
};
function HomePageSwitch() {
return <HomePageCE />;
}
export default HomePageSwitch;
"use strict";
module.exports = (_config, { strapi }) => {
const redirects = ["/", "/index.html", "/admin", "/admin/"].map((path) => ({
method: "GET",
path,
handler: (ctx) => ctx.redirect("/admin/plugins/dashbored"),
config: { auth: false },
}));
strapi.server.routes(redirects);
};
{
"dashbored.posts.title": "Posts",
"dashbored.projects.title": "Projects",
"dashbored.homepage.title": "Homepage",
"dashbored.analytics.title": "Analytics",
"dashbored.posts.label": "Edit Posts",
"dashbored.projects.label": "Edit Projects",
"dashbored.homepage.label": "Edit Homepage",
"dashbored.analytics.label": "Visit Analytics"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment