Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: strapi admin custom plugin #162

Merged
merged 12 commits into from
Nov 13, 2023
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ yarn scripts strapi bootstrap

### 4. Import data

```
```sh
yarn scripts strapi import
```

Expand Down
4 changes: 4 additions & 0 deletions backend/config/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ module.exports = ({ env }) => ({
"import-export-entries": {
enabled: true,
},
"sami-admin": {
enabled: true,
resolve: "./src/plugins/sami-admin",
},
});
6 changes: 5 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"scripts": {
"start": "strapi develop",
"start:prod": "strapi start",
"build": "strapi build",
"build": "yarn plugins:build && strapi build",
"plugins:build": "cd src/plugins/sami-admin && npm run build",
"plugins:dev": "cd src/plugins/sami-admin && npm run develop",
"strapi": "strapi"
},
"dependencies": {
Expand All @@ -20,6 +22,8 @@
"knex": "^2.4.2",
"pg": "^8.9.0",
"pg-connection-string": "^2.5.0",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"sqlite3": "5.1.6",
"strapi-connector-bookshelf": "^3.6.11",
"strapi-plugin-import-export-entries": "^1.18.1"
Expand Down
1 change: 1 addition & 0 deletions backend/public/s-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
"type": "string",
"required": true,
"unique": true
},
"Status": {
"type": "enumeration",
"enum": ["Ongoing", "Completed"]
}
}
}
10 changes: 2 additions & 8 deletions backend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ module.exports = {
// extensionService.shadowCRUD("api::restaurant.restaurant").disable();
},

/**
* An asynchronous bootstrap function that runs before
* your application gets started.
*
* This gives you an opportunity to set up your data model,
* run jobs, or perform some special logic.
*/
bootstrap(/*{ strapi }*/) {},
/** */
bootstrap(/* { strapi } */) {},
};
1 change: 1 addition & 0 deletions backend/src/plugins/sami-admin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
21 changes: 21 additions & 0 deletions backend/src/plugins/sami-admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Strapi plugin sami-admin

A quick description of sami-admin.

Build plugin and backend

```sh
yarn workspace backend build
```

Start backend

```sh
yarn start --only backend
```

## References

- [Socket.io Docs](https://socket.io/docs/v4)
- [Strapi Design System](https://design-system-git-main-strapijs.vercel.app/)
- [Strapi Websockets Example](https://strapi.io/blog/real-time-chat-application-using-strapi-next-socket-io-and-postgre-sql)
6 changes: 6 additions & 0 deletions backend/src/plugins/sami-admin/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- Rename to `sami-admin` plugin
- Remove deprecated routes (if possible)
- Add `--prod` deploy option (?)
- Minor UI improvements
- Progress bar?
- Colored logs? (https://github.com/xpl/ansicolor) https://github.com/rburns/ansi-to-html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
*
* Initializer
*
*/

import { useEffect, useRef } from "react";
import pluginId from "../../pluginId";

type InitializerProps = {
setPlugin: (id: string) => void;
};

const Initializer = ({ setPlugin }: InitializerProps) => {
const ref = useRef(setPlugin);

useEffect(() => {
ref.current(pluginId);
}, []);

return null;
};

export default Initializer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
*
* PluginIcon
*
*/

import React from "react";
import { Puzzle } from "@strapi/icons";

const PluginIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path
fill="#00c4ee"
fill-rule="evenodd"
d="M65.45676 128.000004c-10.56726-.66497-21.31694-1.31602-31.68025-4.74844-3.21933-1.06561-6.30658-2.42707-9.0479-4.49709-8.49804-6.41502-7.32908-20.87826 2.098-25.86782 3.18338-1.68422 6.24369-1.17691 9.28241.62138 9.23569 5.46533 19.11202 7.81172 29.7341 6.17203 3.23101-.49803 6.31377-1.54139 7.09097-5.34292.73318-3.591-1.12942-6.24901-3.73327-8.35519-3.49877-2.82958-7.71903-4.17992-11.76139-5.87155-5.50511-2.30188-11.0246-4.55275-16.17032-7.67261-12.04081-7.29979-17.91342-18.2861-17.56749-32.52768.35849-14.78413 6.46202-26.542064 19.61158-33.153704C58.90309-1.080352 75.28099-2.094031 91.70917 3.6857c13.11543 4.61488 19.59902 16.777174 16.54501 29.770444-1.33607 5.68328-4.57427 9.72408-10.07849 11.57616-3.84559 1.29468-7.7352 2.40945-11.83506 2.35195-4.23464-.0594-5.12505-.88476-5.58958-5.13424-1.28574-11.75979-11.38401-16.90794-21.13724-10.77671-5.45569 3.42962-6.29489 9.59981-1.88505 14.39554 3.43316 3.73382 7.98048 5.4746 12.53229 7.15881 7.59054 2.80825 15.29789 5.35868 22.15345 9.89381 12.32564 8.15579 17.52078 20.00647 15.51982 34.74144-2.05848 15.16345-11.18546 24.24946-25.29732 28.20587-5.55542 1.56086-11.2681 1.92534-17.18024 2.13123z"
clip-rule="evenodd"
/>
</svg>
);

export default PluginIcon;
67 changes: 67 additions & 0 deletions backend/src/plugins/sami-admin/admin/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { prefixPluginTranslations } from "@strapi/helper-plugin";

import pluginPkg from "../../package.json";
import pluginId from "./pluginId";
import Initializer from "./components/Initializer";
import PluginIcon from "./components/PluginIcon";

const name = pluginPkg.strapi.name;

export default {
register(app: any) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: "SAMI Admin",
},
Component: async () => {
const component = await import(/* webpackChunkName: "[request]" */ "./pages/App");

return component;
},
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugin::plugin-name.actionType
// subject: null,
// },
],
});
const plugin = {
id: pluginId,
initializer: Initializer,
isReady: false,
name,
};

app.registerPlugin(plugin);
},

bootstrap(app: any) {},

async registerTrads(app: any) {
const { locales } = app;

const importedTrads = await Promise.all(
(locales as any[]).map((locale) => {
return import(`./translations/${locale}.json`)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);

return Promise.resolve(importedTrads);
},
};
25 changes: 25 additions & 0 deletions backend/src/plugins/sami-admin/admin/src/pages/App/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
*
* This component is the skeleton around the actual pages, and should only
* contain code that should be seen on all pages. (e.g. navigation bar)
*
*/

import React from "react";
import { Switch, Route } from "react-router-dom";
import { AnErrorOccurred } from "@strapi/helper-plugin";
import pluginId from "../../pluginId";
import HomePage from "../HomePage";

const App = () => {
return (
<div>
<Switch>
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
<Route component={AnErrorOccurred} />
</Switch>
</div>
);
};

export default App;
101 changes: 101 additions & 0 deletions backend/src/plugins/sami-admin/admin/src/pages/HomePage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
*
* HomePage
*
*/

import React, { Fragment, useRef, useState } from "react";
import { GridLayout } from "@strapi/design-system";
import { Link } from "@strapi/design-system";
import socket from "socket.io-client";

// https://design-system.strapi.io/components
import { BaseHeaderLayout, Box, Button } from "@strapi/design-system";

const HomePage = () => {
const [updates, setUpdates] = useState<string[]>([]);
const [showOutput, setShowOutput] = useState(false);
const [deployDisabled, setDeployDisabled] = useState(false);
const [inspectLink, setInspectLink] = useState("");
const [previewLink, setPreviewLink] = useState("");

const codeEndRef = useRef<HTMLDivElement>(null);

const websocketDeploy = () => {
setDeployDisabled(true);
setUpdates([]);
setShowOutput(true);
const io = socket("http://localhost:1337", { path: "/sami-admin/" }); //Connecting to Socket.io backend

io.on("message", async (data: string, error) => {
//Listening for a message connection
setUpdates((existing) => [...existing, data]);
codeEndRef.current?.scrollIntoView({ behavior: "smooth" });
if (data.startsWith("Inspect: https://vercel.com/")) {
setInspectLink(data.split(" ")[1]);
}
if (data.startsWith("https://sami-website")) {
setPreviewLink(data);
}
});
io.emit("deploy", (error) => {
if (error) return alert(error);
});
};

return (
<div>
<BaseHeaderLayout title="SAMI Admin" as="h2" />
<Box paddingLeft={10} paddingRight={10} background="neutral100" marginBottom={4}>
{/* <Button onClick={() => triggerDeploy()}>Deploy to live site</Button> */}
<GridLayout style={{ gap: "8px" }}>
<Button disabled={deployDisabled} onClick={() => websocketDeploy()}>
Deploy Preview
</Button>
<Link
disabled={!inspectLink}
isExternal
href={inspectLink}
rel="noopener noreferrer"
style={{ margin: "auto", visibility: deployDisabled ? "visible" : "hidden" }}
>
Build Inspect
</Link>
<Link
disabled={!previewLink}
isExternal
href={previewLink}
rel="noopener noreferrer"
style={{ margin: "auto", visibility: deployDisabled ? "visible" : "hidden" }}
>
Site Preview
</Link>
</GridLayout>
</Box>
{showOutput && (
<Box margin={4} padding={4} background="black" style={{ height: 400, overflow: "auto" }}>
<code style={{ color: "white", fontFamily: "monospace", lineHeight: "1rem" }}>
{updates.map((u) => (
<Fragment key={u}>
{u}
<br></br>
</Fragment>
))}
</code>
<div ref={codeEndRef} style={{ marginTop: "1rem" }} />
</Box>
)}
</div>
);
};

export default HomePage;

// const triggerDeploy = () => {
// console.log("triggering deploy");
// fetch("/sami-admin/deploy", {
// method: "GET", // default, so we can ignore
// })
// .then((response) => response.json())
// .then((json) => console.log(json));
// };
5 changes: 5 additions & 0 deletions backend/src/plugins/sami-admin/admin/src/pluginId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pluginPkg from "../../package.json";

const pluginId = pluginPkg.name.replace(/^(@[^-,.][\w,-]+\/|strapi-)plugin-/i, "");

export default pluginId;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions backend/src/plugins/sami-admin/admin/src/utils/getTrad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pluginId from "../pluginId";

const getTrad = (id: string) => `${pluginId}.${id}`;

export default getTrad;
5 changes: 5 additions & 0 deletions backend/src/plugins/sami-admin/custom.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module '@strapi/design-system/*';
declare module '@strapi/design-system';
declare module '@strapi/icons';
declare module '@strapi/icons/*';
declare module '@strapi/helper-plugin';
Loading