bananas-commerce-admin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/cjs/Admin.js +47 -0
- package/dist/cjs/App.js +49 -0
- package/dist/cjs/api.js +225 -0
- package/dist/cjs/components/Branding.js +41 -0
- package/dist/cjs/components/Hamburger.js +40 -0
- package/dist/cjs/components/Link.js +21 -0
- package/dist/cjs/components/Logo.js +25 -0
- package/dist/cjs/components/NavBar.js +101 -0
- package/dist/cjs/components/NavBarItem.js +42 -0
- package/dist/cjs/components/NavBarRoutes.js +47 -0
- package/dist/cjs/components/ProgressBar.js +14 -0
- package/dist/cjs/components/User.js +71 -0
- package/dist/cjs/containers/Content.js +16 -0
- package/dist/cjs/containers/ErrorScreen.js +35 -0
- package/dist/cjs/containers/LoadingScreen.js +84 -0
- package/dist/cjs/containers/PageErrorBoundary.js +43 -0
- package/dist/cjs/containers/PageLoader.js +123 -0
- package/dist/cjs/contexts/ApiContext.js +105 -0
- package/dist/cjs/contexts/I18nContext.js +109 -0
- package/dist/cjs/contexts/RouterContext.js +99 -0
- package/dist/cjs/contexts/UserContext.js +144 -0
- package/dist/cjs/extensions/bananas/components/PasswordChangeForm.js +85 -0
- package/dist/cjs/extensions/bananas/index.js +54 -0
- package/dist/cjs/extensions/bananas/pages/me/list.js +20 -0
- package/dist/cjs/extensions/pos/components/PurchaseRow.js +32 -0
- package/dist/cjs/extensions/pos/components/ReceiptCard.js +86 -0
- package/dist/cjs/extensions/pos/components/ReceiptLine.js +29 -0
- package/dist/cjs/extensions/pos/index.js +22 -0
- package/dist/cjs/extensions/pos/pages/purchase/detail.js +13 -0
- package/dist/cjs/extensions/pos/pages/purchase/list.js +34 -0
- package/dist/cjs/extensions/pos/types/purchase.js +2 -0
- package/dist/cjs/extensions/pos/types/receipt.js +2 -0
- package/dist/cjs/forms/LoginForm.js +63 -0
- package/dist/cjs/hooks/useAsyncError.js +15 -0
- package/dist/cjs/hooks/useLocalStorage.js +47 -0
- package/dist/cjs/index.js +40 -0
- package/dist/cjs/pages/DashboardPage.js +10 -0
- package/dist/cjs/pages/LoginPage.js +31 -0
- package/dist/cjs/router/Router.js +35 -0
- package/dist/cjs/router/routes.js +57 -0
- package/dist/cjs/types/index.js +2 -0
- package/dist/cjs/util/get_cookie.js +10 -0
- package/dist/cjs/util/index.js +62 -0
- package/dist/cjs/util/select_styles.js +38 -0
- package/dist/esm/Admin.js +42 -0
- package/dist/esm/App.js +44 -0
- package/dist/esm/api.js +219 -0
- package/dist/esm/components/Branding.js +36 -0
- package/dist/esm/components/Hamburger.js +35 -0
- package/dist/esm/components/Link.js +16 -0
- package/dist/esm/components/Logo.js +20 -0
- package/dist/esm/components/NavBar.js +73 -0
- package/dist/esm/components/NavBarItem.js +37 -0
- package/dist/esm/components/NavBarRoutes.js +42 -0
- package/dist/esm/components/ProgressBar.js +9 -0
- package/dist/esm/components/User.js +66 -0
- package/dist/esm/containers/Content.js +11 -0
- package/dist/esm/containers/ErrorScreen.js +30 -0
- package/dist/esm/containers/LoadingScreen.js +79 -0
- package/dist/esm/containers/PageErrorBoundary.js +38 -0
- package/dist/esm/containers/PageLoader.js +117 -0
- package/dist/esm/contexts/ApiContext.js +77 -0
- package/dist/esm/contexts/I18nContext.js +77 -0
- package/dist/esm/contexts/RouterContext.js +71 -0
- package/dist/esm/contexts/UserContext.js +113 -0
- package/dist/esm/extensions/bananas/components/PasswordChangeForm.js +80 -0
- package/dist/esm/extensions/bananas/index.js +48 -0
- package/dist/esm/extensions/bananas/pages/me/list.js +15 -0
- package/dist/esm/extensions/pos/components/PurchaseRow.js +25 -0
- package/dist/esm/extensions/pos/components/ReceiptCard.js +56 -0
- package/dist/esm/extensions/pos/components/ReceiptLine.js +22 -0
- package/dist/esm/extensions/pos/index.js +16 -0
- package/dist/esm/extensions/pos/pages/purchase/detail.js +8 -0
- package/dist/esm/extensions/pos/pages/purchase/list.js +29 -0
- package/dist/esm/extensions/pos/types/purchase.js +1 -0
- package/dist/esm/extensions/pos/types/receipt.js +1 -0
- package/dist/esm/forms/LoginForm.js +58 -0
- package/dist/esm/hooks/useAsyncError.js +9 -0
- package/dist/esm/hooks/useLocalStorage.js +41 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/pages/DashboardPage.js +5 -0
- package/dist/esm/pages/LoginPage.js +26 -0
- package/dist/esm/router/Router.js +28 -0
- package/dist/esm/router/routes.js +49 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/util/get_cookie.js +6 -0
- package/dist/esm/util/index.js +54 -0
- package/dist/esm/util/select_styles.js +34 -0
- package/dist/types/Admin.d.ts +13 -0
- package/dist/types/App.d.ts +13 -0
- package/dist/types/api.d.ts +20 -0
- package/dist/types/components/Branding.d.ts +14 -0
- package/dist/types/components/Hamburger.d.ts +8 -0
- package/dist/types/components/Link.d.ts +9 -0
- package/dist/types/components/Logo.d.ts +7 -0
- package/dist/types/components/NavBar.d.ts +11 -0
- package/dist/types/components/NavBarItem.d.ts +12 -0
- package/dist/types/components/NavBarRoutes.d.ts +7 -0
- package/dist/types/components/ProgressBar.d.ts +7 -0
- package/dist/types/components/User.d.ts +824 -0
- package/dist/types/containers/Content.d.ts +3 -0
- package/dist/types/containers/ErrorScreen.d.ts +7 -0
- package/dist/types/containers/LoadingScreen.d.ts +50 -0
- package/dist/types/containers/PageErrorBoundary.d.ts +16 -0
- package/dist/types/containers/PageLoader.d.ts +13 -0
- package/dist/types/contexts/ApiContext.d.ts +12 -0
- package/dist/types/contexts/I18nContext.d.ts +10 -0
- package/dist/types/contexts/RouterContext.d.ts +24 -0
- package/dist/types/contexts/UserContext.d.ts +20 -0
- package/dist/types/extensions/bananas/components/PasswordChangeForm.d.ts +3 -0
- package/dist/types/extensions/bananas/index.d.ts +2 -0
- package/dist/types/extensions/bananas/pages/me/list.d.ts +6 -0
- package/dist/types/extensions/pos/components/PurchaseRow.d.ts +7 -0
- package/dist/types/extensions/pos/components/ReceiptCard.d.ts +7 -0
- package/dist/types/extensions/pos/components/ReceiptLine.d.ts +7 -0
- package/dist/types/extensions/pos/index.d.ts +2 -0
- package/dist/types/extensions/pos/pages/purchase/detail.d.ts +7 -0
- package/dist/types/extensions/pos/pages/purchase/list.d.ts +9 -0
- package/dist/types/extensions/pos/types/purchase.d.ts +18 -0
- package/dist/types/extensions/pos/types/receipt.d.ts +34 -0
- package/dist/types/forms/LoginForm.d.ts +3 -0
- package/dist/types/hooks/useAsyncError.d.ts +1 -0
- package/dist/types/hooks/useLocalStorage.d.ts +2 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/pages/DashboardPage.d.ts +5 -0
- package/dist/types/pages/LoginPage.d.ts +9 -0
- package/dist/types/router/Router.d.ts +13 -0
- package/dist/types/router/routes.d.ts +20 -0
- package/dist/types/types/index.d.ts +6 -0
- package/dist/types/util/get_cookie.d.ts +1 -0
- package/dist/types/util/index.d.ts +8 -0
- package/dist/types/util/select_styles.d.ts +3 -0
- package/example/Dockerfile +27 -0
- package/example/docker-compose.yml +7 -0
- package/example/index.html +13 -0
- package/example/index.tsx +21 -0
- package/example/package-lock.json +13167 -0
- package/example/package.json +52 -0
- package/example/pages/.gitkeep +0 -0
- package/example/webpack.config.js +67 -0
- package/package.json +52 -0
- package/src/Admin.tsx +94 -0
- package/src/App.tsx +43 -0
- package/src/api.ts +202 -0
- package/src/components/Branding.tsx +79 -0
- package/src/components/Hamburger.tsx +41 -0
- package/src/components/Link.tsx +23 -0
- package/src/components/Logo.tsx +57 -0
- package/src/components/NavBar.tsx +115 -0
- package/src/components/NavBarItem.tsx +73 -0
- package/src/components/NavBarRoutes.tsx +67 -0
- package/src/components/ProgressBar.tsx +30 -0
- package/src/components/User.tsx +97 -0
- package/src/containers/Content.tsx +18 -0
- package/src/containers/ErrorScreen.tsx +64 -0
- package/src/containers/LoadingScreen.tsx +135 -0
- package/src/containers/PageErrorBoundary.tsx +32 -0
- package/src/containers/PageLoader.tsx +64 -0
- package/src/contexts/ApiContext.tsx +55 -0
- package/src/contexts/I18nContext.tsx +55 -0
- package/src/contexts/RouterContext.tsx +127 -0
- package/src/contexts/UserContext.tsx +99 -0
- package/src/extensions/bananas/components/PasswordChangeForm.tsx +138 -0
- package/src/extensions/bananas/index.ts +14 -0
- package/src/extensions/bananas/pages/me/list.tsx +31 -0
- package/src/extensions/pos/components/PurchaseRow.tsx +42 -0
- package/src/extensions/pos/components/ReceiptCard.tsx +101 -0
- package/src/extensions/pos/components/ReceiptLine.tsx +51 -0
- package/src/extensions/pos/index.tsx +20 -0
- package/src/extensions/pos/pages/purchase/detail.tsx +22 -0
- package/src/extensions/pos/pages/purchase/list.tsx +56 -0
- package/src/extensions/pos/types/purchase.ts +20 -0
- package/src/extensions/pos/types/receipt.ts +36 -0
- package/src/forms/LoginForm.tsx +99 -0
- package/src/hooks/useAsyncError.ts +13 -0
- package/src/hooks/useLocalStorage.ts +42 -0
- package/src/index.ts +18 -0
- package/src/pages/DashboardPage.tsx +9 -0
- package/src/pages/LoginPage.tsx +50 -0
- package/src/router/Router.tsx +63 -0
- package/src/router/routes.ts +67 -0
- package/src/types/index.ts +4 -0
- package/src/types/swagger-client.d.ts +1 -0
- package/src/util/get_cookie.ts +6 -0
- package/src/util/index.ts +63 -0
- package/src/util/select_styles.ts +29 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bananas-commerce-admin-example",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Bananas-commerce admin interface and building blocks",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"admin",
|
|
8
|
+
"bananas",
|
|
9
|
+
"django",
|
|
10
|
+
"django-admin",
|
|
11
|
+
"react",
|
|
12
|
+
"bananas-commerce",
|
|
13
|
+
"bananas-commerce-admin"
|
|
14
|
+
],
|
|
15
|
+
"author": "Elias Sjögreen",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "webpack serve --config webpack.config.js",
|
|
18
|
+
"build": "webpack --config webpack.config.js"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"stream-http": "^3.2.0",
|
|
22
|
+
"typescript": "^4.7.4",
|
|
23
|
+
"url": "^0.11.0",
|
|
24
|
+
"util": "^0.12.4",
|
|
25
|
+
"webpack": "^5.73.0",
|
|
26
|
+
"webpack-cli": "^4.10.0",
|
|
27
|
+
"webpack-dev-server": "^4.9.3",
|
|
28
|
+
"@types/lodash": "^4.14.182",
|
|
29
|
+
"@types/node": "^16.11.43",
|
|
30
|
+
"@types/react": "^17.0.0",
|
|
31
|
+
"@types/react-dom": "^17.0.0",
|
|
32
|
+
"babel-loader": "^8.2.5",
|
|
33
|
+
"buffer": "^6.0.3",
|
|
34
|
+
"file-loader": "^6.2.0",
|
|
35
|
+
"html-webpack-plugin": "^5.5.0",
|
|
36
|
+
"https-browserify": "^1.0.0",
|
|
37
|
+
"node-polyfill-webpack-plugin": "^2.0.0",
|
|
38
|
+
"@babel/core": "^7.18.6",
|
|
39
|
+
"@babel/preset-env": "^7.18.6",
|
|
40
|
+
"@babel/preset-react": "^7.18.6",
|
|
41
|
+
"@babel/preset-typescript": "^7.18.6"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@mui/icons-material": "^5.8.4",
|
|
45
|
+
"@mui/lab": "^5.0.0-alpha.89",
|
|
46
|
+
"@mui/material": "^5.8.7",
|
|
47
|
+
"@mui/system": "^5.8.7",
|
|
48
|
+
"bananas-commerce-admin": "file:..",
|
|
49
|
+
"react": "^17.0.0",
|
|
50
|
+
"react-dom": "^17.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
2
|
+
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
context: __dirname,
|
|
7
|
+
mode: "development",
|
|
8
|
+
entry: "./index.tsx",
|
|
9
|
+
output: {
|
|
10
|
+
publicPath: "/",
|
|
11
|
+
chunkFilename: "[name].[chunkhash].js",
|
|
12
|
+
filename: "[name].bundle.js",
|
|
13
|
+
path: path.resolve(__dirname, "dist"),
|
|
14
|
+
clean: true,
|
|
15
|
+
},
|
|
16
|
+
plugins: [
|
|
17
|
+
new HtmlWebpackPlugin({
|
|
18
|
+
filename: "index.html",
|
|
19
|
+
template: "./index.html",
|
|
20
|
+
}),
|
|
21
|
+
new NodePolyfillPlugin(),
|
|
22
|
+
],
|
|
23
|
+
resolve: {
|
|
24
|
+
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
|
25
|
+
},
|
|
26
|
+
module: {
|
|
27
|
+
rules: [
|
|
28
|
+
{
|
|
29
|
+
test: /\.m?[jt]sx?$/,
|
|
30
|
+
exclude: /(node_modules|bower_components)/,
|
|
31
|
+
use: {
|
|
32
|
+
loader: "babel-loader",
|
|
33
|
+
options: {
|
|
34
|
+
presets: [
|
|
35
|
+
"@babel/preset-env",
|
|
36
|
+
["@babel/preset-react", { runtime: "automatic" }],
|
|
37
|
+
"@babel/preset-typescript",
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
test: /\.(png|jpg|gif|svg)$/,
|
|
44
|
+
use: {
|
|
45
|
+
loader: "file-loader",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
optimization: {
|
|
51
|
+
nodeEnv: "development",
|
|
52
|
+
},
|
|
53
|
+
devServer: {
|
|
54
|
+
allowedHosts: "all",
|
|
55
|
+
hot: true,
|
|
56
|
+
compress: true,
|
|
57
|
+
host: "0.0.0.0",
|
|
58
|
+
port: 8123,
|
|
59
|
+
historyApiFallback: true,
|
|
60
|
+
proxy: {
|
|
61
|
+
"/_": "http://localhost:8000",
|
|
62
|
+
"/api": "http://localhost:8000",
|
|
63
|
+
"/private": "http://localhost:8000",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
devtool: "eval-source-map",
|
|
67
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bananas-commerce-admin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bananas-commerce admin interface and building blocks",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"admin",
|
|
7
|
+
"bananas",
|
|
8
|
+
"django",
|
|
9
|
+
"django-admin",
|
|
10
|
+
"react",
|
|
11
|
+
"bananas-commerce",
|
|
12
|
+
"bananas-commerce-admin"
|
|
13
|
+
],
|
|
14
|
+
"author": "Elias Sjögreen",
|
|
15
|
+
"module": "./dist/esm/index.js",
|
|
16
|
+
"main": "./dist/cjs/index.js",
|
|
17
|
+
"types": "./dist/types/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/esm/index.js",
|
|
21
|
+
"require": "./dist/cjs/index.js",
|
|
22
|
+
"types": "./dist/types/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "npm run build:esm && npm run build:cjs",
|
|
27
|
+
"build:esm": "tsc",
|
|
28
|
+
"build:esm:watch": "npm run build:esm -- --watch",
|
|
29
|
+
"build:cjs": "tsc --module commonjs --outDir ./dist/cjs",
|
|
30
|
+
"build:cjs:watch": "npm run build:cjs -- --watch"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/lodash": "^4.14.182",
|
|
34
|
+
"@types/node": "^16.11.43",
|
|
35
|
+
"@types/react": "^17.0.0",
|
|
36
|
+
"@types/react-dom": "^17.0.0",
|
|
37
|
+
"typescript": "^4.7.4",
|
|
38
|
+
"openapi-types": "^12.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@apidevtools/swagger-parser": "^10.1.0",
|
|
42
|
+
"@mui/icons-material": "^5.8.4",
|
|
43
|
+
"@mui/lab": "^5.0.0-alpha.89",
|
|
44
|
+
"@mui/material": "^5.8.7",
|
|
45
|
+
"@mui/system": "^5.8.7",
|
|
46
|
+
"notistack": "^2.0.5",
|
|
47
|
+
"react": "^17.0.0",
|
|
48
|
+
"react-router-dom": "^6.3.0",
|
|
49
|
+
"@emotion/react": "^11.10.4",
|
|
50
|
+
"@emotion/styled": "^11.10.4"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/Admin.tsx
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { BrowserRouter } from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
|
|
6
|
+
import NavBar from "./components/NavBar";
|
|
7
|
+
import LoadingScreen from "./containers/LoadingScreen";
|
|
8
|
+
import { useApi } from "./contexts/ApiContext";
|
|
9
|
+
import { useI18n } from "./contexts/I18nContext";
|
|
10
|
+
import { RouterContextProvider } from "./contexts/RouterContext";
|
|
11
|
+
import { useUser } from "./contexts/UserContext";
|
|
12
|
+
import LoginPage from "./pages/LoginPage";
|
|
13
|
+
import { Router, RouterProps } from "./router/Router";
|
|
14
|
+
import { LogoType } from "./types";
|
|
15
|
+
|
|
16
|
+
export type AdminProps = {
|
|
17
|
+
logo?: LogoType;
|
|
18
|
+
title?: string;
|
|
19
|
+
subtitle?: string;
|
|
20
|
+
version?: string;
|
|
21
|
+
nav: Record<string, React.ReactNode>;
|
|
22
|
+
basename?: string;
|
|
23
|
+
} & RouterProps;
|
|
24
|
+
|
|
25
|
+
const Admin: React.FC<AdminProps> = (
|
|
26
|
+
{
|
|
27
|
+
logo,
|
|
28
|
+
title,
|
|
29
|
+
subtitle,
|
|
30
|
+
version,
|
|
31
|
+
nav,
|
|
32
|
+
pages,
|
|
33
|
+
extensions,
|
|
34
|
+
dashboard,
|
|
35
|
+
basename,
|
|
36
|
+
},
|
|
37
|
+
) => {
|
|
38
|
+
const [loaded, setLoaded] = React.useState(false);
|
|
39
|
+
|
|
40
|
+
const api = useApi();
|
|
41
|
+
const i18n = useI18n();
|
|
42
|
+
const { user } = useUser();
|
|
43
|
+
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (
|
|
46
|
+
!loaded && api !== undefined && i18n !== undefined && user !== undefined
|
|
47
|
+
) {
|
|
48
|
+
setLoaded(true);
|
|
49
|
+
}
|
|
50
|
+
}, [loaded, api, i18n, user]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<Box
|
|
55
|
+
sx={{
|
|
56
|
+
display: "flex",
|
|
57
|
+
position: "fixed",
|
|
58
|
+
top: 0,
|
|
59
|
+
left: 0,
|
|
60
|
+
width: "100%",
|
|
61
|
+
height: "100%",
|
|
62
|
+
flexDirection: "column",
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{loaded
|
|
66
|
+
? user !== null
|
|
67
|
+
? (
|
|
68
|
+
<BrowserRouter basename={basename}>
|
|
69
|
+
<RouterContextProvider>
|
|
70
|
+
<Box sx={{ display: "flex" }}>
|
|
71
|
+
<NavBar
|
|
72
|
+
nav={nav}
|
|
73
|
+
logo={logo}
|
|
74
|
+
title={title}
|
|
75
|
+
subtitle={subtitle}
|
|
76
|
+
version={version}
|
|
77
|
+
/>
|
|
78
|
+
<Router
|
|
79
|
+
pages={pages}
|
|
80
|
+
extensions={extensions}
|
|
81
|
+
dashboard={dashboard}
|
|
82
|
+
/>
|
|
83
|
+
</Box>
|
|
84
|
+
</RouterContextProvider>
|
|
85
|
+
</BrowserRouter>
|
|
86
|
+
)
|
|
87
|
+
: <LoginPage logo={logo} title={title} />
|
|
88
|
+
: <LoadingScreen logo={logo} loading={!loaded} />}
|
|
89
|
+
</Box>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default Admin;
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import CssBaseline from "@mui/material/CssBaseline";
|
|
4
|
+
import { createTheme, Theme, ThemeProvider } from "@mui/material/styles";
|
|
5
|
+
import Admin, { AdminProps } from "./Admin";
|
|
6
|
+
import { ApiContextProvider } from "./contexts/ApiContext";
|
|
7
|
+
import { ApiClient } from "./api";
|
|
8
|
+
import { UserContextProvider } from "./contexts/UserContext";
|
|
9
|
+
import { I18nContextProvider } from "./contexts/I18nContext";
|
|
10
|
+
import { SnackbarProvider } from "notistack";
|
|
11
|
+
|
|
12
|
+
type AppProps = {
|
|
13
|
+
api: ApiClient | string | URL | {
|
|
14
|
+
schema: string | URL;
|
|
15
|
+
server?: string | URL;
|
|
16
|
+
};
|
|
17
|
+
theme?: Partial<Theme> | ((outerTheme: Theme) => Theme);
|
|
18
|
+
} & AdminProps;
|
|
19
|
+
|
|
20
|
+
const App: React.FC<AppProps> = ({ theme, api, ...rest }) => {
|
|
21
|
+
return (
|
|
22
|
+
<ThemeProvider theme={theme ?? createTheme()}>
|
|
23
|
+
<CssBaseline />
|
|
24
|
+
<SnackbarProvider
|
|
25
|
+
maxSnack={4}
|
|
26
|
+
anchorOrigin={{
|
|
27
|
+
vertical: "bottom",
|
|
28
|
+
horizontal: "right",
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<ApiContextProvider api={api}>
|
|
32
|
+
<I18nContextProvider>
|
|
33
|
+
<UserContextProvider>
|
|
34
|
+
<Admin {...rest} />
|
|
35
|
+
</UserContextProvider>
|
|
36
|
+
</I18nContextProvider>
|
|
37
|
+
</ApiContextProvider>
|
|
38
|
+
</SnackbarProvider>
|
|
39
|
+
</ThemeProvider>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default App;
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
2
|
+
import { OpenAPI } from "openapi-types";
|
|
3
|
+
import { getCookie } from "./util/get_cookie";
|
|
4
|
+
|
|
5
|
+
export class ApiOperation {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly tags: string[];
|
|
8
|
+
readonly server: string;
|
|
9
|
+
readonly endpoint: string;
|
|
10
|
+
readonly method: string;
|
|
11
|
+
readonly request: OpenAPI.Request;
|
|
12
|
+
readonly summary?: string;
|
|
13
|
+
readonly description?: string;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
id: string,
|
|
17
|
+
tags: string[],
|
|
18
|
+
server: string,
|
|
19
|
+
endpoint: string,
|
|
20
|
+
method: string,
|
|
21
|
+
request: OpenAPI.Request,
|
|
22
|
+
summary?: string,
|
|
23
|
+
description?: string,
|
|
24
|
+
) {
|
|
25
|
+
this.id = id;
|
|
26
|
+
this.tags = tags;
|
|
27
|
+
this.server = server;
|
|
28
|
+
this.endpoint = endpoint;
|
|
29
|
+
this.method = method;
|
|
30
|
+
this.request = request;
|
|
31
|
+
this.summary = summary;
|
|
32
|
+
this.description = description;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async call(request?: OpenAPI.Request, init?: RequestInit) {
|
|
36
|
+
let endpoint = this.endpoint;
|
|
37
|
+
const csrftoken = getCookie("csrftoken");
|
|
38
|
+
|
|
39
|
+
init ??= {};
|
|
40
|
+
init.method ??= this.method;
|
|
41
|
+
init.headers = new Headers(init.headers);
|
|
42
|
+
if (csrftoken !== undefined) init.headers.append("X-CSRFToken", csrftoken);
|
|
43
|
+
init.credentials = "include";
|
|
44
|
+
|
|
45
|
+
// Add body to request
|
|
46
|
+
if (request?.body !== undefined) {
|
|
47
|
+
init.body ??= JSON.stringify(request?.body);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add params to url
|
|
51
|
+
if (request?.params !== undefined) {
|
|
52
|
+
for (const [name, value] of Object.entries(request.params)) {
|
|
53
|
+
endpoint = endpoint.replace(`{${name}}`, value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const url = new URL(this.server + endpoint);
|
|
58
|
+
|
|
59
|
+
// Add query to url
|
|
60
|
+
if (request?.query !== undefined) {
|
|
61
|
+
for (const [name, value] of Object.entries(request.query)) {
|
|
62
|
+
url.searchParams.append(name, value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return await fetch(url, init);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class ApiClient {
|
|
71
|
+
readonly document: OpenAPI.Document;
|
|
72
|
+
readonly operations: Record<string, ApiOperation> = {};
|
|
73
|
+
|
|
74
|
+
static async load(source: string | URL, server?: string | URL) {
|
|
75
|
+
const response = await fetch(source);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(`Could not load schema from ${source}`);
|
|
78
|
+
}
|
|
79
|
+
const schema = await response.json().catch((e) => {
|
|
80
|
+
throw e;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const document = await SwaggerParser.dereference(schema);
|
|
84
|
+
return new ApiClient(document, server);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
constructor(document: OpenAPI.Document, server?: string | URL) {
|
|
88
|
+
this.document = document;
|
|
89
|
+
|
|
90
|
+
// If the document uses servers and it is not manually set this will be prepended to path later
|
|
91
|
+
if (
|
|
92
|
+
"servers" in this.document && this.document.servers !== undefined &&
|
|
93
|
+
this.document.servers.length >= 1
|
|
94
|
+
) {
|
|
95
|
+
server ??= this.document.servers.pop()!.url;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Make sure the server variable is an URL
|
|
99
|
+
if (server !== undefined && !(server instanceof URL)) {
|
|
100
|
+
server = new URL(server);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Build operations
|
|
104
|
+
for (
|
|
105
|
+
const [path, definition] of Object.entries(
|
|
106
|
+
this.document.paths ?? {},
|
|
107
|
+
) as [string, Record<string, OpenAPI.Operation>][]
|
|
108
|
+
) {
|
|
109
|
+
for (const [method, operation] of Object.entries(definition)) {
|
|
110
|
+
// All operations require an operation id
|
|
111
|
+
if (!operation.operationId) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if an tag starting with `app:` exists on the operation
|
|
116
|
+
if (!operation.tags?.some((tag) => tag.startsWith("app:"))) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const request: OpenAPI.Request = {};
|
|
121
|
+
|
|
122
|
+
// Fill the request object with `path`, `query` and `body` data which may be provided
|
|
123
|
+
for (
|
|
124
|
+
const parameter of operation.parameters ?? [] as OpenAPI.Parameter[]
|
|
125
|
+
) {
|
|
126
|
+
if ("in" in parameter) {
|
|
127
|
+
switch (parameter.in) {
|
|
128
|
+
case "body": {
|
|
129
|
+
request["body"] = {
|
|
130
|
+
required: parameter.required ?? false,
|
|
131
|
+
schema: parameter.schema,
|
|
132
|
+
};
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "path": {
|
|
137
|
+
request["params"] ??= {};
|
|
138
|
+
request["params"][parameter.name] = {
|
|
139
|
+
// Path parameters are always required: https://swagger.io/docs/specification/describing-parameters/#path-parameters
|
|
140
|
+
required: true,
|
|
141
|
+
schema: parameter.schema,
|
|
142
|
+
};
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case "query": {
|
|
147
|
+
request["query"] ??= {};
|
|
148
|
+
request["query"][parameter.name] = {
|
|
149
|
+
required: parameter.required ?? false,
|
|
150
|
+
schema: parameter.schema,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If `requestBody` exists then add it to the request objects body object
|
|
158
|
+
if ("requestBody" in operation && operation.requestBody !== undefined) {
|
|
159
|
+
if ("content" in operation.requestBody) {
|
|
160
|
+
const schema =
|
|
161
|
+
operation.requestBody.content["application/json"].schema;
|
|
162
|
+
const required = operation.requestBody.required ?? false;
|
|
163
|
+
|
|
164
|
+
request["body"] = {
|
|
165
|
+
schema,
|
|
166
|
+
required,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Combine base url and path
|
|
172
|
+
server = server ? server.toString() : "";
|
|
173
|
+
if (server.endsWith("/")) {
|
|
174
|
+
server = server.slice(0, -1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const endpoint = path.startsWith("/") ? path : "/" + path;
|
|
178
|
+
|
|
179
|
+
// Create a new `ApiOperation` with the relevant information
|
|
180
|
+
this.operations[operation.operationId] = new ApiOperation(
|
|
181
|
+
operation.operationId,
|
|
182
|
+
operation.tags,
|
|
183
|
+
server,
|
|
184
|
+
endpoint,
|
|
185
|
+
method,
|
|
186
|
+
request,
|
|
187
|
+
operation.summary,
|
|
188
|
+
operation.description,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async isAuthenticated() {
|
|
195
|
+
try {
|
|
196
|
+
const response = await this.operations["bananas.me:list"].call();
|
|
197
|
+
return response.ok;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import ButtonBase from "@mui/material/ButtonBase";
|
|
5
|
+
import { SxProps } from "@mui/material";
|
|
6
|
+
import { Theme } from "@mui/material/styles";
|
|
7
|
+
import Typography from "@mui/material/Typography";
|
|
8
|
+
|
|
9
|
+
import { ss } from "../util/select_styles";
|
|
10
|
+
import Logo from "./Logo";
|
|
11
|
+
import { LogoType } from "../types";
|
|
12
|
+
|
|
13
|
+
interface BrandingProps {
|
|
14
|
+
logo?: LogoType;
|
|
15
|
+
title?: string;
|
|
16
|
+
subtitle?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
onClick?: React.MouseEventHandler;
|
|
19
|
+
fullWidth?: boolean;
|
|
20
|
+
sx?: SxProps;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const Branding: React.FC<BrandingProps> = (
|
|
24
|
+
{ logo, title, subtitle, version, onClick, fullWidth, sx },
|
|
25
|
+
) => {
|
|
26
|
+
return (
|
|
27
|
+
<Box
|
|
28
|
+
sx={ss(
|
|
29
|
+
{
|
|
30
|
+
display: "flex",
|
|
31
|
+
flexDirection: "row",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
flexGrow: 1,
|
|
34
|
+
},
|
|
35
|
+
[{ width: "100%" }, fullWidth ?? false],
|
|
36
|
+
sx,
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<ButtonBase
|
|
40
|
+
sx={{
|
|
41
|
+
justifyContent: "flex-start",
|
|
42
|
+
width: "100%",
|
|
43
|
+
height: "100%",
|
|
44
|
+
padding: 0,
|
|
45
|
+
}}
|
|
46
|
+
color="inherit"
|
|
47
|
+
onClick={onClick}
|
|
48
|
+
>
|
|
49
|
+
{logo ? <Logo src={logo} /> : (
|
|
50
|
+
<Typography
|
|
51
|
+
sx={{
|
|
52
|
+
fontWeight: "bold",
|
|
53
|
+
}}
|
|
54
|
+
color="inherit"
|
|
55
|
+
variant="h4"
|
|
56
|
+
>
|
|
57
|
+
{title}
|
|
58
|
+
</Typography>
|
|
59
|
+
)}
|
|
60
|
+
<Box
|
|
61
|
+
sx={{
|
|
62
|
+
marginLeft: 10,
|
|
63
|
+
"& > *": {
|
|
64
|
+
textAlign: "left",
|
|
65
|
+
fontSize: "0.75rem",
|
|
66
|
+
display: "block",
|
|
67
|
+
lineHeight: `1em`,
|
|
68
|
+
},
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Typography color="inherit">{subtitle}</Typography>
|
|
72
|
+
<Typography color="inherit">{version}</Typography>
|
|
73
|
+
</Box>
|
|
74
|
+
</ButtonBase>
|
|
75
|
+
</Box>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default Branding;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
|
4
|
+
import MenuIcon from "@mui/icons-material/Menu";
|
|
5
|
+
import Box from "@mui/material/Box";
|
|
6
|
+
import IconButton, { IconButtonProps } from "@mui/material/IconButton";
|
|
7
|
+
import { useTheme } from "@mui/material/styles";
|
|
8
|
+
|
|
9
|
+
type HamburgerProps = {
|
|
10
|
+
open: boolean;
|
|
11
|
+
onToggle?: React.MouseEventHandler;
|
|
12
|
+
} & IconButtonProps;
|
|
13
|
+
|
|
14
|
+
const Hamburger: React.FC<HamburgerProps> = ({ open, onToggle, ...rest }) => {
|
|
15
|
+
const theme = useTheme();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Box
|
|
19
|
+
sx={{
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "flex-start",
|
|
23
|
+
padding: theme.spacing(0, 1),
|
|
24
|
+
flexShrink: 1,
|
|
25
|
+
...theme.mixins.toolbar,
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<IconButton
|
|
29
|
+
aria-label={open ? "Close drawer" : "Open drawer"}
|
|
30
|
+
color="inherit"
|
|
31
|
+
onClick={onToggle}
|
|
32
|
+
{...rest}
|
|
33
|
+
size="large"
|
|
34
|
+
>
|
|
35
|
+
{open ? <ChevronLeftIcon /> : <MenuIcon />}
|
|
36
|
+
</IconButton>
|
|
37
|
+
</Box>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default Hamburger;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RouteInfo, useRouter } from "../contexts/RouterContext";
|
|
2
|
+
import { Link as RouterLink } from "react-router-dom";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
type LinkProps = { route: RouteInfo } | { route: string };
|
|
6
|
+
|
|
7
|
+
const Link: React.FC<LinkProps> = ({ route }) => {
|
|
8
|
+
const { getRoute } = useRouter();
|
|
9
|
+
|
|
10
|
+
if (typeof route === "string") {
|
|
11
|
+
const routeInfo = getRoute(route);
|
|
12
|
+
|
|
13
|
+
if (routeInfo === undefined) {
|
|
14
|
+
throw new Error(`Could not find route with reverse: ${route}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
route = routeInfo;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <RouterLink to={route.path}></RouterLink>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default Link;
|