├── .editorconfig
├── .eslintrc
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .prettierrc.js
├── .storybook
├── addons.js
├── config.js
├── presets.js
└── theme.js
├── .vscode
├── launch.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── REFERENCES.md
├── config-overrides.js
├── netlify.toml
├── package-lock.json
├── package.json
├── public
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── background-auth.jpg
├── browserconfig.xml
├── example-company-logos
│ ├── License.txt
│ ├── a-lab.png
│ ├── asgardia.png
│ ├── atica.png
│ ├── aven.png
│ ├── circle.png
│ ├── cod-lab.png
│ ├── earth.png
│ ├── fossa.png
│ ├── fox-hub-2.png
│ ├── fox-hub.png
│ ├── hex-lab.png
│ ├── hexa.png
│ ├── ideaa.png
│ ├── kyan.png
│ ├── leaf.png
│ ├── lighting.png
│ ├── muszica.png
│ ├── nira.png
│ ├── solaytic.png
│ ├── tower.png
│ ├── towers.png
│ ├── treva.png
│ ├── tvit.png
│ ├── u-mark.png
│ └── volicity-9.png
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── index.html
├── logo-storybook.png
├── logo.png
├── logo.svg
├── mockServiceWorker.js
├── mstile-150x150.png
├── robots.txt
├── safari-pinned-tab.svg
└── site.webmanifest
├── src
├── Account
│ ├── Account.js
│ ├── Organization
│ │ └── Organization.stories.mdx
│ ├── Profile
│ │ ├── Profile.stories.mdx
│ │ └── index.js
│ └── index.js
├── Administration
│ ├── Administration.stories.mdx
│ ├── Administration.tsx
│ ├── Users
│ │ ├── UsersEditor
│ │ │ ├── UsersEditor.tsx
│ │ │ └── index.ts
│ │ └── UsersList
│ │ │ ├── UsersList.tsx
│ │ │ ├── UsersListActions.tsx
│ │ │ ├── UsersListTableItems.tsx
│ │ │ ├── index.ts
│ │ │ └── usersListContext.ts
│ └── index.ts
├── App.stories.mdx
├── App.test.tsx
├── App.tsx
├── AppRouter.tsx
├── Auth
│ ├── Auth.stories.mdx
│ ├── Auth.tsx
│ ├── Login
│ │ ├── Login.stories.mdx
│ │ ├── Login.tsx
│ │ └── index.ts
│ ├── Recover
│ │ ├── Recover.stories.mdx
│ │ ├── Recover.tsx
│ │ └── index.ts
│ ├── Reset
│ │ ├── Reset.stories.mdx
│ │ ├── Reset.tsx
│ │ └── index.ts
│ ├── Signup
│ │ ├── Signup.stories.mdx
│ │ ├── Signup.tsx
│ │ └── index.ts
│ ├── _common
│ │ ├── AuthContent
│ │ │ ├── AuthContent.tsx
│ │ │ └── index.ts
│ │ ├── AuthFooter
│ │ │ ├── AuthFooter.tsx
│ │ │ └── index.ts
│ │ └── AuthHeader
│ │ │ ├── AuthHeader.tsx
│ │ │ └── index.ts
│ └── index.ts
├── Dashboard
│ ├── Dashboard.stories.mdx
│ ├── Dashboard.tsx
│ ├── DashboardActions.tsx
│ ├── KeyMetrics
│ │ ├── KeyMetrics.test.tsx
│ │ ├── KeyMetrics.tsx
│ │ ├── data.ts
│ │ └── index.ts
│ ├── SubscriptionsBreakdown
│ │ ├── SubscriptionsBreakdown.tsx
│ │ ├── data.ts
│ │ └── index.ts
│ ├── SubscriptionsHistory
│ │ ├── SubscriptionsHistory.tsx
│ │ ├── data.ts
│ │ └── index.ts
│ ├── SubscriptionsRecent
│ │ ├── SubscriptionsRecent.tsx
│ │ ├── data.ts
│ │ └── index.ts
│ ├── dashboardContext.ts
│ └── index.ts
├── Settings
│ ├── README.md
│ ├── Settings.stories.mdx
│ └── index.ts
├── _api
│ ├── _mocks
│ │ ├── _data
│ │ │ ├── notificationsData.ts
│ │ │ ├── organizationsData.ts
│ │ │ ├── subscriptionPlansData.ts
│ │ │ ├── usersData.ts
│ │ │ └── usersToOrganizationsData.ts
│ │ ├── _ref
│ │ │ ├── index.ts
│ │ │ ├── organizationsMocksAxios.ts
│ │ │ └── usersMocksAxios.ts
│ │ ├── index.ts
│ │ ├── organizationsMocks.ts
│ │ ├── plansMocks.ts
│ │ └── usersMocks.ts
│ ├── _types
│ │ ├── Entity.ts
│ │ ├── Organization.ts
│ │ ├── SubscriptionPlan.ts
│ │ ├── User.ts
│ │ └── UserToOrganization.ts
│ ├── client.ts
│ ├── index.ts
│ ├── organizations.ts
│ └── users.ts
├── _common
│ ├── AppFooter
│ │ ├── AppFooter.stories.mdx
│ │ ├── AppFooter.tsx
│ │ └── index.ts
│ ├── AppHeader
│ │ ├── AppHeader.stories.mdx
│ │ ├── AppHeader.tsx
│ │ ├── AppHeaderDemoButtons.tsx
│ │ ├── AppHeaderNotifications.tsx
│ │ ├── AppHeaderProfile.tsx
│ │ ├── AppHeaderSearch.tsx
│ │ └── index.ts
│ ├── AppSidebar
│ │ ├── AppSidebar.stories.mdx
│ │ ├── AppSidebar.test.tsx
│ │ ├── AppSidebar.tsx
│ │ ├── SidebarNav
│ │ │ ├── SidebarNav.test.tsx
│ │ │ ├── SidebarNav.tsx
│ │ │ ├── SidebarNavListItem.tsx
│ │ │ ├── SidebarNavService.ts
│ │ │ └── index.ts
│ │ ├── SidebarNavRecursive
│ │ │ ├── NavItem.tsx
│ │ │ ├── NavItemComponent.tsx
│ │ │ ├── NavList.tsx
│ │ │ ├── SidebarNav.tsx
│ │ │ └── index.ts
│ │ ├── _assets
│ │ │ └── AppSidebarBg.jpg
│ │ └── index.ts
│ ├── BaseLogo
│ │ ├── BaseLogo.tsx
│ │ └── index.ts
│ ├── BasePageContainer
│ │ ├── BasePageContainer.tsx
│ │ └── index.ts
│ ├── BasePageToolbar
│ │ ├── BasePageToolbar.tsx
│ │ └── index.ts
│ ├── BaseTable
│ │ ├── BaseTablePagination.tsx
│ │ └── index.ts
│ └── BaseTitle
│ │ ├── BaseTitle.tsx
│ │ └── index.ts
├── _config
│ └── index.ts
├── _layouts
│ ├── DashboardLayout
│ │ ├── DashboardLayout.stories.mdx
│ │ ├── DashboardLayout.tsx
│ │ └── index.ts
│ └── index.ts
├── _ref
│ └── AppRouter.js.bak
├── _services
│ ├── authService.js
│ └── utilsService.js
├── _tests
│ └── index.tsx
├── _theme
│ └── index.ts
├── index.tsx
└── react-app-env.d.ts
├── tsconfig.custom.json
├── tsconfig.json
└── tsconfig.to-es.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs.
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # We recommend you to keep these unchanged.
10 | charset = utf-8
11 | end_of_line = lf
12 | indent_size = 2
13 | indent_style = space
14 | insert_final_newline = true
15 | trim_trailing_whitespace = true
16 |
17 | [package.json]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "prettier/prettier": "error",
6 | "import/no-anonymous-default-export": [0]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | push:
4 | jobs:
5 | build:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 | - name: Install modules
10 | run: npm install
11 | - name: Run tests
12 | run: npm run test
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # builds
12 | build
13 | dist
14 | .rpt2_cache
15 | storybook-static
16 | .cache
17 |
18 |
19 | # misc
20 | .DS_Store
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 |
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: false,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 90,
6 | tabWidth: 2,
7 | };
8 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-knobs/register';
3 | import '@storybook/addon-storysource/register';
4 | import '@storybook/addon-a11y/register';
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | // import 'typeface-roboto'
2 |
3 | import React from 'react'
4 | import { configure, addDecorator, addParameters } from '@storybook/react'
5 | import { withA11y } from '@storybook/addon-a11y'
6 | import { themes } from '@storybook/theming';
7 | import { ThemeProvider } from '@material-ui/styles'
8 | import CssBaseline from '@material-ui/core/CssBaseline'
9 | import { BrowserRouter } from 'react-router-dom' //
10 |
11 |
12 | import docsTheme from './theme'
13 | import appTheme from '../src/_theme'
14 |
15 | // import { GlobalStyle } from '../src/components/shared/global';
16 |
17 | addParameters({
18 | options: {
19 | showRoots: true,
20 | theme: docsTheme,
21 | storySort: (a, b) => {
22 | const getPriority = (id) => {
23 | var priority = '';
24 |
25 | if (id.indexOf('general-') > -1) {
26 | priority = '0'
27 | }
28 | else {
29 | priority = 'z';
30 | }
31 |
32 | if (id.includes('--page')) {
33 | priority = priority + '0';
34 | }
35 | return priority;
36 | }
37 |
38 | const aPriority = getPriority(a[1].id);
39 | const bPriority = getPriority(b[1].id);
40 |
41 | return `${aPriority}${a[1].id}`.localeCompare(`${bPriority}${b[1].id}`);
42 | }
43 | },
44 | });
45 |
46 | addDecorator(withA11y);
47 | addDecorator(story => (
48 |
49 |
50 |
51 |
52 | {story()}
53 |
54 |
55 |
56 | ));
57 |
58 | // automatically import all files ending in *.stories.js
59 | configure(
60 | [
61 | require.context('../src', true, /\.stories\.mdx$/),
62 | require.context('../src', true, /\.stories\.js$/),
63 | ],
64 | module
65 | );
66 |
67 | // loadFontsForStorybook();
--------------------------------------------------------------------------------
/.storybook/presets.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | '@storybook/preset-create-react-app',
3 | '@storybook/addon-docs/preset'
4 | ];
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | import { create } from '@storybook/theming/create';
2 | import logo from '../public/logo-storybook.png';
3 |
4 |
5 | export default create({
6 | base: 'light',
7 | brandTitle: 'Modular Material Admin + React',
8 | // brandUrl: 'https://modular-admin.com',
9 | brandImage: logo,
10 | });
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Chrome",
6 | "type": "chrome",
7 | "request": "launch",
8 | "url": "http://localhost:3000",
9 | "webRoot": "${workspaceFolder}/src",
10 | "sourceMapPathOverrides": {
11 | "webpack:///src/*": "${webRoot}/*"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": false,
3 | "eslint.autoFixOnSave": true,
4 | "eslint.validate": [
5 | "javascript",
6 | "javascriptreact",
7 | {
8 | "language": "typescript",
9 | "autoFix": true
10 | },
11 | {
12 | "language": "typescriptreact",
13 | "autoFix": true
14 | }
15 | ],
16 | "editor.codeActionsOnSave": {
17 | "source.fixAll.eslint": true
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/CHANGELOG.md
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Gevorg Harutyunyan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Modular Admin React
2 |
3 |
4 |
5 | Free [MaterialUI](https://material-ui.com/) dashboard theme implemented by using **ReactJS** + **React Hooks** + **TypeScript** + **Context API**
6 |
7 | **(this project is experimental and in development, please use only for reference)**
8 |
9 |
10 |
11 | [](https://modular-admin-react.modularcode.io/#/)
12 |
13 |
14 |
15 |
16 | 🚀 View Demo
17 |
18 | |
19 |
20 | 🤷🏼♂️ Read The Docs
21 |
22 |
23 |
24 |
25 | ## Available Scripts
26 |
27 | In the project directory, you can run:
28 |
29 | ### `npm start`
30 |
31 | Runs the app in the development mode.
32 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
33 |
34 | The page will reload if you make edits.
35 | You will also see any lint errors in the console.
36 |
37 | ### `npm run test`
38 |
39 | Launches the test runner in the interactive watch mode.
40 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
41 |
42 | ### `npm run build`
43 |
44 | Builds the app for production to the `build` folder.
45 | It correctly bundles React in production mode and optimizes the build for the best performance.
46 |
47 | The build is minified and the filenames include the hashes.
48 | Your app is ready to be deployed!
49 |
50 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
51 |
52 |
53 |
54 | ### ToDo:
55 |
56 | - Migrate mocks to https://github.com/mswjs/msw
57 |
--------------------------------------------------------------------------------
/REFERENCES.md:
--------------------------------------------------------------------------------
1 | # Used materials references
2 |
3 |
4 | ### Company logos:
5 |
6 | uilogos.co : 25+ Dummy Logos (SVG) - uiLogos By vijay verma
7 | https://uilogos.co/
8 | http://bit.ly/2E3uMER
9 | https://gumroad.com/l/UAxnr
10 |
11 |
12 | ### Materials
13 |
14 | https://www.carlrippon.com/react-forwardref-typescript/
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | // The Webpack config to use when compiling your react app for development or production.
5 | webpack: function(config, env) {
6 | // ...add your webpack config
7 | config.resolve.alias = {
8 | ...config.resolve.alias,
9 | '@': path.resolve('./src'),
10 | }
11 |
12 | return config
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "build"
3 | command = "yarn build"
4 | [build.environment]
5 | NODE_VERSION = "10"
6 | YARN_VERSION = "1.17.3"
7 | [context.docs]
8 | publish = "storybook-static"
9 | command = "yarn build-docs"
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "modular-admin-react",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-app-rewired start",
7 | "build": "react-app-rewired build",
8 | "test": "react-app-rewired test",
9 | "deploy": "npm run build-preview && npm run build-docs && gh-pages -d build --repo git@github.com:modularcode/modular-admin-react.git --branch gh-pages",
10 | "build-preview": "cross-env PUBLIC_PATH='/' npm run build && echo modular-admin-react.modularcode.io > ./build/CNAME",
11 | "build-docs": "build-storybook --docs -s ./public -o build/docs",
12 | "build-storybook": "build-storybook",
13 | "storybook": "start-storybook -p 6060",
14 | "use:es": "symlink-dir ./src-es ./src/",
15 | "use:ts": "symlink-dir ./src-ts ./src/",
16 | "es:init": "npm run es:clean && npm run es:build && npm run es:prettify",
17 | "es:clean": "rimraf src-es/",
18 | "es:build": "tsc --project ./tsconfig.to-es.json",
19 | "es:prettify": "prettier --config ./.prettierrc.js --write \"src-es/**/*.(js|jsx|ts)\"",
20 | "eject": "react-scripts eject"
21 | },
22 | "homepage": "http://modular-admin-react.modularcode.io",
23 | "dependencies": {
24 | "@material-ui/core": "^4.11.4",
25 | "@material-ui/icons": "4.11.2",
26 | "@material-ui/lab": "^4.0.0-alpha.58",
27 | "@rehooks/component-size": "^1.0.3",
28 | "@types/lodash": "^4.14.168",
29 | "@types/react-router-dom": "^5.1.7",
30 | "@types/uuid": "^8.3.0",
31 | "axios": "^0.21.1",
32 | "axios-mock-adapter": "^1.19.0",
33 | "chart.js": "^3.2.1",
34 | "cross-env": "^7.0.3",
35 | "disqus-react": "^1.0.11",
36 | "faker": "^5.5.3",
37 | "lodash": "^4.17.21",
38 | "moment": "^2.29.1",
39 | "prop-types": "^15.7.2",
40 | "react": "^16",
41 | "react-chartjs-2": "^3.0.3",
42 | "react-dom": "^16",
43 | "react-intl": "^5.17.1",
44 | "react-router-dom": "^5.2.0",
45 | "react-script": "^2.0.5",
46 | "react-scripts": "^4.0.3",
47 | "store": "^2.0.12",
48 | "storybook-chromatic": "^4.0.2",
49 | "typeface-roboto": "1.1.13",
50 | "typescript": "^4.2.4",
51 | "uuid": "^8.3.2"
52 | },
53 | "devDependencies": {
54 | "@storybook/addon-a11y": "^6.2.9",
55 | "@storybook/addon-actions": "^6.2.9",
56 | "@storybook/addon-docs": "^6.2.9",
57 | "@storybook/addon-knobs": "^6.2.9",
58 | "@storybook/addon-storysource": "^6.2.9",
59 | "@storybook/addons": "^6.2.9",
60 | "@storybook/preset-create-react-app": "^3.1.7",
61 | "@storybook/preset-typescript": "^3.0.0",
62 | "@storybook/react": "^6.2.9",
63 | "@storybook/source-loader": "^6.2.9",
64 | "@testing-library/jest-dom": "^5.12.0",
65 | "@testing-library/react": "^11.2.6",
66 | "@types/jest": "^26.0.23",
67 | "@types/node": "^15.0.1",
68 | "@types/react": "^17.0.4",
69 | "@types/react-dom": "^17.0.3",
70 | "babel-loader": "^8.1.0",
71 | "eslint-plugin-prettier": "^3.4.0",
72 | "fork-ts-checker-webpack-plugin": "^6.2.5",
73 | "gh-pages": "^3.1.0",
74 | "jest-canvas-mock": "^2.3.1",
75 | "msw": "^0.28.2",
76 | "prettier": "2.2.1",
77 | "react-app-rewired": "^2.1.8",
78 | "react-docgen-typescript-loader": "^3.7.2",
79 | "rimraf": "^3.0.2",
80 | "symlink-dir": "^5.0.0",
81 | "ts-loader": "^9.1.1"
82 | },
83 | "eslintConfig": {
84 | "extends": "react-app"
85 | },
86 | "browserslist": {
87 | "production": [
88 | ">0.2%",
89 | "not dead",
90 | "not op_mini all"
91 | ],
92 | "development": [
93 | "last 1 chrome version",
94 | "last 1 firefox version",
95 | "last 1 safari version"
96 | ]
97 | },
98 | "jest": {
99 | "moduleNameMapper": {
100 | "@/(.*)": "/src/$1"
101 | }
102 | },
103 | "msw": {
104 | "workerDirectory": "public"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/background-auth.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/background-auth.jpg
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2b5797
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/example-company-logos/License.txt:
--------------------------------------------------------------------------------
1 | IMPORTANT NOTICE:
2 |
3 | THESE PNG FILES ARE ONLY AVAILABLE TO USE FOR DESIGN PRESENTATION/MOCKUPS AS DEMO.
4 |
5 | ---------------------------------------------------------------------------------------------------------
6 |
7 | You must attribute the image/logos to uilogos:
8 |
9 | In order to use a content or a part of it, you must attribute it to uilogos.co or realvjy,
10 | so we will be able to continue updating with new premium logos.
11 |
12 |
13 | How to attribute it?
14 |
15 | For websites:
16 |
17 | Please, copy this code on your website to accredit the author/website:
18 | Designed by realvjy
19 |
20 | Or,
21 | Downloaded from uiLogos.co
22 |
23 |
24 |
25 | You are free to use this image:
26 |
27 | - For both personal and commercial projects as dummy placeholders of your design/mockup.
28 | - In a website or presentation template or application or as part of your design/mockup.
29 |
30 |
31 | You are not allowed to:
32 |
33 | - Sub-license, resell or rent it.
34 | - Use for commercial project (For dummy logos it's ok, you can't use/release as original project).
35 |
36 |
37 | The full terms of the license are described at uilogos.co terms of use sections, available online in the following link:
38 |
39 | http://www.uilogos.co/terms_of_use
40 |
41 | The terms described in the above link have precedence over the terms described
42 | in the present document. In case of disagreement, the uilogos.co Terms of Use
43 | will prevail.
44 |
45 | ---------------------------------------------------------------------------------------------------------
46 |
47 |
48 | **updated 19-NOV-18 by http://twitter.com/realvjy **
--------------------------------------------------------------------------------
/public/example-company-logos/a-lab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/a-lab.png
--------------------------------------------------------------------------------
/public/example-company-logos/asgardia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/asgardia.png
--------------------------------------------------------------------------------
/public/example-company-logos/atica.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/atica.png
--------------------------------------------------------------------------------
/public/example-company-logos/aven.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/aven.png
--------------------------------------------------------------------------------
/public/example-company-logos/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/circle.png
--------------------------------------------------------------------------------
/public/example-company-logos/cod-lab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/cod-lab.png
--------------------------------------------------------------------------------
/public/example-company-logos/earth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/earth.png
--------------------------------------------------------------------------------
/public/example-company-logos/fossa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/fossa.png
--------------------------------------------------------------------------------
/public/example-company-logos/fox-hub-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/fox-hub-2.png
--------------------------------------------------------------------------------
/public/example-company-logos/fox-hub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/fox-hub.png
--------------------------------------------------------------------------------
/public/example-company-logos/hex-lab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/hex-lab.png
--------------------------------------------------------------------------------
/public/example-company-logos/hexa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/hexa.png
--------------------------------------------------------------------------------
/public/example-company-logos/ideaa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/ideaa.png
--------------------------------------------------------------------------------
/public/example-company-logos/kyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/kyan.png
--------------------------------------------------------------------------------
/public/example-company-logos/leaf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/leaf.png
--------------------------------------------------------------------------------
/public/example-company-logos/lighting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/lighting.png
--------------------------------------------------------------------------------
/public/example-company-logos/muszica.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/muszica.png
--------------------------------------------------------------------------------
/public/example-company-logos/nira.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/nira.png
--------------------------------------------------------------------------------
/public/example-company-logos/solaytic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/solaytic.png
--------------------------------------------------------------------------------
/public/example-company-logos/tower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/tower.png
--------------------------------------------------------------------------------
/public/example-company-logos/towers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/towers.png
--------------------------------------------------------------------------------
/public/example-company-logos/treva.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/treva.png
--------------------------------------------------------------------------------
/public/example-company-logos/tvit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/tvit.png
--------------------------------------------------------------------------------
/public/example-company-logos/u-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/u-mark.png
--------------------------------------------------------------------------------
/public/example-company-logos/volicity-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/example-company-logos/volicity-9.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
33 | Modular Material Admin | ReactJS version
34 |
35 |
36 | You need to enable JavaScript to run this app.
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/public/logo-storybook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/logo-storybook.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/logo.png
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 | Modular Material Admin + React
2 |
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Modular Admin",
3 | "short_name": "Modular Admin",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/src/Account/Account.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/src/Account/Account.js
--------------------------------------------------------------------------------
/src/Account/Organization/Organization.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Organization module
4 |
5 | Allows to manage a single organization account
6 |
7 | - Update account settings (profile picture, info, etc, account owner)
8 | - Manage account users
9 | - Preview the account
10 |
--------------------------------------------------------------------------------
/src/Account/Profile/Profile.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Profile module
4 |
5 | Allows to manage a single profile
6 |
7 | - Update profile settings (email, password, notifications, profile picture, info, etc)
8 | - Preview the profile
9 |
10 |
11 | One profile can belong to multiple accounts
12 |
--------------------------------------------------------------------------------
/src/Account/Profile/index.js:
--------------------------------------------------------------------------------
1 | const ProfileModule = {
2 | init() {},
3 | }
4 |
5 | export default ProfileModule
6 |
--------------------------------------------------------------------------------
/src/Account/index.js:
--------------------------------------------------------------------------------
1 | const AccountModule = {
2 | init() {},
3 | }
4 |
5 | export default AccountModule
6 |
--------------------------------------------------------------------------------
/src/Administration/Administration.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Admininstration module
4 |
5 | Allows to manage all system accounts and users. This module might be suitable for the platform owners/admins, who should be able to manage other accounts data
6 |
7 | - View all accounts
8 | - Edit all accounts
9 | - Veiw all users
10 | - Manage all users
11 |
--------------------------------------------------------------------------------
/src/Administration/Administration.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, match as Match } from 'react-router-dom'
3 | import UsersList from './Users/UsersList'
4 | import UserEditor from './Users/UsersEditor'
5 |
6 | export type AdministrationRouteParams = {
7 | userId: string
8 | }
9 |
10 | export type AdministrationProps = {
11 | match: Match
12 | }
13 |
14 | const Administration: React.FC = ({ match }) => {
15 | return (
16 | <>
17 |
18 | }
21 | />
22 | (
25 |
26 | )}
27 | />
28 | >
29 | )
30 | }
31 |
32 | Administration.propTypes = {}
33 |
34 | export default Administration
35 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersEditor/UsersEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, ChangeEvent, FormEvent } from 'react'
2 | import { match as Match } from 'react-router-dom'
3 | import {
4 | Input,
5 | Select,
6 | MenuItem,
7 | Paper,
8 | Button,
9 | FormControl,
10 | InputLabel,
11 | makeStyles,
12 | Grid,
13 | } from '@material-ui/core'
14 | import { Save as SaveIcon } from '@material-ui/icons/'
15 |
16 | import api from '_api'
17 |
18 | import { UserSubmissionData } from '_api/_types/User'
19 | import BasePageContainer from '_common/BasePageContainer'
20 | import BasePageToolbar from '_common/BasePageToolbar'
21 |
22 | export type UserEditorProps = {
23 | userId?: number
24 | match: Match
25 | }
26 |
27 | const UserEditor: React.FC = (props) => {
28 | const classes = useStyles()
29 |
30 | const { userId } = props
31 | const [user, setUser] = useState({
32 | avatarUrl: '',
33 | email: '',
34 | firstName: '',
35 | globalRole: '',
36 | lastName: '',
37 | username: '',
38 | })
39 |
40 | useEffect(() => {
41 | if (!userId) {
42 | return
43 | }
44 |
45 | async function fetchUser() {
46 | if (!userId) return
47 |
48 | try {
49 | const userDataRes = await api.users.getOne(userId)
50 | setUser(userDataRes)
51 | } catch (err) {
52 | console.log('error', err.message)
53 | }
54 | }
55 | fetchUser()
56 | }, [userId])
57 |
58 | const onChangeHandler = (e: ChangeEvent) =>
59 | setUser({
60 | ...user,
61 | [e.target.name]: e.target.value,
62 | })
63 |
64 | const setGlobalRole = (e: ChangeEvent) =>
65 | setUser({
66 | ...user,
67 | globalRole: e.target.value,
68 | })
69 |
70 | const handleSubmit = async (e: FormEvent) => {
71 | e.preventDefault()
72 | try {
73 | if (userId) {
74 | await api.users.update(userId, user)
75 | } else {
76 | await api.users.create(user)
77 | }
78 | } catch (e) {
79 | console.log(e)
80 | }
81 | }
82 |
83 | return (
84 |
85 |
86 |
87 |
148 |
149 |
150 | )
151 | }
152 |
153 | const useStyles = makeStyles((theme) => ({
154 | box: {
155 | padding: 16,
156 | },
157 | control: {
158 | display: 'block',
159 | marginTop: 16,
160 | },
161 | margin: {
162 | marginTop: 16,
163 | },
164 | width: {
165 | minWidth: 200,
166 | },
167 | }))
168 |
169 | export default UserEditor
170 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersEditor/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './UsersEditor'
2 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersList/UsersList.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { match as Match } from 'react-router-dom'
3 | import Grid from '@material-ui/core/Grid'
4 | import {
5 | // makeStyles,
6 | Paper,
7 | Table,
8 | TableCell,
9 | TableRow,
10 | TableHead,
11 | TableBody,
12 | TableFooter,
13 | TableContainer,
14 | TableSortLabel,
15 | } from '@material-ui/core'
16 | import { Alert, AlertTitle } from '@material-ui/lab'
17 |
18 | import { User } from '_api/_types/User'
19 | import api from '_api'
20 |
21 | import BasePageContainer from '_common/BasePageContainer'
22 | import BasePageToolbar from '_common/BasePageToolbar'
23 | import { BaseTablePagination } from '_common/BaseTable'
24 |
25 | import UsersListAction from './UsersListActions'
26 | import UsersListTableItems from './UsersListTableItems'
27 |
28 | const tableColumns = [
29 | {
30 | id: 'avatarUrl',
31 | label: '',
32 | isSortable: false,
33 | },
34 | {
35 | id: 'firstName',
36 | label: 'First Name',
37 | isSortable: true,
38 | },
39 | {
40 | id: 'lastName',
41 | label: 'Last Name',
42 | isSortable: true,
43 | },
44 | {
45 | id: 'username',
46 | label: 'Username',
47 | isSortable: true,
48 | },
49 | {
50 | id: 'email',
51 | label: 'Email',
52 | isSortable: true,
53 | },
54 | {
55 | id: 'status',
56 | label: 'Status',
57 | isSortable: true,
58 | },
59 | {
60 | id: 'createdAt',
61 | label: 'Created',
62 | isSortable: true,
63 | },
64 | ]
65 |
66 | type UsersListRouteParams = {}
67 |
68 | export type UsersListProps = {
69 | match: Match
70 | }
71 |
72 | type UsersData = {
73 | users: User[]
74 | count: number
75 | }
76 |
77 | const UsersList: React.FC = () => {
78 | const [status, setStatus] = useState('idle')
79 | const [statusMessage, setStatusMessage] = useState('')
80 | const [page, setPage] = useState(0)
81 | const [usersData, setUsersData] = useState({ users: [], count: 0 })
82 | const [rowsPerPage, setRowsPerPage] = useState(10)
83 | const [order, setOrder] = useState({
84 | order: 'desc',
85 | orderBy: 'createdAt',
86 | })
87 |
88 | const { users, count } = usersData
89 |
90 | // Request users
91 | useEffect(() => {
92 | async function fetchUsers() {
93 | setStatus('loading')
94 |
95 | try {
96 | const userDataRes = await api.users.getList({
97 | limit: rowsPerPage,
98 | offset: page * rowsPerPage,
99 | order,
100 | })
101 |
102 | setStatus('idle')
103 | setUsersData(userDataRes)
104 | } catch (err) {
105 | console.log('error', err.message)
106 |
107 | setStatus('error')
108 | setStatusMessage(err.message)
109 | }
110 | }
111 |
112 | fetchUsers()
113 | }, [order, page, rowsPerPage])
114 |
115 | const handleChangePage = (event: any, newPage: number) => {
116 | setPage(newPage)
117 | }
118 |
119 | const handleChangeRowsPerPage = (event: any) => {
120 | setRowsPerPage(parseInt(event.target.value, 10))
121 | setPage(0)
122 | }
123 |
124 | const handelChangeOrder = (
125 | event: React.MouseEvent,
126 | columnId: string,
127 | ) => {
128 | setOrder({
129 | // If the sorting column has changed
130 | order: columnId !== order.orderBy || order.order === 'desc' ? 'asc' : 'desc',
131 | orderBy: columnId,
132 | })
133 | }
134 |
135 | return (
136 |
137 |
141 |
142 |
143 | {status === 'error' && (
144 |
145 | Error
146 | {statusMessage}
147 |
148 | )}
149 |
150 | {status !== 'error' && (
151 |
152 |
153 |
154 |
155 | {tableColumns.map((column) => (
156 |
157 | {/* Sortable */}
158 | {column.isSortable && (
159 | handelChangeOrder(event, column.id)}
163 | >
164 | {column.label}
165 |
166 | )}
167 | {/* Non-sortable */}
168 | {!column.isSortable && column.label}
169 |
170 | ))}
171 | Actions
172 |
173 |
174 |
175 |
179 |
180 |
181 |
182 |
189 |
190 |
191 |
192 |
193 | )}
194 |
195 |
196 |
197 | )
198 | }
199 |
200 | export default UsersList
201 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersList/UsersListActions.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 | import React, { useState } from 'react'
3 |
4 | import { fade, makeStyles, Button, Tooltip, InputBase } from '@material-ui/core'
5 |
6 | import {
7 | MoreVert as IconMore,
8 | Tune as IconFilter,
9 | // ArrowDropDown as IconDropDown,
10 | Add as IconNew,
11 | Search as IconSearch,
12 | Clear as IconClear,
13 | } from '@material-ui/icons'
14 |
15 | // import usersListContext from './usersListContext'
16 |
17 | const UsersListActions: React.FC = () => {
18 | const classes = useStyles()
19 | const [search, setSearch] = useState('')
20 | // const { filter } = useContext(usersListContext)
21 |
22 | const handleChangeSearchInput: React.ChangeEventHandler<
23 | HTMLInputElement | HTMLTextAreaElement
24 | > = (event) => {
25 | setSearch(event.target.value)
26 | }
27 |
28 | const handelClickSearchClearButton = () => {
29 | setSearch('')
30 | }
31 |
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
48 | {search && (
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | )}
57 |
58 |
59 |
60 |
61 | New
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | )
76 | }
77 |
78 | const useStyles = makeStyles((theme) => ({
79 | root: {
80 | // color: theme.palette.primary.main,
81 | color: theme.palette.grey[600],
82 | },
83 | iconNew: {
84 | marginRight: 5,
85 | },
86 | search: {
87 | position: 'relative',
88 | display: 'inline-block',
89 | borderRadius: theme.shape.borderRadius,
90 | backgroundColor: fade(theme.palette.common.white, 0),
91 | '&:hover': {
92 | // backgroundColor: fade(theme.palette.common.white, 0.5),
93 | backgroundColor: 'rgba(140, 209, 54, 0.04)',
94 | },
95 | marginLeft: 0,
96 | width: '100%',
97 | [theme.breakpoints.up('sm')]: {
98 | marginLeft: theme.spacing(1),
99 | width: 'auto',
100 | },
101 | },
102 | searchIcon: {
103 | padding: theme.spacing(0, 2),
104 | height: '100%',
105 | position: 'absolute',
106 | pointerEvents: 'none',
107 | display: 'flex',
108 | alignItems: 'center',
109 | justifyContent: 'center',
110 | },
111 | searchButtonClear: {
112 | position: 'absolute',
113 | height: '100%',
114 | top: 0,
115 | right: 0,
116 | display: 'flex',
117 | alignItems: 'center',
118 | justifyContent: 'center',
119 | },
120 | searchInputRoot: {
121 | color: 'inherit',
122 | },
123 | searchInputInput: {
124 | padding: theme.spacing(1, 1, 1, 0),
125 | // vertical padding + font size from searchIcon
126 | paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
127 | paddingRight: '2.2em',
128 | transition: theme.transitions.create('width'),
129 | width: '100%',
130 | [theme.breakpoints.up('sm')]: {
131 | width: '12ch',
132 | '&:focus, &.-active': {
133 | width: '20ch',
134 | },
135 | },
136 | },
137 | }))
138 |
139 | export default UsersListActions
140 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersList/UsersListTableItems.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormattedDate } from 'react-intl'
3 | import { Link } from 'react-router-dom'
4 | import { makeStyles, TableCell, TableRow, Avatar, Chip } from '@material-ui/core'
5 | import { Skeleton } from '@material-ui/lab'
6 | import { Edit as EditIcon, Delete as DeleteIcon } from '@material-ui/icons/'
7 |
8 | import { User } from '_api/_types/User'
9 |
10 | export type UsersListTableItemsProps = {
11 | users: User[]
12 | rowsPerPage?: number
13 | rowsExpected?: number
14 | }
15 |
16 | const UsersListTableItems: React.FC = ({
17 | users,
18 | rowsPerPage = 10,
19 | rowsExpected = 10,
20 | }) => {
21 | const classes = useStyles()
22 | // Count how many empty rows needs to be filled
23 | const usersVisible = users.length || rowsExpected
24 | const usersArrayExpected = Array.from({ length: usersVisible }).map(
25 | (item, index) => index,
26 | )
27 | const emptyRows = rowsPerPage - usersVisible
28 |
29 | return (
30 | <>
31 | {!users.length &&
32 | usersArrayExpected.map((item) => (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ))}
57 | {users.map((row) => (
58 |
59 |
60 |
61 |
62 |
63 | {row.firstName}
64 |
65 | {row.lastName}
66 | {row.username}
67 | {row.email}
68 |
69 |
75 |
76 |
77 | {row.createdAt && (
78 |
84 | )}
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | ))}
94 | {emptyRows > 0 && (
95 |
96 |
97 |
98 | )}
99 | >
100 | )
101 | }
102 |
103 | const useStyles = makeStyles((theme) => ({
104 | link: {
105 | color: 'inherit',
106 | },
107 | }))
108 |
109 | export default UsersListTableItems
110 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersList/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './UsersList'
2 |
--------------------------------------------------------------------------------
/src/Administration/Users/UsersList/usersListContext.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import moment, { Moment } from 'moment'
3 |
4 | // The default context, which is used when there is no provider
5 | // (might be used for components testing)
6 | export const usersListContextDefault = {
7 | filter: {
8 | dateFrom: moment().subtract(14, 'day').startOf('day'),
9 | dateTo: moment().startOf('day'),
10 | },
11 | }
12 |
13 | export const usersListContext = React.createContext(usersListContextDefault)
14 |
15 | export const dashboardProvider = usersListContext.Provider
16 | export const dashboardConsumer = usersListContext.Consumer
17 |
18 | export default usersListContext
19 |
--------------------------------------------------------------------------------
/src/Administration/index.ts:
--------------------------------------------------------------------------------
1 | import Administration from './Administration'
2 |
3 | const AdministrationModule = {
4 | init() {},
5 | }
6 | export { Administration }
7 |
8 | export default AdministrationModule
9 |
--------------------------------------------------------------------------------
/src/App.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Modular Admin + React
5 |
6 | > React dashboard application starter
7 |
8 |
9 | ## Directory Structure
10 |
11 |
12 | ## Module system
13 |
14 |
15 | ## Layouts
16 |
17 | ## Routing
18 |
19 | ## Authorization
20 |
21 | ## API calls and data
22 |
23 |
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '_tests'
3 | import App from './App'
4 |
5 | test('renders without crashing', () => {
6 | render( )
7 | })
8 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ThemeProvider } from '@material-ui/styles'
3 | import { IntlProvider } from 'react-intl'
4 |
5 | import CssBaseline from '@material-ui/core/CssBaseline'
6 |
7 | import theme from './_theme'
8 | import AppRouter from './AppRouter'
9 |
10 | const App: React.FC = () => {
11 | return (
12 |
16 | )
17 | }
18 | export default () => (
19 |
20 |
21 |
22 |
23 |
24 | )
25 |
--------------------------------------------------------------------------------
/src/AppRouter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { HashRouter, BrowserRouter, Route, Switch, RouteProps } from 'react-router-dom' //
3 |
4 | import config from './_config'
5 |
6 | import DashboardLayout from '_layouts/DashboardLayout'
7 | import { Auth } from './Auth'
8 | import { Administration } from './Administration'
9 | import { Dashboard } from './Dashboard'
10 |
11 | // Use different router type depending on configuration
12 | const AppRouterComponent: React.FC = ({ children }) => {
13 | return config.navigationType === 'history' ? (
14 | {children}
15 | ) : (
16 | {children}
17 | )
18 | }
19 |
20 | const AppRouter: React.FC = () => {
21 | return (
22 |
23 |
24 |
25 |
31 |
36 | null}
39 | layout={DashboardLayout}
40 | />
41 | null}
44 | layout={DashboardLayout}
45 | />
46 |
47 |
48 | )
49 | }
50 |
51 | export interface RouteWithLayoutProps extends RouteProps {
52 | layout: React.ComponentType
53 | }
54 |
55 | const RouteWithLayout: React.FC = ({
56 | component: Component,
57 | layout: Layout,
58 | children,
59 | ...rest
60 | }) => {
61 | return (
62 | {
65 | if (!Component) return null
66 |
67 | if (Layout) {
68 | return (
69 |
70 |
71 |
72 | )
73 | } else {
74 | return
75 | }
76 | }}
77 | >
78 | {children}
79 |
80 | )
81 | }
82 |
83 | export default AppRouter
84 |
--------------------------------------------------------------------------------
/src/Auth/Auth.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Auth module
4 |
5 | Handles authentication
6 |
7 | - Login
8 | - Logout
9 | - Signup
10 | - Password recover
11 | - Password reset
--------------------------------------------------------------------------------
/src/Auth/Auth.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Redirect, match as Match } from 'react-router-dom' //
3 | import { makeStyles } from '@material-ui/core/styles'
4 |
5 | import { Grid, Typography, Link, Box } from '@material-ui/core/'
6 | import { Link as RouterLink } from 'react-router-dom'
7 |
8 | import Login from './Login'
9 | import Signup from './Signup'
10 | import Recover from './Recover'
11 | import Reset from './Reset'
12 |
13 | const AuthFooter = () => {
14 | return (
15 |
16 |
17 | Go back to dashboard
18 |
19 |
20 | )
21 | }
22 |
23 | export type AuthProps = {
24 | match: Match
25 | }
26 |
27 | const Auth: React.FC = ({ match }) => {
28 | const classes = useStyles()
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
49 | const useStyles = makeStyles((theme) => ({
50 | container: {
51 | minHeight: '100vh',
52 | },
53 | formSection: {
54 | display: 'flex',
55 | justifyContent: 'center',
56 | alignItems: 'center',
57 | },
58 | introSection: {
59 | display: 'flex',
60 | justifyContent: 'center',
61 | alignItems: 'center',
62 | backgroundImage: 'url(/background-auth.jpg)',
63 | backgroundSize: 'cover',
64 | backgroundPosition: 'right',
65 | backgroundRepeat: 'no-repeat',
66 | [theme.breakpoints.down('sm')]: {
67 | display: 'none',
68 | },
69 | },
70 | }))
71 |
72 | export default Auth
73 |
--------------------------------------------------------------------------------
/src/Auth/Login/Login.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 |
3 | import Login from './Login'
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Auth/Login/Login.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | makeStyles,
4 | Button,
5 | TextField,
6 | FormControlLabel,
7 | Checkbox,
8 | Link,
9 | Grid,
10 | } from '@material-ui/core'
11 | import { Link as RouterLink } from 'react-router-dom'
12 |
13 | import AuthHeader from '../_common/AuthHeader'
14 | import AuthContent from '../_common/AuthContent'
15 |
16 | const Login: React.FC = () => {
17 | const classes = useStyles()
18 |
19 | return (
20 |
21 |
22 |
71 |
72 | )
73 | }
74 |
75 | const useStyles = makeStyles((theme) => ({
76 | form: {
77 | width: '100%', // Fix IE 11 issue.
78 | marginTop: theme.spacing(1),
79 | },
80 | submit: {
81 | margin: theme.spacing(3, 0, 2),
82 | },
83 | }))
84 |
85 | export default Login
86 |
--------------------------------------------------------------------------------
/src/Auth/Login/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Login'
2 |
--------------------------------------------------------------------------------
/src/Auth/Recover/Recover.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 |
3 | import Recover from './Recover'
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Auth/Recover/Recover.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, Button, TextField, Link, Grid } from '@material-ui/core'
3 | import { Link as RouterLink } from 'react-router-dom'
4 |
5 | import AuthContent from '../_common/AuthContent'
6 | import AuthHeader from '../_common/AuthHeader'
7 |
8 | const Recover: React.FC = () => {
9 | const classes = useStyles()
10 |
11 | return (
12 |
13 |
14 |
48 |
49 | )
50 | }
51 |
52 | const useStyles = makeStyles((theme) => ({
53 | form: {
54 | width: '100%', // Fix IE 11 issue.
55 | marginTop: theme.spacing(1),
56 | },
57 | submit: {
58 | margin: theme.spacing(3, 0, 2),
59 | },
60 | }))
61 |
62 | export default Recover
63 |
--------------------------------------------------------------------------------
/src/Auth/Recover/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Recover'
2 |
--------------------------------------------------------------------------------
/src/Auth/Reset/Reset.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 |
3 | import Reset from './Reset'
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Auth/Reset/Reset.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, Button, TextField, Link, Grid } from '@material-ui/core'
3 | import { Link as RouterLink } from 'react-router-dom'
4 |
5 | import AuthContent from '../_common/AuthContent'
6 | import AuthHeader from '../_common/AuthHeader'
7 |
8 | const Reset: React.FC = () => {
9 | const classes = useStyles()
10 |
11 | return (
12 |
13 |
14 |
57 |
58 | )
59 | }
60 |
61 | const useStyles = makeStyles((theme) => ({
62 | form: {
63 | width: '100%', // Fix IE 11 issue.
64 | marginTop: theme.spacing(1),
65 | },
66 | submit: {
67 | margin: theme.spacing(3, 0, 2),
68 | },
69 | }))
70 |
71 | export default Reset
72 |
--------------------------------------------------------------------------------
/src/Auth/Reset/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Reset'
2 |
--------------------------------------------------------------------------------
/src/Auth/Signup/Signup.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 |
3 | import Signup from './Signup'
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Auth/Signup/Signup.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | makeStyles,
4 | Grid,
5 | Checkbox,
6 | Link,
7 | FormControlLabel,
8 | TextField,
9 | Button,
10 | } from '@material-ui/core'
11 | import { Link as RouterLink } from 'react-router-dom'
12 |
13 | import AuthContent from '../_common/AuthContent'
14 | import AuthHeader from '../_common/AuthHeader'
15 |
16 | const Signup: React.FC = () => {
17 | const classes = useStyles()
18 |
19 | return (
20 |
21 |
22 |
106 |
107 | )
108 | }
109 |
110 | const useStyles = makeStyles((theme) => ({
111 | form: {
112 | width: '100%', // Fix IE 11 issue.
113 | marginTop: theme.spacing(3),
114 | },
115 | submit: {
116 | margin: theme.spacing(3, 0, 2),
117 | },
118 | }))
119 |
120 | export default Signup
121 |
--------------------------------------------------------------------------------
/src/Auth/Signup/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Signup'
2 |
--------------------------------------------------------------------------------
/src/Auth/_common/AuthContent/AuthContent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core'
3 |
4 | export type AuthContentProps = {}
5 |
6 | const AuthContent: React.FC = (props) => {
7 | const classes = useStyles()
8 |
9 | return {props.children}
10 | }
11 |
12 | const useStyles = makeStyles((theme) => ({
13 | paper: {
14 | display: 'flex',
15 | flexDirection: 'column',
16 | alignItems: 'center',
17 | },
18 | }))
19 |
20 | export default AuthContent
21 |
--------------------------------------------------------------------------------
/src/Auth/_common/AuthContent/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AuthContent'
2 |
--------------------------------------------------------------------------------
/src/Auth/_common/AuthFooter/AuthFooter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const AuthFooter = () => {
4 | return
5 | }
6 |
7 | export default AuthFooter
8 |
--------------------------------------------------------------------------------
/src/Auth/_common/AuthFooter/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AuthFooter'
2 |
--------------------------------------------------------------------------------
/src/Auth/_common/AuthHeader/AuthHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, Typography } from '@material-ui/core'
3 |
4 | import BaseLogo from '_common/BaseLogo'
5 |
6 | export type AuthHeaderProps = {
7 | title: string
8 | }
9 |
10 | const AuthHeader: React.FC = ({ title = '' }) => {
11 | const classes = useStyles()
12 |
13 | return (
14 |
15 | {title}
16 |
17 | )
18 | }
19 |
20 | const useStyles = makeStyles((theme) => ({
21 | logo: {
22 | color: theme.palette.secondary.main,
23 | position: 'relative',
24 | top: '1px',
25 | },
26 | }))
27 |
28 | export default AuthHeader
29 |
--------------------------------------------------------------------------------
/src/Auth/_common/AuthHeader/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AuthHeader'
2 |
--------------------------------------------------------------------------------
/src/Auth/index.ts:
--------------------------------------------------------------------------------
1 | import Auth from './Auth'
2 |
3 | const AuthModule = {
4 | init() {},
5 | }
6 |
7 | // export const Auth from './Auth'
8 |
9 | export { Auth }
10 |
11 | export default AuthModule
12 |
--------------------------------------------------------------------------------
/src/Dashboard/Dashboard.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 | # General Dashboard module
4 |
5 | General dashboard template
--------------------------------------------------------------------------------
/src/Dashboard/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Grid from '@material-ui/core/Grid'
4 |
5 | import BasePageContainer from '../_common/BasePageContainer'
6 | import BasePageToolbar from '../_common/BasePageToolbar'
7 |
8 | import DashboardActions from './DashboardActions'
9 | import SubscriptionsHistory from './SubscriptionsHistory/'
10 | import KeyMetrics from './KeyMetrics/'
11 | import SubscriptionsRecent from './SubscriptionsRecent/'
12 | import SubscriptionsBreakdown from './SubscriptionsBreakdown/'
13 |
14 | export type DashboardProps = {}
15 |
16 | const Dashboard: React.FC = () => {
17 | console.log('Dashboard rendered')
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default Dashboard
35 |
--------------------------------------------------------------------------------
/src/Dashboard/DashboardActions.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 |
3 | import { makeStyles } from '@material-ui/core/styles'
4 | import Button from '@material-ui/core/Button'
5 | import Tooltip from '@material-ui/core/Tooltip'
6 | import IconMore from '@material-ui/icons/MoreVert'
7 | import IconFilter from '@material-ui/icons/Tune'
8 | import IconDropDown from '@material-ui/icons/ArrowDropDown'
9 | import IconNew from '@material-ui/icons/Add'
10 |
11 | import dashboardContext from './dashboardContext'
12 |
13 | const DashboardActions = () => {
14 | const classes = useStyles()
15 | const { filter } = useContext(dashboardContext)
16 |
17 | const dateFilterLabel = filter
18 | ? `${filter.dateFrom.format('ll')} - ${filter.dateTo.format('ll')}`
19 | : 'Date Filter'
20 |
21 | return (
22 |
23 |
24 |
25 | {dateFilterLabel}
26 |
27 |
28 |
29 |
30 |
31 | New
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | const useStyles = makeStyles((theme) => ({
49 | iconNew: {
50 | marginRight: 5,
51 | },
52 | }))
53 |
54 | export default DashboardActions
55 |
--------------------------------------------------------------------------------
/src/Dashboard/KeyMetrics/KeyMetrics.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, waitFor, screen } from '_tests/'
3 | import KeyMetrics from './KeyMetrics'
4 |
5 | describe('Dashboard/KeyMetrics', () => {
6 | it('has 4 key metrics', async () => {
7 | render( )
8 |
9 | // wait for headings to appear
10 | await waitFor(() => screen.getAllByRole('heading'))
11 |
12 | const Headings = screen.getAllByRole('heading')
13 |
14 | expect(Headings).toHaveLength(4)
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/Dashboard/KeyMetrics/KeyMetrics.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import clsx from 'clsx'
3 | import { makeStyles } from '@material-ui/core/styles'
4 | import { Paper, Box, Grid, Typography } from '@material-ui/core'
5 | import { Line } from 'react-chartjs-2'
6 |
7 | import { generateTrendChartData } from './data'
8 |
9 | const keyMetrics = [
10 | {
11 | name: 'Monthly Revenue',
12 | value: '24350$',
13 | change: '+13%',
14 | trend: 'positive',
15 | chart: generateTrendChartData({
16 | name: 'Monthly Revenue',
17 | from: Math.round(24350 / 1.13),
18 | to: 24350,
19 | length: 15,
20 | }),
21 | },
22 | {
23 | name: 'Total Users',
24 | value: 48205,
25 | change: '+10%',
26 | trend: 'positive',
27 | chart: generateTrendChartData({
28 | name: 'Total Users',
29 | from: Math.round(48205 / 1.1),
30 | to: 48205,
31 | }),
32 | },
33 | {
34 | name: 'Subscriptions',
35 | value: 139,
36 | change: '-5%',
37 | trend: 'negative',
38 | chart: generateTrendChartData({
39 | name: 'Subscriptions',
40 | from: 139,
41 | to: Math.round(139 / 1.05),
42 | length: 15,
43 | }),
44 | },
45 | {
46 | name: 'Monthly Churn',
47 | value: 13,
48 | change: '-10%',
49 | trend: 'positive',
50 | chart: generateTrendChartData({
51 | name: 'Monthly Churn',
52 | from: 13,
53 | to: Math.random() / 1.1,
54 | length: 15,
55 | }),
56 | },
57 | ]
58 |
59 | export type KeyMetricsProps = {}
60 |
61 | const KeyMetrics: React.FC = () => {
62 | const classes = useStyles()
63 |
64 | return (
65 | <>
66 | {keyMetrics.map(({ name, value, change, trend, chart }) => (
67 |
68 |
69 |
70 |
71 |
72 |
78 | {name}
79 |
80 |
81 | {value}{' '}
82 |
89 | {change}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | ))}
105 | >
106 | )
107 | }
108 |
109 | const useStyles = makeStyles((theme) => ({
110 | root: {
111 | flexGrow: 1,
112 | },
113 | paper: {
114 | // padding: theme.spacing(2),
115 | textAlign: 'left',
116 | color: theme.palette.text.primary,
117 | height: '100%',
118 | },
119 | name: {
120 | whiteSpace: 'nowrap',
121 | overflow: 'hidden',
122 | textOverflow: 'ellipsis',
123 | },
124 | value: {
125 | fontWeight: 'bold',
126 | whiteSpace: 'nowrap',
127 | },
128 | valueChange: {},
129 | negative: {
130 | color: theme.palette.error.main,
131 | },
132 | positive: {
133 | color: theme.palette.success.main,
134 | },
135 | chartContainer: {
136 | position: 'absolute',
137 | width: '100%',
138 | height: '100%',
139 | top: 0,
140 | left: 0,
141 | },
142 | }))
143 |
144 | export default KeyMetrics
145 |
--------------------------------------------------------------------------------
/src/Dashboard/KeyMetrics/data.ts:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 | import theme from '../../_theme'
3 | import utilsService from '../../_services/utilsService'
4 |
5 | export const generateTrendChartData = ({
6 | name = '',
7 | from = 0,
8 | to = 1000,
9 | length = 30,
10 | }) => {
11 | return {
12 | type: 'line',
13 | data: {
14 | datasets: [
15 | {
16 | backgroundColor: theme.palette.primary.main,
17 | borderColor: theme.palette.primary.main,
18 | borderWidth: 2,
19 | pointRadius: 1,
20 | pointHoverRadius: 3,
21 | label: name,
22 | fill: false,
23 | data: utilsService.generateRandomeChartDataArray({ from, to, length }),
24 | },
25 | ],
26 | labels: Array(length)
27 | .fill(null)
28 | .map((item, index) =>
29 | moment()
30 | .subtract(length - index, 'days')
31 | .format('ll'),
32 | ),
33 | },
34 | options: {
35 | layout: {
36 | padding: {
37 | left: 10,
38 | right: 10,
39 | top: 10,
40 | bottom: 10,
41 | },
42 | },
43 | scales: {
44 | x: {
45 | display: false,
46 | },
47 | y: {
48 | display: false,
49 | },
50 | },
51 | plugins: {
52 | legend: {
53 | display: false,
54 | },
55 | },
56 | tooltips: {
57 | mode: 'index',
58 | intersect: false,
59 | caretSize: 0,
60 | callbacks: {
61 | label: function (tooltipItem: any, data: any) {
62 | // var datasetLabel = ''
63 | // var label = data.labels[tooltipItem.index]
64 | return data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]
65 | },
66 | },
67 | },
68 | interaction: {
69 | mode: 'index',
70 | intersect: false,
71 | },
72 | responsive: true,
73 | maintainAspectRatio: false,
74 | },
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Dashboard/KeyMetrics/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './KeyMetrics'
2 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsBreakdown/SubscriptionsBreakdown.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | makeStyles,
4 | Grid,
5 | Card,
6 | CardHeader,
7 | CardContent,
8 | IconButton,
9 | } from '@material-ui/core'
10 | import { BarChart as IconBarChart, MoreVert as IconMoreVert } from '@material-ui/icons'
11 | import { Bar } from 'react-chartjs-2'
12 |
13 | import { chart } from './data'
14 |
15 | export type SubscriptionsBreakdownProps = {}
16 |
17 | const SubscriptionsBreakdown: React.FC = () => {
18 | const classes = useStyles()
19 |
20 | return (
21 |
22 |
23 | }
26 | action={
27 |
28 |
29 |
30 | }
31 | title="Subscriptions Breakdown"
32 | />
33 |
34 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | const useStyles = makeStyles((theme) => ({
46 | card: {
47 | height: '100%',
48 | },
49 | cardHeader: {
50 | borderBottom: '1px solid',
51 | borderBottomColor: theme.palette.divider,
52 | },
53 | cardBody: {
54 | overflow: 'hidden',
55 | },
56 | headerTitleBox: {},
57 | headerActionsBox: {
58 | textAlign: 'right',
59 | },
60 | headerIcon: {
61 | color: theme.palette.grey[300],
62 | verticalAlign: 'sub',
63 | marginRight: '.3em',
64 | },
65 | chartBox: {
66 | borderRight: '1px solid',
67 | borderRightColor: theme.palette.divider,
68 | },
69 | chartContainer: {
70 | width: '100%',
71 | position: 'relative',
72 | paddingBottom: '25%',
73 | minHeight: 240,
74 | },
75 | chart: {
76 | position: 'absolute',
77 | width: '100%',
78 | height: '100%',
79 | top: 0,
80 | left: 0,
81 | },
82 | cardContent: {
83 | height: '100%',
84 | '&:last-child': {
85 | paddingBottom: 'default',
86 | },
87 | },
88 | ratingBox: {
89 | display: 'flex',
90 | flexDirection: 'column',
91 | justifyContent: 'space-between',
92 | },
93 | ratingItemValueBox: {
94 | textAlign: 'right',
95 | fontSize: '0.7em',
96 | },
97 | ratingItemValue: {
98 | display: 'inline-block',
99 | },
100 | ratingItemRatio: {
101 | marginLeft: 4,
102 | display: 'inline-block',
103 | width: '3em',
104 | // fontSize: '1em',
105 | },
106 | }))
107 |
108 | export default SubscriptionsBreakdown
109 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsBreakdown/data.ts:
--------------------------------------------------------------------------------
1 | import theme from '../../_theme'
2 |
3 | export const chart = {
4 | data: {
5 | datasets: [
6 | {
7 | backgroundColor: theme.palette.primary.main,
8 | borderColor: theme.palette.primary.main,
9 | borderWidth: 2,
10 | label: 'Customers',
11 | fill: false,
12 | data: [1545, 540, 749, 310, 56],
13 | yAxisID: 'y',
14 | },
15 | {
16 | backgroundColor: 'rgba(136, 151, 170, 0.1)',
17 | borderColor: '#8897aa',
18 | borderDash: [5, 5],
19 | borderWidth: 1,
20 | data: [23686, 30820, 59622, 146465, 78160],
21 | label: 'Total Monthly Revenue, $',
22 | yAxisID: 'y1',
23 | },
24 | ],
25 | labels: ['Trial', 'Starter', 'Pro', 'Silver', 'Gold'],
26 | },
27 | options: {
28 | scales: {
29 | x: {
30 | grid: { display: false },
31 | ticks: { fontColor: '#aaa', autoSkipPadding: 50 },
32 | },
33 | y: {
34 | grid: { display: false },
35 | ticks: { fontColor: '#aaa', maxTicksLimit: 5 },
36 | },
37 | y1: {
38 | position: 'right',
39 | grid: { display: false },
40 | ticks: { fontColor: '#aaa', maxTicksLimit: 5 },
41 | },
42 | },
43 | tooltips: {
44 | mode: 'index',
45 | intersect: false,
46 | },
47 | hover: {
48 | mode: 'index',
49 | intersect: false,
50 | },
51 | responsive: true,
52 | maintainAspectRatio: false,
53 | },
54 | }
55 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsBreakdown/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SubscriptionsBreakdown'
2 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsHistory/SubscriptionsHistory.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | makeStyles,
4 | Grid,
5 | Card,
6 | CardHeader,
7 | CardContent,
8 | Typography,
9 | IconButton,
10 | LinearProgress,
11 | } from '@material-ui/core'
12 | import { Timeline as IconTimeline, MoreVert as IconMoreVert } from '@material-ui/icons'
13 | import { Line } from 'react-chartjs-2'
14 |
15 | import { subscriptionsItems, subscriptionsHistoryChart } from './data'
16 |
17 | export type SubscriptionsProps = {}
18 |
19 | const Subscriptions: React.FC = () => {
20 | const classes = useStyles()
21 |
22 | return (
23 |
24 | }
27 | action={
28 |
29 |
30 |
31 | }
32 | title="Subscriptions"
33 | />
34 |
35 |
36 |
37 |
46 |
47 |
48 | {subscriptionsItems.map(({ name, ratio, value }) => (
49 |
50 |
51 |
52 | {name}
53 |
54 |
55 |
61 | {value}
62 |
63 |
69 | {ratio}%
70 |
71 |
72 |
73 |
74 |
75 | ))}
76 |
77 |
78 |
79 |
80 | )
81 | }
82 |
83 | Subscriptions.propTypes = {}
84 |
85 | const useStyles = makeStyles((theme) => ({
86 | cardHeader: {
87 | borderBottom: '1px solid',
88 | borderBottomColor: theme.palette.divider,
89 | },
90 | cardBody: {
91 | overflow: 'hidden',
92 | },
93 | headerTitleBox: {},
94 | headerActionsBox: {
95 | textAlign: 'right',
96 | },
97 | headerIcon: {
98 | color: theme.palette.grey[300],
99 | verticalAlign: 'sub',
100 | marginRight: '.3em',
101 | },
102 | chartBox: {
103 | borderRight: '1px solid',
104 | borderRightColor: theme.palette.divider,
105 | },
106 | chartContainer: {
107 | width: '100%',
108 | position: 'relative',
109 | paddingBottom: '25%',
110 | minHeight: 240,
111 | },
112 | chart: {
113 | position: 'absolute',
114 | width: '100%',
115 | height: '100%',
116 | top: 0,
117 | left: 0,
118 | },
119 | cardContent: {
120 | '&:last-child': {
121 | paddingBottom: 'default',
122 | },
123 | },
124 | ratingBox: {
125 | display: 'flex',
126 | flexDirection: 'column',
127 | justifyContent: 'space-between',
128 | },
129 | ratingItemValueBox: {
130 | textAlign: 'right',
131 | fontSize: '0.7em',
132 | },
133 | ratingItemValue: {
134 | display: 'inline-block',
135 | },
136 | ratingItemRatio: {
137 | marginLeft: 4,
138 | display: 'inline-block',
139 | width: '3em',
140 | // fontSize: '1em',
141 | },
142 | }))
143 |
144 | export default Subscriptions
145 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsHistory/data.ts:
--------------------------------------------------------------------------------
1 | import theme from '../../_theme'
2 |
3 | export const subscriptionsItems = [
4 | { name: 'GitHub', ratio: 55.3, value: Math.round(55.3 * 144) },
5 | { name: 'MaterialUI', ratio: 25.7, value: Math.round(25.7 * 144) },
6 | { name: 'Google', ratio: 15.6, value: Math.round(15.6 * 144) },
7 | { name: 'ModularCode', ratio: 8.4, value: Math.round(8.4 * 144) },
8 | { name: 'GH', ratio: 5.5, value: Math.round(5.5 * 144) },
9 | ]
10 |
11 | export const subscriptionsHistoryChart = {
12 | data: {
13 | datasets: [
14 | {
15 | backgroundColor: theme.palette.primary.main,
16 | borderColor: theme.palette.primary.main,
17 | borderWidth: 2,
18 | label: 'Subscriptions',
19 | fill: false,
20 | data: [1545, 1350, 1270, 1830, 1955, 1865, 2034, 2544, 1956, 2211, 1540, 1670],
21 | yAxisID: 'y',
22 | cubicInterpolationMode: 'monotone',
23 | tension: 0.4,
24 | },
25 | {
26 | fill: true,
27 | backgroundColor: 'rgba(136, 151, 170, 0.1)',
28 | borderColor: '#8897aa',
29 | borderDash: [5, 5],
30 | borderWidth: 1,
31 | data: [
32 | 23686,
33 | 30820,
34 | 59622,
35 | 146465,
36 | 78160,
37 | 79520,
38 | 36148,
39 | 48781,
40 | 158303,
41 | 155174,
42 | 104830,
43 | 86895,
44 | ],
45 | label: 'Visits',
46 | yAxisID: 'y1',
47 | cubicInterpolationMode: 'monotone',
48 | tension: 0.4,
49 | },
50 | ],
51 | labels: [
52 | '2019-03',
53 | '2019-04',
54 | '2019-05',
55 | '2019-06',
56 | '2019-07',
57 | '2019-08',
58 | '2019-09',
59 | '2019-10',
60 | '2019-11',
61 | '2019-12',
62 | '2020-01',
63 | '2020-02',
64 | ],
65 | },
66 | options: {
67 | stacked: false,
68 | scales: {
69 | x: {
70 | grid: { display: false },
71 | ticks: { fontColor: '#aaa', autoSkipPadding: 50 },
72 | },
73 | y: {
74 | grid: { display: false },
75 | ticks: { fontColor: '#aaa', maxTicksLimit: 5 },
76 | },
77 | y1: {
78 | position: 'right',
79 | grid: { display: false },
80 | ticks: { fontColor: '#aaa', maxTicksLimit: 5 },
81 | },
82 | },
83 | tooltips: {
84 | mode: 'index',
85 | intersect: false,
86 | },
87 | interaction: {
88 | mode: 'index',
89 | intersect: false,
90 | },
91 | responsive: true,
92 | maintainAspectRatio: false,
93 | },
94 | }
95 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsHistory/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SubscriptionsHistory'
2 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsRecent/SubscriptionsRecent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | makeStyles,
4 | Grid,
5 | Card,
6 | Button,
7 | CardHeader,
8 | CardActions,
9 | IconButton,
10 | Table,
11 | TableBody,
12 | TableCell,
13 | TableContainer,
14 | TableHead,
15 | TableRow,
16 | Paper,
17 | } from '@material-ui/core'
18 | import { Notes as IconNotes, MoreVert as IconMoreVert } from '@material-ui/icons'
19 | import { FormattedDate } from 'react-intl'
20 |
21 | import { recentSubscriptions } from './data'
22 |
23 | export type SubscriptionsRecentProps = {}
24 |
25 | const SubscriptionsRecent: React.FC = () => {
26 | const classes = useStyles()
27 |
28 | return (
29 |
30 |
31 | }
34 | action={
35 |
36 |
37 |
38 | }
39 | title="Recent Subscriptions"
40 | />
41 |
42 |
43 |
44 |
45 | Organization
46 | Users
47 | Plan
48 | Date
49 |
50 |
51 |
52 | {recentSubscriptions.map((subscription) => (
53 |
54 |
55 | {subscription.organization}
56 |
57 | {subscription.numUsers}
58 | {subscription.plan}
59 |
60 |
67 |
68 |
69 | ))}
70 |
71 |
72 |
73 |
74 |
75 | View All
76 |
77 |
78 |
79 |
80 | )
81 | }
82 |
83 | const useStyles = makeStyles((theme) => ({
84 | card: {
85 | height: '100%',
86 | },
87 | cardHeader: {
88 | borderBottom: '1px solid',
89 | borderBottomColor: theme.palette.divider,
90 | },
91 | cardBody: {
92 | overflow: 'hidden',
93 | },
94 | headerTitleBox: {},
95 | headerActionsBox: {
96 | textAlign: 'right',
97 | },
98 | headerIcon: {
99 | color: theme.palette.grey[300],
100 | verticalAlign: 'sub',
101 | marginRight: '.3em',
102 | },
103 | }))
104 |
105 | export default SubscriptionsRecent
106 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsRecent/data.ts:
--------------------------------------------------------------------------------
1 | export const recentSubscriptions = [
2 | {
3 | organization: 'Apple',
4 | plan: 'Pro',
5 | numUsers: 144,
6 | created: '2020-04-16T02:04:22.406Z',
7 | },
8 | {
9 | organization: 'Google',
10 | numUsers: 3673,
11 | plan: 'Gold',
12 | created: '2020-04-14T12:03:15.406Z',
13 | },
14 | {
15 | organization: 'GitHub',
16 | numUsers: 36730,
17 | plan: 'Enterprise',
18 | created: '2020-04-13T21:32:04.406Z',
19 | },
20 | {
21 | organization: 'Microsoft',
22 | numUsers: 124,
23 | plan: 'Trial',
24 | created: '2020-04-10T11:03:46.406Z',
25 | },
26 | ]
27 |
--------------------------------------------------------------------------------
/src/Dashboard/SubscriptionsRecent/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SubscriptionsRecent'
2 |
--------------------------------------------------------------------------------
/src/Dashboard/dashboardContext.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import moment from 'moment'
3 |
4 | // The default context, which is used when there is no provider
5 | // (might be used for components testing)
6 | export const dashboardContextDefault = {
7 | filter: {
8 | dateFrom: moment().subtract(14, 'day').startOf('day'),
9 | dateTo: moment().startOf('day'),
10 | },
11 | }
12 |
13 | export const dashboardContext = React.createContext(dashboardContextDefault)
14 |
15 | export const dashboardProvider = dashboardContext.Provider
16 | export const dashboardConsumer = dashboardContext.Consumer
17 |
18 | export default dashboardContext
19 |
--------------------------------------------------------------------------------
/src/Dashboard/index.ts:
--------------------------------------------------------------------------------
1 | import Dashboard from './Dashboard'
2 |
3 | const DashboardModule = {}
4 |
5 | export { Dashboard }
6 |
7 | export default DashboardModule
8 |
--------------------------------------------------------------------------------
/src/Settings/README.md:
--------------------------------------------------------------------------------
1 | # Settings module
2 |
--------------------------------------------------------------------------------
/src/Settings/Settings.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Settings module
5 |
6 | The settings module is the place for managing all application specific settings like
7 |
8 | - Profile
9 | - Organizations
10 | - Security and authentications
11 | - Notifications
12 | - Any other settings
13 |
--------------------------------------------------------------------------------
/src/Settings/index.ts:
--------------------------------------------------------------------------------
1 | const SettingsModule = {
2 | init() {},
3 | }
4 |
5 | export default SettingsModule
6 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_data/notificationsData.ts:
--------------------------------------------------------------------------------
1 | const notificationsData = {}
2 |
3 | export default notificationsData
4 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_data/organizationsData.ts:
--------------------------------------------------------------------------------
1 | import _keyBy from 'lodash/keyBy'
2 | import Organization from '../../_types/Organization'
3 | // import organizationsToUsersData from './organizationsToUsersData'
4 |
5 | const list: Organization[] = [
6 | {
7 | id: 1,
8 | name: 'ModularCode',
9 | plan: {
10 | id: 'silver',
11 | name: 'Silver',
12 | },
13 | },
14 | {
15 | id: 2,
16 | name: 'Cool LLC',
17 | plan: {
18 | id: 'gold',
19 | name: 'Gold',
20 | },
21 | // organizationToUsers: organizationsToUsersData.byOrganizationId[2],
22 | },
23 | {
24 | id: 3,
25 | name: 'Other LLC',
26 | plan: {
27 | id: 'trial',
28 | name: 'Trial',
29 | },
30 | // organizationToUsers: organizationsToUsersData.byOrganizationId[3],
31 | },
32 | ]
33 |
34 | const byId: { [key: number]: Organization } = _keyBy(list, 'id')
35 |
36 | const organizationsData = {
37 | list,
38 | byId,
39 | }
40 |
41 | export default organizationsData
42 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_data/subscriptionPlansData.ts:
--------------------------------------------------------------------------------
1 | import { get as _get, keyBy as _keyBy } from 'lodash'
2 | import moment from 'moment'
3 | import Plan from '../../_types/SubscriptionPlan'
4 |
5 | const list: Plan[] = [
6 | {
7 | id: 1,
8 | name: 'Trial',
9 | price: 0,
10 | status: 'active',
11 | },
12 | {
13 | id: 2,
14 | name: 'Pro',
15 | price: 0,
16 | status: 'active',
17 | },
18 | {
19 | id: 2,
20 | name: 'Gold',
21 | price: 0,
22 | status: 'active',
23 | },
24 | ]
25 |
26 | const byId: { [key: number]: Plan } = _keyBy(list, 'id')
27 |
28 | const subscriptionPlansData = {
29 | list,
30 | byId,
31 | current: byId[1],
32 | }
33 |
34 | export default subscriptionPlansData
35 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_data/usersData.ts:
--------------------------------------------------------------------------------
1 | import { get as _get, keyBy as _keyBy } from 'lodash'
2 | import moment from 'moment'
3 | import User from '../../_types/User'
4 | import UserToOrganization from '../../_types/UserToOrganization'
5 | import usersToOrganizationsData from './usersToOrganizationsData'
6 | import organizationsData from './organizationsData'
7 |
8 | const list: User[] = [
9 | {
10 | id: 1,
11 | firstName: 'Gevorg',
12 | lastName: 'Harutyunyan',
13 | username: 'modularcoder',
14 | email: 'modularcoder@gmail.com',
15 | avatarUrl: 'https://avatars3.githubusercontent.com/u/3959008?v=3&s=40',
16 | globalRole: 'admin',
17 | status: 'active',
18 | },
19 | {
20 | id: 2,
21 | firstName: 'Jay',
22 | lastName: 'Nickolson',
23 | username: null,
24 | email: 'example@gmail.com',
25 | avatarUrl:
26 | 'https://tinyfac.es/data/avatars/475605E3-69C5-4D2B-8727-61B7BB8C4699-500w.jpeg',
27 | status: 'active',
28 | },
29 | {
30 | id: 3,
31 | firstName: 'Ana',
32 | lastName: 'De Armas',
33 | username: null,
34 | email: 'Ana+De+Armas@example.com',
35 | avatarUrl:
36 | 'https://images-na.ssl-images-amazon.com/images/M/MV5BMjA3NjYzMzE1MV5BMl5BanBnXkFtZTgwNTA4NDY4OTE@._V1_UX172_CR0,0,172,256_AL_.jpg',
37 | status: 'active',
38 | },
39 | {
40 | id: 4,
41 | firstName: 'Armas',
42 | lastName: 'De Ana',
43 | username: null,
44 | email: 'Ana+De+Armas@example.com',
45 | avatarUrl:
46 | 'https://images-na.ssl-images-amazon.com/images/M/MV5BMTc0MzgxMzQ5N15BMl5BanBnXkFtZTgwMzMzNjkwOTE@._V1_UX172_CR0,0,172,256_AL_.jpg',
47 | status: 'active',
48 | },
49 | {
50 | id: 5,
51 | firstName: 'Sonequa',
52 | lastName: 'Martin-Green',
53 | email: 'Sonequa+Martin+Green@example.com',
54 | avatarUrl:
55 | 'https://images-na.ssl-images-amazon.com/images/M/MV5BMTgxMTc1MTYzM15BMl5BanBnXkFtZTgwNzI5NjMwOTE@._V1_UY256_CR16,0,172,256_AL_.jpg',
56 | status: 'disabled',
57 | },
58 | {
59 | id: 6,
60 | firstName: 'Bessie',
61 | lastName: 'Walker',
62 | username: 'bwalk',
63 | email: 'bessie.walker@example.com',
64 | avatarUrl: 'https://randomuser.me/api/portraits/women/29.jpg',
65 | globalRole: 'admin',
66 | status: 'active',
67 | },
68 | {
69 | id: 7,
70 | firstName: 'Scarlett',
71 | lastName: 'Sanders',
72 | username: 'sanders',
73 | email: 'scarlett.sanders@example.com',
74 | avatarUrl: 'https://randomuser.me/api/portraits/women/26.jpg',
75 | status: 'active',
76 | },
77 | {
78 | id: 8,
79 | firstName: 'Scott',
80 | lastName: 'Jensen',
81 | username: 'scjx',
82 | email: 'scott.jensen@example.com',
83 | avatarUrl: 'https://randomuser.me/api/portraits/men/87.jpg',
84 | status: 'pending',
85 | },
86 | {
87 | id: 9,
88 | firstName: 'Marcus ',
89 | lastName: 'Barrett',
90 | username: null,
91 | email: 'marcus.barrett@example.com',
92 | avatarUrl: 'https://randomuser.me/api/portraits/men/88.jpg',
93 | status: 'pending',
94 | },
95 | {
96 | id: 10,
97 | firstName: 'Penny',
98 | lastName: 'Lawrence',
99 | email: 'penny.lawrence@example.com',
100 | avatarUrl: 'https://randomuser.me/api/portraits/women/79.jpg',
101 | status: 'active',
102 | },
103 | {
104 | id: 11,
105 | firstName: 'Melvin',
106 | lastName: 'Sutton',
107 | username: 'johndoe1',
108 | email: 'melvin.sutton@example.com',
109 | avatarUrl: 'https://randomuser.me/api/portraits/men/85.jpg',
110 | globalRole: 'admin',
111 | status: 'disabled',
112 | },
113 | {
114 | id: 12,
115 | firstName: 'Della',
116 | lastName: 'Case',
117 | username: null,
118 | email: 'della.case@example.com',
119 | avatarUrl:
120 | 'https://images.unsplash.com/photo-1510227272981-87123e259b17?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=3759e09a5b9fbe53088b23c615b6312e',
121 | status: 'pending',
122 | },
123 | {
124 | id: 13,
125 | firstName: 'Fischer',
126 | lastName: 'Garland',
127 | username: 'fgfr',
128 | email: 'Fischer+Garland@example.com',
129 | avatarUrl:
130 | 'https://images.unsplash.com/photo-1456327102063-fb5054efe647?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=f05c14dd4db49f08a789e6449604c490',
131 | status: 'active',
132 | },
133 | {
134 | id: 14,
135 | firstName: 'Abdullah',
136 | lastName: 'Hadley',
137 | username: 'hadley',
138 | email: 'Hadley+Abdullah@example.com',
139 | avatarUrl:
140 | 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=a72ca28288878f8404a795f39642a46f',
141 | status: 'active',
142 | },
143 | {
144 | id: 15,
145 | firstName: 'Lucy',
146 | lastName: 'Walker',
147 | email: 'Lucy+Walker@example.com',
148 | avatarUrl: 'https://randomuser.me/api/portraits/women/0.jpg',
149 | status: 'active',
150 | },
151 | ].map((user) => {
152 | const userToOrganization = usersToOrganizationsData.byUserId[user.id] || []
153 |
154 | return {
155 | ...user,
156 | userOgranizations: userToOrganization.map((relation: UserToOrganization) => {
157 | return {
158 | ...relation,
159 | organization: _get(organizationsData.byId, relation.organizationId, {}),
160 | }
161 | }),
162 | createdAt: moment().subtract(user.id, 'days').format(),
163 | }
164 | })
165 |
166 | const byId: { [key: number]: User } = _keyBy(list, 'id')
167 |
168 | const usersData = {
169 | list,
170 | byId,
171 | current: byId[1],
172 | }
173 |
174 | export default usersData
175 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_data/usersToOrganizationsData.ts:
--------------------------------------------------------------------------------
1 | import _groupBy from 'lodash/groupBy'
2 | import UserToOrganization from '../../_types/UserToOrganization'
3 |
4 | // import organizationsData from './organizationsData'
5 | // import usersData from './usersData'
6 |
7 | const list: UserToOrganization[] = [
8 | {
9 | id: 1,
10 | organizationId: 1,
11 | userId: 1,
12 | role: 'owner',
13 | // organization: organizationsData.byId[1],
14 | // user: usersData.byId[1],
15 | },
16 | {
17 | id: 2,
18 | organizationId: 1,
19 | userId: 2,
20 | role: 'admin',
21 | // organization: organizationsData.byId[1],
22 | // user: usersData.byId[2],
23 | },
24 | {
25 | id: 3,
26 | organizationId: 1,
27 | userId: 2,
28 | role: 'member',
29 | // organization: organizationsData.byId[1],
30 | // user: usersData.byId[2],
31 | },
32 | {
33 | id: 4,
34 | organizationId: 2,
35 | userId: 2,
36 | role: 'owner',
37 | // organization: organizationsData.byId[2],
38 | // user: usersData.byId[2],
39 | },
40 | {
41 | id: 5,
42 | organizationId: 3,
43 | userId: 3,
44 | role: 'owner',
45 | // organization: organizationsData.byId[3],
46 | // user: usersData.byId[3],
47 | },
48 | {
49 | id: 6,
50 | organizationId: 3,
51 | userId: 2,
52 | role: 'member',
53 | // organization: organizationsData.byId[3],
54 | // user: usersData.byId[2],
55 | },
56 | ]
57 |
58 | const byUserId = _groupBy(list, 'userId')
59 | const byOrganizationId = _groupBy(list, 'organizationId')
60 |
61 | const usersToOrganizationsData = {
62 | list,
63 | byUserId,
64 | byOrganizationId,
65 | }
66 |
67 | export default usersToOrganizationsData
68 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_ref/index.ts:
--------------------------------------------------------------------------------
1 | console.log('hello mocks!')
2 |
3 | export default {}
4 |
5 | // import { AxiosInstance } from 'axios'
6 | // import MockAdapter from 'axios-mock-adapter'
7 |
8 | // import usersMocks from './usersMocks'
9 | // import organizationsMocks from './organizationsMocks'
10 |
11 | // const init = (instance: AxiosInstance) => {
12 | // const mockAdapter = new MockAdapter(instance, { delayResponse: 200 })
13 |
14 | // usersMocks.init(mockAdapter, instance)
15 | // organizationsMocks.init(mockAdapter, instance)
16 |
17 | // return mockAdapter
18 | // }
19 |
20 | // export default {
21 | // init,
22 | // }
23 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_ref/organizationsMocksAxios.ts:
--------------------------------------------------------------------------------
1 | import { AxiosInstance } from 'axios'
2 | import MockAdapter from 'axios-mock-adapter'
3 | import organizationsData from './../_data/organizationsData'
4 |
5 | export default {
6 | init(mock: MockAdapter, instance: AxiosInstance) {
7 | mock.onGet('/organizations').reply(200, {
8 | organizations: {
9 | ...organizationsData.list,
10 | },
11 | count: organizationsData.list.length,
12 | })
13 |
14 | //
15 | mock.onGet(/\/organizations\/\d+/).reply((config: any) => {
16 | // console.log(config)
17 | const urlPaths = config.url.split('/')
18 | const organizationId = urlPaths[urlPaths.length - 1]
19 | const organization = organizationsData.byId[organizationId]
20 |
21 | if (organization) {
22 | return [200, { ...organization }]
23 | } else {
24 | return [404, { message: 'Organization not found ' }]
25 | }
26 | })
27 |
28 | mock.onPut(/\/organizations\/\d+/).reply((config: any) => {
29 | const urlPaths = config.url.split('/')
30 | const organizationId = urlPaths[urlPaths.length - 1]
31 | const organizationData = JSON.parse(config.data)
32 | const organization = organizationsData.byId[organizationId]
33 |
34 | if (organization) {
35 | return [200, { ...organization, ...organizationData }]
36 | } else {
37 | return [403, { message: 'Update not permitted' }]
38 | }
39 | })
40 |
41 | mock.onPost(/\/organizations/).reply((config: any) => {
42 | const organizationData = JSON.parse(config.data)
43 |
44 | return [200, { id: 100, ...organizationData }]
45 | })
46 |
47 | mock.onDelete(/\/organizations\/\d+/).reply((config: any) => {
48 | return [200, { message: 'Organization deleted' }]
49 | })
50 | },
51 | }
52 |
--------------------------------------------------------------------------------
/src/_api/_mocks/_ref/usersMocksAxios.ts:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { AxiosInstance } from 'axios'
3 | import MockAdapter from 'axios-mock-adapter'
4 | import usersData from '../_data/usersData'
5 |
6 | export default {
7 | init(mock: MockAdapter, instance: AxiosInstance) {
8 | mock.onGet('/users/profile').reply(200, {
9 | ...usersData.current,
10 | })
11 |
12 | mock.onGet('/users').reply(config => {
13 | const { limit = 10, offset = 0, order = {}, customResponse } = config.params
14 |
15 | if (customResponse) {
16 | return [
17 | customResponse.status || 403,
18 | {
19 | message: customResponse.message || 'Something went wrong...',
20 | },
21 | ]
22 | }
23 |
24 | const usersAll = order
25 | ? _.orderBy(usersData.list, [order.orderBy], [order.order])
26 | : usersData.list
27 |
28 | if (order) {
29 | }
30 |
31 | return [
32 | 200,
33 | {
34 | users: usersAll.slice(offset, offset + limit),
35 | count: usersAll.length,
36 | },
37 | ]
38 | })
39 |
40 | //
41 | mock.onGet(/\/users\/\d+/).reply((config: any) => {
42 | // console.log(config)
43 | const urlPaths = config.url.split('/')
44 | const userId = urlPaths[urlPaths.length - 1]
45 | const user = usersData.byId[userId]
46 |
47 | if (user) {
48 | return [200, { ...user }]
49 | } else {
50 | return [404, { message: 'User not found ' }]
51 | }
52 | })
53 |
54 | mock.onPut(/\/users\/\d+/).reply((config: any) => {
55 | const urlPaths = config.url.split('/')
56 | const userId = urlPaths[urlPaths.length - 1]
57 | const userData = JSON.parse(config.data)
58 | const user = usersData.byId[userId]
59 |
60 | if (user) {
61 | return [200, { ...user, ...userData }]
62 | } else {
63 | return [403, { message: 'Update not permitted' }]
64 | }
65 | })
66 |
67 | mock.onPost(/\/users/).reply((config: any) => {
68 | const userData = JSON.parse(config.data)
69 |
70 | console.log('config', config)
71 | console.log('userData', userData)
72 |
73 | return [200, { id: 100, ...userData }]
74 | })
75 |
76 | mock.onDelete(/\/users\/\d+/).reply((config: any) => {
77 | return [200, { message: 'User removed' }]
78 | })
79 | },
80 | }
81 |
--------------------------------------------------------------------------------
/src/_api/_mocks/index.ts:
--------------------------------------------------------------------------------
1 | import { setupWorker } from 'msw'
2 | import { setupServer } from 'msw/node'
3 | import usersMocks from './usersMocks'
4 |
5 | const start =
6 | process.env.NODE_ENV !== 'test'
7 | ? setupWorker(...usersMocks).start
8 | : setupServer(...usersMocks)
9 |
10 | export default {
11 | init: start,
12 | }
13 |
--------------------------------------------------------------------------------
/src/_api/_mocks/organizationsMocks.ts:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { rest } from 'msw'
3 |
4 | import config from '_config'
5 | import organizationsData from './_data/organizationsData'
6 |
7 | const apiUrl = config.api.url
8 |
9 | const organizationsMocks = [
10 | rest.get(`${apiUrl}/organizations`, (req, res, ctx) => {
11 | const { limit = 10, offset = 0, order = {} } = req.params
12 | const organizationsAll = order
13 | ? _.orderBy(organizationsData.list, [order.orderBy], [order.order])
14 | : organizationsData.list
15 |
16 | const result = {
17 | organizations: organizationsAll.slice(offset, offset + limit),
18 | count: organizationsAll.length,
19 | }
20 |
21 | return res(
22 | // Set custom status
23 | ctx.status(200),
24 | // Delay the response
25 | ctx.delay(1000),
26 | // send JSON response body
27 | ctx.json(result),
28 | )
29 | }),
30 |
31 | rest.get(`${apiUrl}/organizations/:organizationId`, (req, res, ctx) => {
32 | const { organizationId } = req.params
33 | const organization = organizationsData.byId[organizationId]
34 |
35 | if (organization) {
36 | return res(ctx.status(200), ctx.delay(200), ctx.json(organization))
37 | } else {
38 | return res(
39 | ctx.status(404),
40 | ctx.status(200),
41 | ctx.json({
42 | message: 'organization not found',
43 | }),
44 | )
45 | }
46 | }),
47 |
48 | rest.post(`${apiUrl}/organizations`, (req, res, ctx) => {
49 | return res(
50 | ctx.status(200),
51 | ctx.delay(200),
52 | ctx.json({
53 | // @ts-ignore
54 | ...req.body,
55 | }),
56 | )
57 | }),
58 |
59 | rest.put(`${apiUrl}/organizations/:organizationId`, (req, res, ctx) => {
60 | const { organizationId } = req.params
61 | const organization = organizationsData.byId[organizationId]
62 |
63 | if (organization) {
64 | return res(
65 | ctx.status(200),
66 | ctx.delay(200),
67 | ctx.json({
68 | ...organization,
69 | // ...req.body,
70 | }),
71 | )
72 | } else {
73 | return res(ctx.status(403), ctx.json({ message: 'Update not permitted' }))
74 | }
75 | }),
76 |
77 | rest.delete(`${apiUrl}/organizations/:organizationId`, (req, res, ctx) => {
78 | const { organizationId } = req.params
79 | const organization = organizationsData.byId[organizationId]
80 |
81 | if (organization) {
82 | return res(
83 | ctx.status(200),
84 | ctx.delay(200),
85 | ctx.json({
86 | message: 'Organization removed',
87 | }),
88 | )
89 | } else {
90 | return res(
91 | ctx.status(403),
92 | ctx.json({ message: 'Organization not found or forbidden' }),
93 | )
94 | }
95 | }),
96 | ]
97 |
98 | export default organizationsMocks
99 |
--------------------------------------------------------------------------------
/src/_api/_mocks/plansMocks.ts:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { rest } from 'msw'
3 |
4 | import config from '_config'
5 | import subscriptionPlansData from './_data/subscriptionPlansData'
6 |
7 | const apiUrl = config.api.url
8 |
9 | const userMocks = [
10 | rest.get(`${apiUrl}/subscriptionPlans`, async (req, res, ctx) => {
11 | let limit = parseInt(req.params.get('limit') || '0')
12 | let offset = parseInt(req.params.get('offset') || '10')
13 | let order = JSON.parse(req.params.get('order') || '{}')
14 |
15 | const subscriptionPlans = order
16 | ? _.orderBy(subscriptionPlansData.list, [order.orderBy], [order.order])
17 | : subscriptionPlansData.list
18 |
19 | const result = {
20 | subscriptionPlans: subscriptionPlans.slice(offset, offset + limit),
21 | count: subscriptionPlans.length,
22 | }
23 |
24 | return res(
25 | // Set custom status
26 | ctx.status(200),
27 | // Delay the response
28 | ctx.delay(500),
29 | // send JSON response body
30 | ctx.json(result),
31 | )
32 | }),
33 |
34 | rest.post(`${apiUrl}/subscriptionPlans`, (req, res, ctx) => {
35 | return res(
36 | ctx.status(200),
37 | ctx.delay(200),
38 | ctx.json({
39 | // @ts-ignore
40 | ...req.body,
41 | }),
42 | )
43 | }),
44 |
45 | rest.put(`${apiUrl}/subscriptionPlans/:subscriptionPlanId`, (req, res, ctx) => {
46 | const { subscriptionPlanId } = req.params
47 | const user = subscriptionPlansData.byId[subscriptionPlanId]
48 |
49 | if (user) {
50 | return res(
51 | ctx.status(200),
52 | ctx.delay(200),
53 | ctx.json({
54 | ...user,
55 | // ...req.body,
56 | }),
57 | )
58 | } else {
59 | return res(ctx.status(403), ctx.json({ message: 'Update not permitted' }))
60 | }
61 | }),
62 |
63 | rest.delete(`${apiUrl}/subscriptionPlans/:subscriptionPlanId`, (req, res, ctx) => {
64 | const { subscriptionPlanId } = req.params
65 | const user = subscriptionPlansData.byId[subscriptionPlanId]
66 |
67 | if (user) {
68 | return res(
69 | ctx.status(200),
70 | ctx.delay(200),
71 | ctx.json({
72 | message: 'User removed',
73 | }),
74 | )
75 | } else {
76 | return res(ctx.status(403), ctx.json({ message: 'User not found or forbidden' }))
77 | }
78 | }),
79 | ]
80 |
81 | export default userMocks
82 |
--------------------------------------------------------------------------------
/src/_api/_mocks/usersMocks.ts:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { rest } from 'msw'
3 |
4 | import config from '_config'
5 | import usersData from './_data/usersData'
6 |
7 | const apiUrl = config.api.url
8 |
9 | const userMocks = [
10 | rest.get(`${apiUrl}/users/profile`, (rex, res, ctx) => {
11 | return res(ctx.json(usersData.current))
12 | }),
13 |
14 | rest.get(`${apiUrl}/users`, async (req, res, ctx) => {
15 | let limit = parseInt(req.params.limit || '10')
16 | let offset = parseInt(req.params.offset || '0')
17 | let order = req.params.order ? JSON.parse(req.params.order || '{}') : null
18 |
19 | const usersAll = order
20 | ? _.orderBy(usersData.list, [order.orderBy], [order.order])
21 | : usersData.list
22 |
23 | const result = {
24 | users: usersAll.slice(offset, offset + limit),
25 | count: usersAll.length,
26 | }
27 |
28 | return res(
29 | // Set custom status
30 | ctx.status(200),
31 | // Delay the response
32 | ctx.delay(500),
33 | // send JSON response body
34 | ctx.json(result),
35 | )
36 | }),
37 |
38 | rest.get(`${apiUrl}/users/:userId`, (req, res, ctx) => {
39 | const { userId } = req.params
40 | const user = usersData.byId[userId]
41 |
42 | if (user) {
43 | return res(ctx.status(200), ctx.delay(200), ctx.json(user))
44 | } else {
45 | return res(
46 | ctx.status(404),
47 | ctx.status(200),
48 | ctx.json({
49 | message: 'User not found',
50 | }),
51 | )
52 | }
53 | }),
54 |
55 | rest.post(`${apiUrl}/users`, (req, res, ctx) => {
56 | return res(
57 | ctx.status(200),
58 | ctx.delay(200),
59 | ctx.json({
60 | // @ts-ignore
61 | ...req.body,
62 | }),
63 | )
64 | }),
65 |
66 | rest.put(`${apiUrl}/users/:userId`, (req, res, ctx) => {
67 | const { userId } = req.params
68 | const user = usersData.byId[userId]
69 |
70 | if (user) {
71 | return res(
72 | ctx.status(200),
73 | ctx.delay(200),
74 | ctx.json({
75 | ...user,
76 | // ...req.body,
77 | }),
78 | )
79 | } else {
80 | return res(ctx.status(403), ctx.json({ message: 'Update not permitted' }))
81 | }
82 | }),
83 |
84 | rest.delete(`${apiUrl}/users/:userId`, (req, res, ctx) => {
85 | const { userId } = req.params
86 | const user = usersData.byId[userId]
87 |
88 | if (user) {
89 | return res(
90 | ctx.status(200),
91 | ctx.delay(200),
92 | ctx.json({
93 | message: 'User removed',
94 | }),
95 | )
96 | } else {
97 | return res(ctx.status(403), ctx.json({ message: 'User not found or forbidden' }))
98 | }
99 | }),
100 | ]
101 |
102 | export default userMocks
103 |
--------------------------------------------------------------------------------
/src/_api/_types/Entity.ts:
--------------------------------------------------------------------------------
1 | export type EntityId = number | string
2 |
3 | export default interface Entity {
4 | createdAt?: string
5 | updatedAt?: string
6 | }
7 |
--------------------------------------------------------------------------------
/src/_api/_types/Organization.ts:
--------------------------------------------------------------------------------
1 | import Entity, { EntityId } from './Entity'
2 | import User from './User'
3 | import UserToOrganization from './UserToOrganization'
4 |
5 | export type OrganizationId = EntityId
6 | export interface OrganizationPlan {
7 | id: number | string
8 | name: string
9 | features?: {}
10 | }
11 |
12 | export interface OrganizationSubmissionData {
13 | name: string
14 | username?: string
15 | }
16 |
17 | export default interface Organization extends OrganizationSubmissionData, Entity {
18 | id: OrganizationId
19 | plan: OrganizationPlan
20 | users?: User[]
21 | organizationUsers?: UserToOrganization[]
22 | }
23 |
--------------------------------------------------------------------------------
/src/_api/_types/SubscriptionPlan.ts:
--------------------------------------------------------------------------------
1 | import Entity, { EntityId } from './Entity'
2 |
3 | export type SubscriptionPlanId = EntityId
4 |
5 | export interface SubscriptionPlanSubmissionData {
6 | name?: string
7 | price: number
8 | status?: 'active' | 'disabled'
9 | }
10 |
11 | export default interface SubscriptionPlan extends SubscriptionPlanSubmissionData, Entity {
12 | id: SubscriptionPlanId
13 | }
14 |
--------------------------------------------------------------------------------
/src/_api/_types/User.ts:
--------------------------------------------------------------------------------
1 | import Entity, { EntityId } from './Entity'
2 |
3 | import Organization from './Organization'
4 | import UserToOrganization from './UserToOrganization'
5 |
6 | export type UserId = EntityId
7 |
8 | // global user role across the system (useful for SAAS or if organizations arn't used)
9 | // Each user can have only one global role
10 | // export type UserGlobalRole = 'admin' | 'support' | 'member'
11 |
12 | export interface UserSubmissionData {
13 | firstName?: string
14 | lastName?: string
15 | displayName?: string
16 | username?: string | null
17 | email: string
18 | password?: string
19 | avatarUrl?: string
20 | globalRole?: string
21 | status?: string
22 | }
23 |
24 | export interface User extends UserSubmissionData, Entity {
25 | id: UserId
26 | organizations?: Organization[]
27 | userToOrganizations?: UserToOrganization[]
28 | }
29 |
30 | export default User
31 |
--------------------------------------------------------------------------------
/src/_api/_types/UserToOrganization.ts:
--------------------------------------------------------------------------------
1 | import Entity, { EntityId } from './Entity'
2 |
3 | import Organization, { OrganizationId } from './Organization'
4 | import User, { UserId } from './User'
5 |
6 | export type UserToOrganizationId = EntityId
7 | export type UserRole = 'member' | 'admin' | 'owner'
8 |
9 | export default interface UserToOrganization extends Entity {
10 | id: UserToOrganizationId
11 | organizationId: OrganizationId
12 | userId: UserId
13 | role: UserRole
14 | organization?: Organization
15 | user?: User
16 | }
17 |
--------------------------------------------------------------------------------
/src/_api/client.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import config from '../_config'
3 | import authService from '../_services/authService'
4 |
5 | const apiService = axios.create({
6 | baseURL: config.api.url,
7 | })
8 |
9 | // Use the Token header for all requests
10 | apiService.interceptors.request.use(
11 | (config) => {
12 | const token = authService.getToken()
13 | config.headers.Authorization = `Bearer ${token}`
14 |
15 | return config
16 | },
17 | (error) => {
18 | return Promise.reject(error)
19 | },
20 | )
21 |
22 | // Unauth the token if we get 401 unauth response from the server
23 | apiService.interceptors.response.use(
24 | (response) => {
25 | return response
26 | },
27 | (error) => {
28 | if (error.response.status === 401) {
29 | authService.unauth()
30 | }
31 |
32 | return Promise.reject(error)
33 | },
34 | )
35 |
36 | export default apiService
37 |
--------------------------------------------------------------------------------
/src/_api/index.ts:
--------------------------------------------------------------------------------
1 | import instance from './client'
2 | import organizations from './organizations'
3 | import users from './users'
4 | import config from '_config'
5 |
6 | let apiMocks: any
7 |
8 | if (config.api.useMocks || process.env.NODE_ENV === 'test') {
9 | apiMocks = require('./_mocks/')
10 | }
11 |
12 | const init = async () => {
13 | if (apiMocks) {
14 | // Remove all SW caches
15 | const cachesNames = await caches.keys()
16 |
17 | await Promise.all(cachesNames.map((name) => caches.delete(name)))
18 |
19 | await apiMocks.default.init()
20 | }
21 |
22 | return instance
23 | }
24 |
25 | export default { instance, organizations, users, init }
26 | export { init, instance, organizations, users }
27 |
--------------------------------------------------------------------------------
/src/_api/organizations.ts:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios'
2 | import Organization, {
3 | OrganizationSubmissionData,
4 | OrganizationId,
5 | } from './_types/Organization'
6 | import apiClient from './client'
7 |
8 | export interface IOrganizationsService {
9 | getOne(organizationId: OrganizationId): Promise
10 | getList(params: any): Promise
11 | create(organization: OrganizationSubmissionData): Promise
12 | update(
13 | organizationId: OrganizationId,
14 | organization: OrganizationSubmissionData,
15 | ): Promise
16 | remove(organizationId: OrganizationId): Promise
17 | }
18 |
19 | export interface OrganizationsListResponse {
20 | organizations: Organization[]
21 | count: number
22 | }
23 |
24 | const OrganizationsService: IOrganizationsService = {
25 | getOne(organizationId) {
26 | return apiClient
27 | .get(`/organizations/${organizationId}`)
28 | .then((res: AxiosResponse) => res.data)
29 | },
30 | getList(params: any) {
31 | return apiClient
32 | .get(`/organizations`, {
33 | params,
34 | })
35 | .then((res: AxiosResponse) => res.data)
36 | },
37 | create(organization: OrganizationSubmissionData) {
38 | return apiClient
39 | .post(`/organizations`, organization)
40 | .then((res: AxiosResponse) => res.data)
41 | },
42 | update(organizationId, organization) {
43 | return apiClient
44 | .put(`/organizations/${organizationId}`, organization)
45 | .then((res: AxiosResponse) => res.data)
46 | },
47 | remove(organizationId) {
48 | return apiClient
49 | .delete(`/organizations/${organizationId}`)
50 | .then((res: AxiosResponse) => res.data)
51 | },
52 | }
53 |
54 | export default OrganizationsService
55 |
--------------------------------------------------------------------------------
/src/_api/users.ts:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios'
2 | import User, { UserSubmissionData, UserId } from './_types/User'
3 | import apiClient from './client'
4 |
5 | export interface UsersService {
6 | getProfile(): Promise
7 | getOne(userId: UserId): Promise
8 | getList(params?: any): Promise
9 | create(user: UserSubmissionData): Promise
10 | update(userId: UserId, user: UserSubmissionData): Promise
11 | remove(userId: UserId): Promise
12 | }
13 |
14 | export interface UsersListResponse {
15 | users: User[]
16 | count: number
17 | }
18 |
19 | const usersService: UsersService = {
20 | getProfile() {
21 | return apiClient.get('/users/profile').then((res: AxiosResponse) => res.data)
22 | },
23 | getOne(userId) {
24 | return apiClient.get(`/users/${userId}`).then((res: AxiosResponse) => res.data)
25 | },
26 | getList(params: any) {
27 | return apiClient
28 | .get(`/users`, {
29 | params,
30 | })
31 | .then((res: AxiosResponse) => res.data)
32 | },
33 | create(user: UserSubmissionData) {
34 | return apiClient.post(`/users`, user).then((res: AxiosResponse) => res.data)
35 | },
36 | update(userId, user) {
37 | return apiClient
38 | .put(`/users/${userId}`, user)
39 | .then((res: AxiosResponse) => res.data)
40 | },
41 | remove(userId) {
42 | return apiClient
43 | .delete(`/users/${userId}`)
44 | .then((res: AxiosResponse) => res.data)
45 | },
46 | }
47 |
48 | export default usersService
49 |
--------------------------------------------------------------------------------
/src/_common/AppFooter/AppFooter.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 | import AppFooter from './AppFooter.js'
3 |
4 |
5 |
6 | # App Footer
7 |
8 | Application footer component.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/_common/AppFooter/AppFooter.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import Typography from '@material-ui/core/Typography'
3 | import Link from '@material-ui/core/Link'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import pkg from '../../../package.json'
6 |
7 | type AppFooterProps = {}
8 |
9 | const AppFooter: React.FC = () => {
10 | console.log('AppFooter rendered')
11 |
12 | const classes = useStyles()
13 |
14 | return (
15 |
16 |
17 | {`Modular Admin React`}{' '}
18 |
22 | v{pkg.version}
23 |
24 | {' | '}
25 |
29 | MIT License
30 |
31 |
32 |
33 |
34 | GitHub
35 |
36 | {' | '}
37 |
38 | LinkedIn
39 |
40 |
41 |
42 | {'Built with '}
43 |
44 | Material-UI
45 |
46 | {' by '}
47 |
48 | Gevorg Harutyunyan
49 |
50 |
51 |
52 | )
53 | }
54 |
55 | const useStyles = makeStyles((theme) => ({
56 | footer: {
57 | display: 'flex',
58 | background: '#fff',
59 | padding: '0.5rem 1rem',
60 | justifyContent: 'space-between',
61 | },
62 | }))
63 |
64 | export default memo(AppFooter)
65 |
--------------------------------------------------------------------------------
/src/_common/AppFooter/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AppFooter'
2 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/AppHeader.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 | import AppHeader from './AppHeader.js'
3 |
4 |
5 |
6 | # App Header
7 |
8 | Application header component.
9 |
10 |
11 |
12 | alert('Clicked toggle button!')} />
14 |
15 |
16 |
17 | ## Props
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/AppHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import clsx from 'clsx'
3 |
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import AppBar from '@material-ui/core/AppBar'
6 | import Toolbar from '@material-ui/core/Toolbar'
7 | import IconButton from '@material-ui/core/IconButton'
8 | import IconMenu from '@material-ui/icons/Menu'
9 |
10 | import HeaderDemo from './AppHeaderDemoButtons'
11 | import HeaderProfile from './AppHeaderProfile'
12 | // import HeaderSearch from './AppHeaderSearch'
13 | // import HeaderNotifications from './AppHeaderNotifications'
14 |
15 | export type AppHeaderProps = {
16 | onToggleClick(): void
17 | }
18 |
19 | const AppHeader: React.FC = ({ onToggleClick }) => {
20 | console.log('AppHeader rendered')
21 |
22 | const classes = useStyles()
23 |
24 | return (
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | const useStyles = makeStyles((theme) => ({
46 | header: {
47 | background: '#fff',
48 | color: '#7b7b7b',
49 | boxShadow: 'none',
50 | },
51 | toolbar: {},
52 | menuButton: {},
53 | actions: {
54 | marginLeft: 'auto',
55 | alignItems: 'center',
56 | display: 'flex',
57 | },
58 | notificationsButton: {
59 | marginRight: 23,
60 | },
61 | title: {
62 | flexGrow: 1,
63 | },
64 | }))
65 |
66 | export default memo(AppHeader)
67 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/AppHeaderDemoButtons.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { makeStyles } from '@material-ui/core/styles'
4 | import Tooltip from '@material-ui/core/Tooltip'
5 | import Button from '@material-ui/core/Button'
6 | import IconCode from '@material-ui/icons/Code'
7 | import IconStar from '@material-ui/icons/Star'
8 | import IconDownload from '@material-ui/icons/GetApp'
9 |
10 | export interface IAppHeaderDemoButtonsProps {}
11 |
12 | const AppHeaderDemoButtons: React.FC = (props) => {
13 | const classes = useStyles(props)
14 |
15 | return (
16 |
17 |
18 |
25 |
26 | View on GitHub
27 |
28 |
29 |
30 |
37 |
38 | Download
39 |
40 |
41 |
42 |
49 |
50 | Rate
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | const useStyles = makeStyles((theme) => ({
58 | demo: {
59 | flex: 1,
60 | display: 'flex',
61 | justifyContent: 'center',
62 | alignItems: 'center',
63 | },
64 | demoIcon: {},
65 | demoName: {
66 | marginLeft: theme.spacing(1),
67 | [theme.breakpoints.down('md')]: {
68 | display: 'none',
69 | },
70 | },
71 | button: {
72 | margin: theme.spacing(1),
73 | [theme.breakpoints.down('md')]: {
74 | margin: 3,
75 | },
76 | },
77 | }))
78 |
79 | export default AppHeaderDemoButtons
80 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/AppHeaderNotifications.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react'
2 |
3 | import { makeStyles } from '@material-ui/core/styles'
4 | import IconButton from '@material-ui/core/IconButton'
5 | import Badge from '@material-ui/core/Badge'
6 | import IconNotifications from '@material-ui/icons/Notifications'
7 | import Menu from '@material-ui/core/Menu'
8 | import List from '@material-ui/core/List'
9 | import ListItem from '@material-ui/core/ListItem'
10 | import ListItemText from '@material-ui/core/ListItemText'
11 | import ListItemAvatar from '@material-ui/core/ListItemAvatar'
12 | import Avatar from '@material-ui/core/Avatar'
13 | import Typography from '@material-ui/core/Typography'
14 |
15 | import { Theme } from '_theme/'
16 |
17 | const notifications = [
18 | {
19 | user: {
20 | name: 'Remy Sharp',
21 | image: 'https://material-ui.com/static/images/avatar/1.jpg',
22 | },
23 | title: 'New Order',
24 | content: " — I'll be in your neighborhood doing errands this…",
25 | },
26 | {
27 | user: {
28 | name: 'Travis Howard',
29 | image: 'https://material-ui.com//static/images/avatar/2.jpg',
30 | },
31 | title: 'New Signup',
32 | content: " — Wish I could come, but I'm out of town this…",
33 | },
34 | {
35 | user: {
36 | name: 'Oui Oui',
37 | image: 'https://material-ui.com//static/images/avatar/3.jpg',
38 | },
39 | title: 'Refund Request',
40 | content: 'please provide me a refund for my order',
41 | },
42 | ]
43 |
44 | export type AppHeaderNotificationsProps = {}
45 |
46 | const AppHeaderNotifications: React.FC = () => {
47 | const classes = useStyles()
48 | const [anchorEl, setAnchorEl] = React.useState()
49 |
50 | function handleClick(event: MouseEvent) {
51 | setAnchorEl(event.currentTarget)
52 | }
53 |
54 | function handleClose() {
55 | setAnchorEl(undefined)
56 | }
57 |
58 | return (
59 |
60 |
69 |
70 |
71 |
72 |
73 |
117 |
118 | )
119 | }
120 |
121 | // const HeaderNotificationsContent = () => {
122 | // const classes = useStyles()
123 |
124 | // return
125 | // }
126 |
127 | const useStyles = makeStyles((theme) => ({
128 | headerNotifications: {
129 | marginRight: 23,
130 | // position: 'relative',
131 | // position: 'absolute'
132 | },
133 | notificationsContainer: {
134 | // position: 'relative',
135 | },
136 | button: {},
137 | badge: {
138 | color: '#fff',
139 | },
140 | notifications: {
141 | // width: 360,
142 | maxWidth: 360,
143 | backgroundColor: theme.palette.background.paper,
144 | },
145 | inline: {
146 | display: 'inline',
147 | },
148 | }))
149 |
150 | export default AppHeaderNotifications
151 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/AppHeaderProfile.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react'
2 | import clsx from 'clsx'
3 |
4 | import { Link } from 'react-router-dom'
5 |
6 | import { makeStyles } from '@material-ui/core/styles'
7 | import IconButton from '@material-ui/core/IconButton'
8 | import Avatar from '@material-ui/core/Avatar'
9 | import Menu from '@material-ui/core/Menu'
10 | import MenuItem from '@material-ui/core/MenuItem'
11 | import ListItemText from '@material-ui/core/ListItemText'
12 | import ListItemIcon from '@material-ui/core/ListItemIcon'
13 | import Divider from '@material-ui/core/Divider'
14 |
15 | import IconArrowDropDown from '@material-ui/icons/ArrowDropDown'
16 | import IconProfile from '@material-ui/icons/AccountBox'
17 | import IconAccount from '@material-ui/icons/AccountBalance'
18 | import IconSettings from '@material-ui/icons/Settings'
19 | import IconLogout from '@material-ui/icons/ExitToApp'
20 |
21 | const AppHeaderProfile: React.FC = () => {
22 | const classes = useStyles()
23 | const [anchorEl, setAnchorEl] = React.useState()
24 | const user = {
25 | firstName: 'Gevorg',
26 | }
27 |
28 | if (!user) {
29 | return
30 | }
31 |
32 | function handleClick(event: MouseEvent) {
33 | setAnchorEl(event.currentTarget)
34 | }
35 |
36 | function handleClose() {
37 | setAnchorEl(undefined)
38 | }
39 |
40 | return (
41 |
42 |
51 |
56 | {user.firstName}
57 |
58 |
59 |
101 |
102 | )
103 | }
104 |
105 | const useStyles = makeStyles((theme) => ({
106 | headerProfile: {
107 | display: 'inline-flex',
108 | },
109 | profileButton: {
110 | borderRadius: 30,
111 | fontSize: '1.2rem',
112 | padding: 8,
113 | },
114 | profileAvatar: {
115 | width: 35,
116 | height: 35,
117 | marginRight: 10,
118 | },
119 | profileName: {
120 | fontWeight: 500,
121 | marginRight: 5,
122 | [theme.breakpoints.down('sm')]: {
123 | display: 'none',
124 | },
125 | },
126 | profileMenu: {
127 | marginLeft: '-16px',
128 | },
129 | profileMenuItemIcon: {
130 | color: theme.palette.primary.main,
131 | },
132 | }))
133 |
134 | export default AppHeaderProfile
135 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/AppHeaderSearch.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Button from '@material-ui/core/Button'
4 | import TextField from '@material-ui/core/TextField'
5 | import Dialog from '@material-ui/core/Dialog'
6 | import DialogActions from '@material-ui/core/DialogActions'
7 | import DialogContent from '@material-ui/core/DialogContent'
8 | import DialogContentText from '@material-ui/core/DialogContentText'
9 | import DialogTitle from '@material-ui/core/DialogTitle'
10 |
11 | import IconSearch from '@material-ui/icons/Search'
12 | import IconButton from '@material-ui/core/IconButton'
13 |
14 | const AppHeaderSearch: React.FC = () => {
15 | const classes = useStyles()
16 | const [open, setOpen] = React.useState(false)
17 |
18 | function handleClickOpen() {
19 | setOpen(true)
20 | }
21 |
22 | function handleClose() {
23 | setOpen(false)
24 | }
25 |
26 | return (
27 |
28 |
35 |
36 |
37 |
46 | Search...
47 |
48 |
49 | You may provide some extra search hints here
50 |
51 |
59 |
60 |
61 |
62 | Cancel
63 |
64 |
65 | Search
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
73 | const useStyles = makeStyles((theme) => ({
74 | searchButton: {
75 | marginRight: 20,
76 | },
77 | scrollPaper: {
78 | alignItems: 'flex-start',
79 | },
80 | }))
81 |
82 | export default AppHeaderSearch
83 |
--------------------------------------------------------------------------------
/src/_common/AppHeader/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AppHeader'
2 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/AppSidebar.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 | import theme from '../../_theme'
3 |
4 | import AppSidebar from './AppSidebar.js'
5 |
6 |
7 |
8 | # App Sidebar
9 |
10 | Application sidebar component.
11 |
12 |
13 |
14 | {() => {
15 | return (
16 |
23 | )
24 | }}
25 |
26 |
27 |
28 |
29 | ## Collapsed sidebar
30 |
31 |
32 |
33 | {() => {
34 | return (
35 |
42 | )
43 | }}
44 |
45 |
46 |
47 | ## Props
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/AppSidebar.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '_tests'
3 | import AppSidebar from './AppSidebar'
4 |
5 | describe('AppSidebar', () => {
6 | it('renders without crashing', () => {
7 | render( )
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/AppSidebar.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 |
4 | import { Link } from 'react-router-dom'
5 | import Typography from '@material-ui/core/Typography'
6 |
7 | import BaseLogo from '_common/BaseLogo'
8 | // import SidebarNav from './SidebarNav'
9 | import SidebarNavRecursive from './SidebarNavRecursive'
10 |
11 | export type AppSidebarProps = {
12 | isCollapsed?: boolean
13 | }
14 |
15 | const AppSidebar: React.FC = (props) => {
16 | console.log('AppSidebar rendered')
17 |
18 | // const { isCollapsed } = props
19 |
20 | const classes = useStyles(props)
21 |
22 | return (
23 |
50 | )
51 | }
52 |
53 | AppSidebar.defaultProps = {
54 | isCollapsed: false,
55 | }
56 |
57 | // Sidebar.propTypes = {
58 | // isCollapsed: PropTypes.bool,
59 | // }
60 |
61 | const useStyles = makeStyles((theme) => ({
62 | sidebar: {
63 | position: 'absolute',
64 | top: 0,
65 | bottom: 0,
66 | width: '100%',
67 | height: '100%',
68 | color: theme.sidebar.color,
69 | background: theme.sidebar.background,
70 | },
71 | sidebarBackground: {
72 | position: 'absolute',
73 | width: '100%',
74 | height: '100%',
75 | top: 0,
76 | left: 0,
77 | zIndex: 0,
78 | // backgroundImage: `url(${AppSidebarBg})`,
79 | backgroundPosition: 'center',
80 | backgroundRepeat: 'repeat',
81 | backgroundSize: 'cover',
82 | },
83 | sidebarBody: {
84 | position: 'absolute',
85 | top: 0,
86 | bottom: 0,
87 | width: '100%',
88 | height: '100%',
89 | overflowX: 'hidden',
90 | overflowY: 'auto',
91 | },
92 | sidebarHeader: {
93 | display: 'flex',
94 | alignItems: 'center',
95 | justifyContent: 'center',
96 | whiteSpace: 'nowrap',
97 | padding: '0 8px',
98 | ...theme.mixins.toolbar,
99 | },
100 | sidebarTitleLink: {
101 | textDecoration: 'none',
102 | color: 'inherit',
103 | display: 'flex',
104 | },
105 | logo: {
106 | color: theme.palette.primary.main,
107 | zIndex: 10,
108 | },
109 | title: (props: AppSidebarProps) => ({
110 | position: 'relative',
111 | overflow: 'visible',
112 | marginLeft: '5px',
113 | display: props.isCollapsed ? 'none' : 'block',
114 | fontSize: '1.1rem',
115 | letterSpacing: '.015em',
116 | // fontWeight: 'bold',
117 | }),
118 | name: {},
119 | tagline: {
120 | fontSize: 8,
121 | fontWeight: 'bold',
122 | position: 'absolute',
123 | top: '100%',
124 | marginTop: -5,
125 | background: theme.palette.secondary.main,
126 | color: '#fff',
127 | borderRadius: 2,
128 | padding: '1px 3px',
129 | right: 0,
130 | },
131 | }))
132 |
133 | export default memo(AppSidebar)
134 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNav/SidebarNav.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from '_tests'
3 | import SidebarNav from './SidebarNav'
4 |
5 | describe('SidebarNav', () => {
6 | it('renders without crashing', () => {
7 | render( )
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNav/SidebarNav.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, createStyles } from '@material-ui/core/styles'
3 | import List from '@material-ui/core/List'
4 | import ListSubheader from '@material-ui/core/ListSubheader'
5 |
6 | import { itemsCore /*, itemsTheme */ } from './SidebarNavService'
7 | import SidebarNavListItem, { SidebarNavListItemProps } from './SidebarNavListItem'
8 |
9 | export type SidebarNavProps = {
10 | // isCollapsed?: boolean
11 | }
12 |
13 | const SidebarNav: React.FC = (props) => {
14 | // const { isCollapsed } = props
15 | const classes = useStyles()
16 |
17 | return (
18 |
19 |
20 |
21 | Core Modules
22 |
23 |
24 |
25 | {itemsCore.map((item: SidebarNavListItemProps) => {
26 | return
27 | })}
28 |
29 |
30 |
31 | Misc
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | const useStyles = makeStyles((theme) =>
39 | createStyles({
40 | navList: {
41 | width: theme.sidebar.width,
42 | fontSize: '1em',
43 | fontWeight: 400,
44 | lineHeight: 1.5,
45 | letterSpacing: '0.00938em',
46 | },
47 | navListHeader: {
48 | textAlign: 'center',
49 | color: 'rgba(255,255,255,0.5)',
50 | },
51 | iconFeatures: {
52 | color: '#95de3c',
53 | },
54 | iconDocs: {
55 | color: '#f8cda9',
56 | },
57 | iconSupporters: {
58 | color: '#e3b546',
59 | },
60 | iconDiscuss: {
61 | color: '#ccc',
62 | },
63 | }),
64 | )
65 |
66 | export default SidebarNav
67 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNav/SidebarNavListItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import List from '@material-ui/core/List'
4 | import ListItem from '@material-ui/core/ListItem'
5 | import Collapse from '@material-ui/core/Collapse'
6 |
7 | // import { makeStyles, createStyles } from '@material-ui/core/styles'
8 |
9 | export type SidebarNavListItemProps = {
10 | name: string
11 | link?: string
12 | Icon?: any
13 | IconClassName?: string
14 | items?: SidebarNavListItemProps[]
15 | }
16 |
17 | const SidebarNavListItem: React.FC = (props) => {
18 | const { name, items = [] } = props
19 |
20 | const [open, setOpen] = React.useState(false)
21 |
22 | const handleItemClick = (e: React.MouseEvent) => {
23 | e.preventDefault()
24 |
25 | setOpen(!open)
26 | }
27 |
28 | return (
29 |
30 |
31 | {name}
32 |
33 |
34 | {items.length > 0 && (
35 |
36 |
37 | {items.map((item: SidebarNavListItemProps) => (
38 |
39 | ))}
40 |
41 |
42 | )}
43 |
44 | )
45 | }
46 |
47 | export default SidebarNavListItem
48 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNav/SidebarNavService.ts:
--------------------------------------------------------------------------------
1 | import IconProfile from '@material-ui/icons/AccountBox'
2 | import IconAdmin from '@material-ui/icons/VpnKey'
3 | import IconDashboard from '@material-ui/icons/Dashboard'
4 | import IconLibraryBooks from '@material-ui/icons/LibraryBooks'
5 | import IconQuestionAnswer from '@material-ui/icons/QuestionAnswer'
6 | import IconNewReleases from '@material-ui/icons/NewReleases'
7 | import IconSettings from '@material-ui/icons/Settings'
8 | import IconGroup from '@material-ui/icons/Group'
9 | import IconPreson from '@material-ui/icons/Person' //
10 |
11 | export const itemsCore = [
12 | {
13 | name: 'Dashboard',
14 | link: '/',
15 | Icon: IconDashboard,
16 | },
17 | {
18 | name: 'Auth',
19 | Icon: IconPreson,
20 | items: [
21 | {
22 | name: 'Login',
23 | link: '/auth/login',
24 | },
25 | {
26 | name: 'Signup',
27 | link: '/auth/signup',
28 | },
29 | {
30 | name: 'Recover',
31 | link: '/auth/recover',
32 | },
33 | {
34 | name: 'Reset',
35 | link: '/auth/reset',
36 | },
37 | ],
38 | },
39 | {
40 | name: 'Account',
41 | Icon: IconProfile,
42 | items: [
43 | {
44 | name: 'Profile',
45 | link: '/profile/me',
46 | },
47 | {
48 | name: 'Organization',
49 | link: '/organization',
50 | },
51 | ],
52 | },
53 | {
54 | name: 'Administration',
55 | Icon: IconAdmin,
56 | items: [
57 | {
58 | name: 'Users',
59 | link: '/administration/users',
60 | Icon: IconGroup,
61 | },
62 | {
63 | name: 'Organizations',
64 | link: '/administration/users',
65 | Icon: IconGroup,
66 | },
67 | {
68 | name: 'Subscription Plans',
69 | link: '/administration/subscriptionPlans',
70 | Icon: IconGroup,
71 | },
72 | ],
73 | },
74 | {
75 | name: 'Settings',
76 | link: '/settings',
77 | Icon: IconSettings,
78 | },
79 | ]
80 |
81 | export const itemsTheme = [
82 | {
83 | name: 'Why Modular?',
84 | link: '/demo/features',
85 | Icon: IconNewReleases,
86 | },
87 | {
88 | name: 'Docs',
89 | link: '/demo/docs',
90 | Icon: IconLibraryBooks,
91 | },
92 | {
93 | name: 'Discuss',
94 | link: 'https://github.com/modularcode/modular-admin-react/discussions',
95 | Icon: IconQuestionAnswer,
96 | },
97 | ]
98 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNav/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SidebarNav'
2 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNavRecursive/NavItemComponent.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, MouseEvent } from 'react'
2 | import clsx from 'clsx'
3 |
4 | import ListItem from '@material-ui/core/ListItem'
5 | import { makeStyles, createStyles } from '@material-ui/core/styles'
6 |
7 | import { NavLink, NavLinkProps } from 'react-router-dom'
8 |
9 | type NavItemComponentProps = {
10 | link?: string
11 | className?: string
12 | isCollapsed?: boolean
13 | style?: any
14 | onClick(e: MouseEvent): void
15 | }
16 |
17 | interface NavItemLinkInternalProps extends NavLinkProps {}
18 |
19 | export const NavItemLinkInternal = forwardRef<
20 | HTMLAnchorElement,
21 | NavItemLinkInternalProps
22 | >((props, ref) => {
23 | return
24 | })
25 |
26 | export const NavItemLinkExternal = forwardRef(
27 | (props, ref) => {
28 | return (
29 |
30 | {props.children}
31 |
32 | )
33 | },
34 | )
35 |
36 | // Can be a link, or button
37 | export const NavItemComponent = forwardRef<
38 | HTMLDivElement,
39 | React.PropsWithChildren
40 | >((props, ref) => {
41 | const { isCollapsed, ...newProps } = props
42 | const classes = useStyles()
43 |
44 | const component = (() => {
45 | if (!props.link) {
46 | return
47 | } else if (typeof props.link === 'string' && !props.link.includes('http')) {
48 | return
49 | } else if (typeof props.link === 'string' && props.link.includes('http')) {
50 | return
51 | }
52 | })()
53 |
54 | return (
55 |
56 | {component}
57 |
58 | )
59 | })
60 |
61 | const useStyles = makeStyles((theme) =>
62 | createStyles({
63 | navItemCollapsedWrapper: {
64 | width: theme.sidebar.widthCollapsed,
65 | },
66 | }),
67 | )
68 |
69 | export default NavItemComponent
70 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNavRecursive/NavList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import NavItem from './NavItem'
4 |
5 | import { ISidebarNavItem } from './SidebarNav'
6 |
7 | export interface IAppSidebarNavList {
8 | isNested?: boolean
9 | isCollapsed?: boolean
10 | items: ISidebarNavItem[]
11 | }
12 |
13 | const AppSidebarNavList: React.FC = (props) => {
14 | const { items = [], isCollapsed = false, isNested = false } = props
15 | // const classes = useStyles()
16 |
17 | return (
18 | <>
19 | {items.map((item, index) => (
20 |
21 | ))}
22 | >
23 | )
24 | }
25 |
26 | export default AppSidebarNavList
27 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNavRecursive/SidebarNav.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, createStyles } from '@material-ui/core/styles'
3 | import List from '@material-ui/core/List'
4 | import ListSubheader from '@material-ui/core/ListSubheader'
5 |
6 | import IconProfile from '@material-ui/icons/AccountBox'
7 | import IconAdmin from '@material-ui/icons/VpnKey'
8 | import IconDashboard from '@material-ui/icons/Dashboard'
9 | import IconLibraryBooks from '@material-ui/icons/LibraryBooks'
10 | import IconQuestionAnswer from '@material-ui/icons/QuestionAnswer'
11 | // import IconNewReleases from '@material-ui/icons/NewReleases'
12 | import IconSettings from '@material-ui/icons/Settings'
13 | import IconGroup from '@material-ui/icons/Group'
14 | import IconPreson from '@material-ui/icons/Person' //
15 |
16 | import NavList from './NavList'
17 |
18 | export interface ISidebarNavItem {
19 | name: string
20 | link?: string
21 | Icon?: any
22 | IconClassName?: string
23 | items?: ISidebarNavItem[]
24 | }
25 |
26 | export interface ISidebarNavProps {
27 | isCollapsed?: boolean
28 | }
29 |
30 | const SidebarNav: React.FC = (props) => {
31 | const { isCollapsed } = props
32 | const classes = useStyles()
33 |
34 | const itemsCore = [
35 | {
36 | name: 'Dashboard',
37 | link: '/',
38 | Icon: IconDashboard,
39 | },
40 | {
41 | name: 'Auth',
42 | Icon: IconPreson,
43 | items: [
44 | {
45 | name: 'Login',
46 | link: '/auth/login',
47 | },
48 | {
49 | name: 'Signup',
50 | link: '/auth/signup',
51 | },
52 | {
53 | name: 'Recover',
54 | link: '/auth/recover',
55 | },
56 | {
57 | name: 'Reset',
58 | link: '/auth/reset',
59 | },
60 | ],
61 | },
62 | {
63 | name: 'Account',
64 | Icon: IconProfile,
65 | items: [
66 | {
67 | name: 'Profile',
68 | link: '/account/profile',
69 | },
70 | {
71 | name: 'Organization',
72 | link: '/account/organization',
73 | },
74 | ],
75 | },
76 | {
77 | name: 'Administration',
78 | Icon: IconAdmin,
79 | items: [
80 | {
81 | name: 'Users',
82 | link: '/administration/users',
83 | Icon: IconGroup,
84 | },
85 | ],
86 | },
87 | {
88 | name: 'Settings',
89 | link: '/settings',
90 | Icon: IconSettings,
91 | },
92 | ]
93 |
94 | const itemsTheme = [
95 | // {
96 | // name: 'Why Modular?',
97 | // link: '/demo/features',
98 | // Icon: IconNewReleases,
99 | // IconClassName: classes.iconFeatures,
100 | // },
101 | {
102 | name: 'Docs',
103 | link: 'https://github.com/modularcode/modular-admin-react/blob/master/README.md',
104 | Icon: IconLibraryBooks,
105 | IconClassName: classes.iconDocs,
106 | },
107 | {
108 | name: 'Discuss',
109 | link: 'https://github.com/modularcode/modular-admin-react/discussions',
110 | Icon: IconQuestionAnswer,
111 | IconClassName: classes.iconDiscuss,
112 | },
113 | ]
114 |
115 | return (
116 |
117 |
118 |
119 | Core Modules
120 |
121 |
122 |
123 |
124 |
125 |
126 | Misc
127 |
128 |
129 |
130 |
131 | )
132 | }
133 |
134 | const useStyles = makeStyles((theme) =>
135 | createStyles({
136 | navList: {
137 | width: theme.sidebar.width,
138 | fontSize: '1em',
139 | fontWeight: 400,
140 | lineHeight: 1.5,
141 | letterSpacing: '0.00938em',
142 | },
143 | navListHeader: {
144 | textAlign: 'center',
145 | color: 'rgba(255,255,255,0.5)',
146 | },
147 | iconFeatures: {
148 | color: '#95de3c',
149 | },
150 | iconDocs: {
151 | color: '#f8cda9',
152 | },
153 | iconSupporters: {
154 | color: '#e3b546',
155 | },
156 | iconDiscuss: {
157 | color: '#ccc',
158 | },
159 | }),
160 | )
161 |
162 | export default SidebarNav
163 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/SidebarNavRecursive/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SidebarNav'
2 |
--------------------------------------------------------------------------------
/src/_common/AppSidebar/_assets/AppSidebarBg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modularcode/modular-admin-react/4e278fa38bc5f5c851e6e9ee68a14a4241c52e40/src/_common/AppSidebar/_assets/AppSidebarBg.jpg
--------------------------------------------------------------------------------
/src/_common/AppSidebar/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './AppSidebar'
2 |
--------------------------------------------------------------------------------
/src/_common/BaseLogo/BaseLogo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import clsx from 'clsx'
3 | import { makeStyles } from '@material-ui/core/styles'
4 |
5 | export type BaseLogoProps = {
6 | className?: string
7 | size?: number
8 | isInversedOutline?: boolean
9 | isInversed?: boolean
10 | }
11 |
12 | const BaseLogo: React.FC = (props) => {
13 | const classes = useStyles(props)
14 |
15 | return (
16 |
23 | Modular Admin React
24 |
25 |
30 |
35 |
36 |
37 | )
38 | }
39 |
40 | const useStyles = makeStyles((theme) => ({
41 | Logo: (props: BaseLogoProps) => ({
42 | display: 'inline-block',
43 | verticalAlign: 'text-bottom',
44 | width: props.size,
45 | height: props.size,
46 | }),
47 | path: {
48 | transition: 'all .3s ease',
49 | },
50 | outline: (props: BaseLogoProps) => ({
51 | fill: props.isInversedOutline ? '#fff' : 'currentColor',
52 | }),
53 | letter: (props: BaseLogoProps) => ({
54 | fill: props.isInversed ? '#fff' : 'currentColor',
55 | }),
56 | }))
57 |
58 | export default BaseLogo
59 |
--------------------------------------------------------------------------------
/src/_common/BaseLogo/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './BaseLogo'
2 |
--------------------------------------------------------------------------------
/src/_common/BasePageContainer/BasePageContainer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 |
4 | export type BasePageContainerProps = {}
5 |
6 | const BasePageContainer: React.FC = ({ children }) => {
7 | const classes = useStyles()
8 |
9 | return (
10 |
11 | {children}
12 |
13 | )
14 | }
15 |
16 | const useStyles = makeStyles((theme) => ({
17 | container: {
18 | flex: 1,
19 | paddingTop: theme.spacing(4),
20 | paddingLeft: theme.spacing(4),
21 | paddingRight: theme.spacing(4),
22 | paddingBottom: theme.spacing(4),
23 | },
24 | }))
25 |
26 | export default BasePageContainer
27 |
--------------------------------------------------------------------------------
/src/_common/BasePageContainer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './BasePageContainer'
2 |
--------------------------------------------------------------------------------
/src/_common/BasePageToolbar/BasePageToolbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import clsx from 'clsx'
3 |
4 | import { makeStyles, createStyles } from '@material-ui/core/styles'
5 | import Grid from '@material-ui/core/Grid'
6 | import Typography from '@material-ui/core/Typography'
7 |
8 | export type BasePageToolbarProps = {
9 | classes?: {
10 | container?: string
11 | titleContainer?: string
12 | actionsContainer?: string
13 | }
14 | title?: string
15 | TitleComponent?: React.ComponentType
16 | ActionsComponent?: React.ComponentType
17 | }
18 |
19 | const BasePageToolbar: React.FC = (props) => {
20 | const classes = useStyles()
21 | const externalClasses = props.classes || {}
22 | const ActionsComponent = props.ActionsComponent || null
23 |
24 | const Title =
25 | typeof props.title === 'string' ? (
26 |
27 | {props.title}
28 |
29 | ) : (
30 | props.TitleComponent
31 | )
32 |
33 | const Actions = ActionsComponent &&
34 |
35 | return (
36 |
41 |
49 | {Title}
50 |
51 |
59 | {Actions}
60 |
61 |
62 | )
63 | }
64 |
65 | const useStyles = makeStyles((theme) =>
66 | createStyles({
67 | container: {
68 | marginBottom: '1rem',
69 | },
70 | titleContainer: {},
71 | actionsContainer: {
72 | color: theme.palette.grey[600],
73 | display: 'flex',
74 | justifyContent: 'flex-end',
75 | },
76 | }),
77 | )
78 |
79 | export default BasePageToolbar
80 |
--------------------------------------------------------------------------------
/src/_common/BasePageToolbar/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './BasePageToolbar'
2 |
--------------------------------------------------------------------------------
/src/_common/BaseTable/BaseTablePagination.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react'
2 | import { TablePagination, TablePaginationProps, IconButton } from '@material-ui/core'
3 | import { TablePaginationActionsProps } from '@material-ui/core/TablePagination/TablePaginationActions'
4 | import { useTheme, makeStyles } from '@material-ui/core/styles'
5 |
6 | import {
7 | FirstPage as FirstPageIcon,
8 | KeyboardArrowLeft,
9 | KeyboardArrowRight,
10 | LastPage as LastPageIcon,
11 | } from '@material-ui/icons/'
12 |
13 | const BaseTablePaginationActions: React.FC = (props) => {
14 | const classes = useStyles()
15 | const theme = useTheme()
16 | const { count, page, rowsPerPage, onChangePage } = props
17 |
18 | const handleFirstPageButtonClick = (event: MouseEvent) => {
19 | onChangePage(event, 0)
20 | }
21 |
22 | const handleBackButtonClick = (event: MouseEvent) => {
23 | onChangePage(event, page - 1)
24 | }
25 |
26 | const handleNextButtonClick = (event: MouseEvent) => {
27 | onChangePage(event, page + 1)
28 | }
29 |
30 | const handleLastPageButtonClick = (event: MouseEvent) => {
31 | onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
32 | }
33 |
34 | return (
35 |
36 |
41 | {theme.direction === 'rtl' ? : }
42 |
43 |
48 | {theme.direction === 'rtl' ? : }
49 |
50 | = Math.ceil(count / rowsPerPage) - 1}
53 | aria-label="next page"
54 | >
55 | {theme.direction === 'rtl' ? : }
56 |
57 | = Math.ceil(count / rowsPerPage) - 1}
60 | aria-label="last page"
61 | >
62 | {theme.direction === 'rtl' ? : }
63 |
64 |
65 | )
66 | }
67 |
68 | export type BaseTablePaginationProps = TablePaginationProps
69 |
70 | const BaseTablePagination: React.FC = (props) => {
71 | const { count, page, rowsPerPage, onChangePage, onChangeRowsPerPage = () => {} } = props
72 |
73 | return (
74 |
88 | )
89 | }
90 |
91 | const useStyles = makeStyles((theme) => ({
92 | root: {
93 | flexShrink: 0,
94 | marginLeft: theme.spacing(2.5),
95 | },
96 | }))
97 |
98 | export default BaseTablePagination
99 |
--------------------------------------------------------------------------------
/src/_common/BaseTable/index.ts:
--------------------------------------------------------------------------------
1 | import BaseTablePagination from './BaseTablePagination'
2 |
3 | export { BaseTablePagination }
4 |
--------------------------------------------------------------------------------
/src/_common/BaseTitle/BaseTitle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export type BaseTitleProps = {}
4 |
5 | const BaseTitle: React.FC = () => {
6 | return
7 | }
8 |
9 | export default BaseTitle
10 |
--------------------------------------------------------------------------------
/src/_common/BaseTitle/index.ts:
--------------------------------------------------------------------------------
1 | import BaseTitle from './BaseTitle'
2 |
3 | export { BaseTitle }
4 |
--------------------------------------------------------------------------------
/src/_config/index.ts:
--------------------------------------------------------------------------------
1 | interface Config {
2 | navigationType: 'hash' | 'history'
3 | useSampleData?: boolean
4 | api: {
5 | useMocks?: boolean
6 | url: string
7 | }
8 | }
9 |
10 | const config: Config = {
11 | navigationType: 'hash',
12 | useSampleData: true,
13 | api: {
14 | useMocks: true,
15 | url: process.env.API_URL || '/api',
16 | },
17 | }
18 |
19 | export default config
20 |
--------------------------------------------------------------------------------
/src/_layouts/DashboardLayout/DashboardLayout.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'
2 | import DashboardLayout from './DashboardLayout.js'
3 |
4 | import AppHeader from '../../_common/AppHeader'
5 | import AppSidebar from '../../_common/AppSidebar'
6 | import AppFooter from '../../_common/AppFooter'
7 |
8 |
9 |
10 |
11 | # Dashboard Layout
12 |
13 | Application dashboard layout.
14 |
15 | export const DemoPlaceholder = ({ children }) =>
27 |
38 | {children}
39 |
40 |
41 |
42 | export const DemoHeader = () =>
50 |
51 | Header
52 |
53 |
54 |
55 | export const DemoSidebar = () =>
66 |
67 | Sidebar
68 |
69 |
70 |
71 | export const DemoFooter = () =>
78 |
79 | Footer
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 | Content
92 |
93 |
94 |
95 |
96 |
97 | ## With real header/footer/sidebar
98 |
99 |
100 |
101 |
106 |
107 | Content
108 |
109 |
110 |
111 |
112 |
113 | ## Props
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/src/_layouts/DashboardLayout/DashboardLayout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, useCallback } from 'react'
2 | import PropTypes from 'prop-types'
3 | import clsx from 'clsx'
4 | import { makeStyles, useTheme } from '@material-ui/core/styles'
5 | import useMediaQuery from '@material-ui/core/useMediaQuery'
6 | import Drawer from '@material-ui/core/Drawer'
7 | import Hidden from '@material-ui/core/Hidden'
8 |
9 | import AppHeader from '../../_common/AppHeader'
10 | import AppFooter from '../../_common/AppFooter'
11 | import AppSidebar from '../../_common/AppSidebar'
12 |
13 | const DashboardLayout: React.FC = ({ children }) => {
14 | const refHeaderContainer = useRef(null)
15 |
16 | const classes = useStyles()
17 | const theme = useTheme()
18 | const isDesktop = useMediaQuery(theme.breakpoints.up('md'))
19 | const isMobile = !isDesktop
20 |
21 | const [headerHeight, setHeaderHeight] = useState(0)
22 | const [isSidebarOpenMobile, setIsSidebarOpenMobile] = useState(false)
23 | const [isSidebarOpenDesktop /* setIsSidebarOpenDesktop */] = useState(true)
24 | const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
25 |
26 | useEffect(() => {
27 | if (refHeaderContainer && refHeaderContainer.current) {
28 | setHeaderHeight(refHeaderContainer.current.offsetHeight)
29 | }
30 | }, [refHeaderContainer])
31 |
32 | const contentOffset = (() => {
33 | if ((isDesktop && !isSidebarOpenDesktop) || isMobile) {
34 | return 0
35 | } else if (isDesktop && isSidebarCollapsed) {
36 | return theme.sidebar.widthCollapsed
37 | } else {
38 | return theme.sidebar.width
39 | }
40 | })()
41 |
42 | function handleSidebarToggleOpenMobile() {
43 | setIsSidebarOpenMobile(!isSidebarOpenMobile)
44 | }
45 |
46 | const handleSidebarToggle = useCallback(() => {
47 | // Open/close on mobile
48 | if (isMobile) {
49 | setIsSidebarOpenMobile((isSidebarOpenMobile) => !isSidebarOpenMobile)
50 | }
51 | // Collapse/uncollapse on desktop
52 | else {
53 | setIsSidebarCollapsed((isSidebarCollapsed) => !isSidebarCollapsed)
54 | }
55 | }, [isMobile])
56 |
57 | // function handleSidebarToggleCollapse() {
58 | // setIsSidebarCollapsed(!isSidebarCollapsed)
59 | // }
60 |
61 | return (
62 |
63 |
72 |
80 | {/* Mobile sidebar */}
81 |
82 |
93 |
94 |
95 |
96 | {/* Desktop sidebar */}
97 |
98 |
104 |
105 |
106 |
107 |
108 |
114 | {children}
115 |
118 |
119 |
120 | )
121 | }
122 |
123 | DashboardLayout.defaultProps = {
124 | header: AppHeader,
125 | sidebar: AppSidebar,
126 | footer: AppFooter,
127 | }
128 |
129 | DashboardLayout.propTypes = {
130 | header: PropTypes.elementType,
131 | sidebar: PropTypes.elementType,
132 | footer: PropTypes.elementType,
133 | }
134 |
135 | const useStyles = makeStyles((theme) => ({
136 | dashboardContainer: {
137 | display: 'flex',
138 | background: '#f5f5f5',
139 | },
140 | headerContainer: {
141 | top: 0,
142 | left: 'auto',
143 | right: 0,
144 | display: 'flex',
145 | alignItems: 'stretch',
146 | position: 'absolute',
147 | zIndex: theme.zIndex.drawer + 1,
148 | transition: theme.transitions.create(['width', 'margin'], {
149 | easing: theme.transitions.easing.sharp,
150 | duration: theme.transitions.duration.leavingScreen,
151 | }),
152 | },
153 | sidebarContainer: {
154 | display: 'flex',
155 | alignItems: 'stretch',
156 | position: 'relative',
157 | top: 0,
158 | bottom: 0,
159 | flexDirection: 'row',
160 | width: theme.sidebar.width,
161 | flexShrink: 0,
162 | // [theme.breakpoints.up('md')]: {
163 | // width: theme.sidebar.width,
164 | // flexShrink: 0,
165 | // },
166 | transition: theme.transitions.create('width', {
167 | easing: theme.transitions.easing.sharp,
168 | duration: theme.transitions.duration.leavingScreen,
169 | }),
170 | },
171 | sidebarContainerMobile: {
172 | width: 0,
173 | },
174 | sidebarContainerCollapsed: {
175 | width: theme.sidebar.widthCollapsed,
176 | },
177 | drawer: {
178 | width: '100%',
179 | position: 'absolute',
180 | [theme.breakpoints.down('sm')]: {
181 | width: theme.sidebar.width,
182 | flexShrink: 0,
183 | },
184 | },
185 | mainContainer: {
186 | flexGrow: 1,
187 | height: '100vh',
188 | overflow: 'auto',
189 | flexDirection: 'column',
190 | display: 'flex',
191 | },
192 | contentContainer: {
193 | display: 'flex',
194 | position: 'relative',
195 | flex: 1,
196 | },
197 | footerContainer: {
198 | position: 'relative',
199 | },
200 | }))
201 |
202 | export default DashboardLayout
203 |
--------------------------------------------------------------------------------
/src/_layouts/DashboardLayout/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './DashboardLayout'
2 |
--------------------------------------------------------------------------------
/src/_layouts/index.ts:
--------------------------------------------------------------------------------
1 | import DashboardLayout from './DashboardLayout'
2 |
3 | export { DashboardLayout }
4 |
--------------------------------------------------------------------------------
/src/_ref/AppRouter.js.bak:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { HashRouter, BrowserRouter, Route, Redirect, Switch } from 'react-router-dom' //
3 | import LinearProgress from '@material-ui/core/LinearProgress'
4 |
5 | import config from './_config'
6 | import authService from './_services/authService'
7 |
8 | import DashboardLayout from '_layouts/DashboardLayout'
9 |
10 | // Import core modules
11 | import Auth from './Auth/Auth'
12 | import Profile from './Profile'
13 | import Organization from './Organization'
14 | import NotFound from './Misc/NotFound'
15 |
16 | // Theme demo module
17 | import Demo from './Demo'
18 |
19 | const LoggedInRouter = () => {
20 | const loading = false
21 | const error = null
22 |
23 | if (loading) return
24 | if (error) return Error :(
25 |
26 | return (
27 |
28 | } />
29 |
30 |
31 |
36 |
37 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | // Use different router type depending on configuration
49 | const AppRouterComponent =
50 | config.navigationType === 'history' ? BrowserRouter : HashRouter
51 |
52 | const AppRouter = () => (
53 |
54 |
55 |
56 |
57 |
58 |
59 | )
60 |
61 | const RouteWithLayout = ({ component: Component, layout: Layout, ...rest }) => (
62 | {
65 | if (Layout) {
66 | return (
67 |
68 |
69 |
70 | )
71 | } else {
72 | return
73 | }
74 | }}
75 | />
76 | )
77 |
78 | // See https://reacttraining.com/react-router/web/example/auth-workflow
79 | const RoutePrivate = ({ component: Component, ...rest }) => {
80 | if (!Component) {
81 | return
82 | }
83 |
84 | return (
85 |
88 | authService.isAuthenticated() ? (
89 |
90 | ) : (
91 |
96 | )
97 | }
98 | />
99 | )
100 | }
101 |
102 | export default AppRouter
103 |
--------------------------------------------------------------------------------
/src/_services/authService.js:
--------------------------------------------------------------------------------
1 | /**
2 | * For the demo purposes we'll be using this predefined JWT token as the token of the signed in user
3 | * https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Db8fjZU7MkBZoJDjmjuvv2EeDgG9RSaZ1xKm__qHelw
4 | */
5 |
6 | import store from 'store'
7 | import config from '../_config'
8 |
9 | const sampleToken =
10 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Db8fjZU7MkBZoJDjmjuvv2EeDgG9RSaZ1xKm__qHelw'
11 |
12 | const authService = {
13 | token: null,
14 | init() {
15 | if (config.api.useMocks) {
16 | this.token = sampleToken
17 | } else {
18 | // Read the token from local storage
19 | this.token = store.get('token') || null
20 | }
21 | },
22 | auth(token) {
23 | store.set('token', token)
24 | },
25 | unauth() {
26 | store.remove('token')
27 | },
28 | isAuthenticated() {
29 | return !!this.token
30 | },
31 | getToken() {
32 | return this.token
33 | },
34 | }
35 |
36 | export default authService
37 |
--------------------------------------------------------------------------------
/src/_services/utilsService.js:
--------------------------------------------------------------------------------
1 | import _random from 'lodash/random'
2 |
3 | /**
4 | * Generates a nice random array for trend charts
5 | * with given from and to points
6 | */
7 | export const generateRandomeChartDataArray = ({
8 | from = 0,
9 | to = 1000,
10 | length = 30,
11 | noize = 0.3,
12 | }) => {
13 | const getLineEquation = ({ x1, y1, x2, y2 }) => {
14 | return (x) => (x * (y2 - y1) + (x2 * y1 - x1 * y2)) / (x2 - x1)
15 | }
16 | const lineEquation = getLineEquation({ x1: 0, y1: from, x2: length - 1, y2: to })
17 | const getPointData = (index) => {
18 | const amplitude = Math.abs(Math.round(noize * (to - from)))
19 | const diff =
20 | index === 0 || index === length - 1
21 | ? 0
22 | : amplitude * Math.sin((index * 2 * Math.PI) / length - 1) +
23 | _random(-amplitude, amplitude)
24 | return Math.max(0, Math.round(lineEquation(index) - diff))
25 | }
26 |
27 | return Array(length)
28 | .fill(null)
29 | .map((item, index) => getPointData(index))
30 | }
31 |
32 | const utilsService = {
33 | generateRandomeChartDataArray,
34 | }
35 |
36 | export default utilsService
37 |
--------------------------------------------------------------------------------
/src/_tests/index.tsx:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import 'jest-canvas-mock'
6 | import '@testing-library/jest-dom'
7 |
8 | import React, { ReactElement } from 'react'
9 | import { render, RenderOptions } from '@testing-library/react'
10 | import { MemoryRouter } from 'react-router-dom'
11 | import { ThemeProvider } from '@material-ui/styles'
12 | import { IntlProvider } from 'react-intl'
13 |
14 | import theme from '../_theme'
15 |
16 | const AllTheProviders: React.FC = ({ children }) => {
17 | return (
18 |
19 |
20 | {children}
21 |
22 |
23 | )
24 | }
25 |
26 | const customRender = (ui: ReactElement, options?: Omit) =>
27 | render(ui, { wrapper: AllTheProviders, ...options })
28 |
29 | jest.mock('react-chartjs-2', () => ({
30 | Bar: () => null,
31 | Line: () => null,
32 | }))
33 |
34 | export * from '@testing-library/react'
35 |
36 | export { customRender as render }
37 |
--------------------------------------------------------------------------------
/src/_theme/index.ts:
--------------------------------------------------------------------------------
1 | import { Theme as MuiTheme } from '@material-ui/core/styles/createMuiTheme'
2 | import { createMuiTheme } from '@material-ui/core/styles'
3 |
4 | // Allow configuration using `createMuiTheme`
5 | // ref: https://material-ui.com/guides/typescript/
6 | declare module '@material-ui/core/styles/createMuiTheme' {
7 | interface Theme {
8 | header: {
9 | background: string
10 | }
11 | sidebar: {
12 | width: number
13 | widthCollapsed: number
14 | background: string
15 | color: string
16 | }
17 | }
18 | interface ThemeOptions {}
19 | }
20 |
21 | export interface Theme extends MuiTheme {}
22 |
23 | const baseTheme = createMuiTheme({
24 | props: {
25 | MuiPaper: {
26 | elevation: 0,
27 | },
28 | MuiAppBar: {
29 | elevation: 1,
30 | },
31 | MuiButton: {
32 | // elevation: 0,
33 | },
34 | MuiMenu: {
35 | elevation: 1,
36 | },
37 | MuiCard: {
38 | elevation: 0,
39 | },
40 | },
41 | overrides: {
42 | MuiButton: {
43 | root: {
44 | minWidth: 0,
45 | },
46 | contained: {
47 | boxShadow: 'none',
48 | '&:active': {
49 | boxShadow: 'none',
50 | },
51 | '&:focus': {
52 | boxShadow: 'none',
53 | },
54 | },
55 | containedSecondary: {
56 | color: '#fff',
57 | '&:hover': {
58 | backgroundColor: 'rgb(118, 195, 21)',
59 | },
60 | },
61 | },
62 | MuiButtonGroup: {
63 | root: {
64 | boxShadow: 'none',
65 | },
66 | contained: {
67 | boxShadow: 'none',
68 | '&:active': {
69 | boxShadow: 'none',
70 | },
71 | '&:focus': {
72 | boxShadow: 'none',
73 | },
74 | },
75 | },
76 | MuiListItemIcon: {
77 | root: {
78 | minWidth: 40,
79 | },
80 | },
81 | MuiCardContent: {
82 | root: {
83 | '&:last-child': {
84 | paddingBottom: 16,
85 | },
86 | },
87 | },
88 | MuiLinearProgress: {
89 | root: {
90 | background: '#f3f3f3 !important',
91 | },
92 | },
93 | },
94 | palette: {
95 | divider: 'rgba(30, 30, 30, 0.06)',
96 | primary: {
97 | main: '#8cd136', //indigo[600],
98 | },
99 | secondary: {
100 | main: '#ae59e3', //'#619f30',
101 | },
102 | text: {
103 | secondary: 'rgba(102, 102, 102, 0.83)',
104 | },
105 | },
106 | typography: {
107 | h1: {
108 | fontSize: '2rem',
109 | },
110 | h2: {
111 | fontSize: '1.8rem',
112 | },
113 | h3: {
114 | fontSize: '1.6rem',
115 | },
116 | h4: {
117 | fontSize: '1.4rem',
118 | },
119 | h5: {
120 | fontSize: '1.2rem',
121 | },
122 | h6: {
123 | fontSize: '1rem',
124 | },
125 | },
126 | })
127 |
128 | const adminTheme = {
129 | header: {
130 | background: '#fff',
131 | },
132 | sidebar: {
133 | width: 255,
134 | widthCollapsed: baseTheme.spacing(7),
135 | background: '#4a4d5a;',
136 | color: '#fff',
137 | },
138 | }
139 |
140 | const theme = {
141 | ...baseTheme,
142 | ...adminTheme,
143 | }
144 |
145 | export default theme
146 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import 'typeface-roboto'
2 |
3 | import React from 'react'
4 | import ReactDOM from 'react-dom'
5 | import App from './App'
6 |
7 | import authService from './_services/authService'
8 | import api from './_api/'
9 |
10 | // Mount the app only when auth services and api services are ready
11 | // this solves api mocks issue
12 | ;(async () => {
13 | // Init the auth service
14 | authService.init()
15 |
16 | // Init rest API client
17 | await api.init()
18 |
19 | ReactDOM.render( , document.getElementById('root'))
20 | })()
21 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.custom.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"],
5 | "$/*": [
6 | "./src/*"
7 | ],
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "es2017",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 | "typeRoots": [
23 | "./node_modules/@types/",
24 | "./src/_types",
25 | "./src/_api/_types"
26 | ],
27 | "noFallthroughCasesInSwitch": true
28 | },
29 | "include": [
30 | "src"
31 | ],
32 | "exclude": [
33 | "node_modules/@nivo/line/index.d.ts"
34 | ],
35 | "extends": "./tsconfig.custom.json"
36 | }
37 |
--------------------------------------------------------------------------------
/tsconfig.to-es.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "src-es",
4 | "baseUrl": "src",
5 | "target": "ES2020",
6 | "noEmit": false,
7 | "jsx": "preserve",
8 | "declaration": false
9 | },
10 | "include": [
11 | "src"
12 | ],
13 | "exclude": [
14 | "package.json"
15 | ],
16 | "extends": "./tsconfig.json"
17 | }
18 |
--------------------------------------------------------------------------------