├── .editorconfig ├── .env ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .vscode ├── launch.json └── settings.json ├── README.md ├── docs ├── drafts.md ├── references.md └── roadmap.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── Administration │ ├── Accounts │ │ ├── AccountsEditor │ │ │ └── .gitkeep │ │ └── AccountsList │ │ │ └── .gitkeep │ ├── Administration.tsx │ ├── README.md │ ├── Users │ │ ├── UsersEditor │ │ │ └── .gitkeep │ │ └── UsersList │ │ │ └── .gitkeep │ └── index.tsx ├── App.test.tsx ├── App.tsx ├── AppRouter.tsx ├── Auth │ ├── Auth.tsx │ ├── Login │ │ └── Login.tsx │ ├── README.md │ ├── Recover │ │ └── Recover.tsx │ ├── Reset │ │ └── Reset.tsx │ ├── Signup │ │ └── Signup.tsx │ └── index.tsx ├── Content │ ├── CategoriesEditor │ │ └── .gitkeep │ ├── CategoriesList │ │ └── .gitkeep │ ├── Content.tsx │ ├── ItemsEditor │ │ └── .gitkeep │ ├── ItemsList │ │ └── .gitkeep │ ├── README.md │ ├── _api │ │ ├── _data │ │ │ ├── itemsCategoriesData.js │ │ │ ├── itemsData.js │ │ │ └── itemsItemsCategoriesData.js │ │ ├── _mocks │ │ │ └── .gitkeep │ │ ├── itemsCategoriesService.tsx │ │ └── itemsService.tsx │ ├── _types │ │ ├── Item.tsx │ │ └── ItemCategory.tsx │ └── index.tsx ├── Demo │ ├── Components │ │ ├── Components.tsx │ │ └── index.tsx │ ├── Demo.tsx │ ├── Discuss │ │ ├── Discuss.tsx │ │ └── index.tsx │ ├── Docs │ │ ├── Docs.tsx │ │ └── index.tsx │ ├── Features │ │ ├── Features.tsx │ │ └── index.tsx │ ├── README.md │ ├── Supporters │ │ ├── Supporters.tsx │ │ └── index.tsx │ └── index.tsx ├── Misc │ ├── NotFound │ │ ├── NotFound.tsx │ │ └── index.tsx │ └── README.md ├── Organization │ ├── Organization.tsx │ ├── Preview │ │ └── .gitkeep │ ├── README.md │ ├── Settings │ │ └── .gitkeep │ ├── Users │ │ ├── UsersEditor │ │ │ └── .gitkeep │ │ └── UsersList │ │ │ └── .gitkeep │ └── index.tsx ├── Profile │ ├── Preview │ │ └── .gitkeep │ ├── Profile.tsx │ ├── README.md │ ├── Settings │ │ └── .gitkeep │ └── index.tsx ├── README.md ├── Sales │ ├── Customers │ │ ├── Customers.tsx │ │ ├── CustomersEditor │ │ │ └── .gitkeep │ │ ├── CustomersList │ │ │ └── .gitkeep │ │ └── index.tsx │ ├── Locations │ │ └── .gitkeep │ ├── Orders │ │ ├── Orders.tsx │ │ ├── OrdersEditor │ │ │ └── .gitkeep │ │ ├── OrdersList │ │ │ └── .gitkeep │ │ └── index.tsx │ ├── Overview │ │ ├── OrdersHistory │ │ │ ├── OrdersHistory.tsx │ │ │ ├── index.tsx │ │ │ └── ordersHistoryService.ts │ │ ├── OrdersLatest │ │ │ ├── OrdersLatest.tsx │ │ │ └── index.tsx │ │ ├── Overview.tsx │ │ ├── OverviewActions.tsx │ │ ├── index.tsx │ │ └── overviewContext.ts │ ├── Payments │ │ └── Payments.tsx │ ├── Products │ │ ├── ProductsCategoriesEditor │ │ │ └── .gitkeep │ │ ├── ProductsCategoriesList │ │ │ └── .gitkeep │ │ ├── ProductsEditor │ │ │ └── .gitkeep │ │ └── ProductsList │ │ │ └── .gitkeep │ ├── README.md │ ├── Sales.tsx │ ├── SalesService.tsx │ ├── Stock │ │ └── Stock.tsx │ ├── Stores │ │ └── Stores.tsx │ ├── _api │ │ ├── _data │ │ │ ├── categoriesData.ts │ │ │ ├── customersData.ts │ │ │ ├── locationsData.ts │ │ │ ├── ordersData.ts │ │ │ ├── paymentsData.ts │ │ │ ├── productsData.ts │ │ │ ├── productsRelCategoriesData.ts │ │ │ ├── stockActionsData.ts │ │ │ └── stocksData.ts │ │ ├── _mocks │ │ │ ├── index.ts │ │ │ └── ordersMocks.ts │ │ ├── customers.ts │ │ ├── index.ts │ │ ├── orders.ts │ │ ├── products.ts │ │ └── productsCategories.ts │ ├── _services │ │ └── .gitkeep │ ├── _types │ │ ├── Customer.ts │ │ ├── EntityOwned.ts │ │ ├── Location.ts │ │ ├── Order.ts │ │ ├── Payment.ts │ │ ├── Product.ts │ │ ├── ProductAttachment.ts │ │ ├── ProductCategory.ts │ │ ├── ProductImage.ts │ │ ├── ProductToProductCategory.ts │ │ ├── Stock.ts │ │ └── StockAction.ts │ └── index.tsx ├── _api │ ├── _data │ │ ├── notificationsData.ts │ │ ├── organizationsData.ts │ │ ├── organizationsToUsersData.ts │ │ └── usersData.ts │ ├── _mocks │ │ ├── index.ts │ │ ├── organizationsMocks.ts │ │ └── usersMocks.ts │ ├── client.ts │ ├── index.ts │ ├── notifications.ts │ ├── organizations.ts │ └── users.ts ├── _common │ ├── Button │ │ └── Button.tsx │ ├── Dropdown │ │ └── Dropdown.tsx │ ├── Logo │ │ └── Logo.tsx │ ├── PageBreadcrumbs │ │ ├── PageBreadcrumbs.tsx │ │ └── index.tsx │ ├── PageContainer │ │ ├── PageContainer.tsx │ │ └── index.tsx │ ├── PageTitle │ │ └── .gitkeep │ └── PageToolbar │ │ ├── PageToolbar.tsx │ │ └── index.tsx ├── _config │ └── index.ts ├── _layouts │ ├── DashboardLayout │ │ ├── DashboardLayout.tsx │ │ ├── Footer │ │ │ ├── Footer.tsx │ │ │ └── index.tsx │ │ ├── Header │ │ │ ├── Header.tsx │ │ │ ├── HeaderDemo.tsx │ │ │ ├── HeaderNotifications.tsx │ │ │ ├── HeaderProfile.tsx │ │ │ ├── HeaderSearch.tsx │ │ │ └── index.tsx │ │ ├── Sidebar │ │ │ ├── Sidebar.tsx │ │ │ ├── SidebarNav.tsx │ │ │ ├── SidebarNavItem.tsx │ │ │ ├── SidebarNavItems.tsx │ │ │ └── index.tsx │ │ └── index.tsx │ └── index.tsx ├── _services │ ├── authService.tsx │ └── helpersService.tsx ├── _state │ ├── appModel.ts │ ├── appState.ts │ └── index.ts ├── _theme │ └── index.ts ├── _types │ ├── Entity.ts │ ├── Organization.ts │ ├── OrganizationToUser.ts │ └── User.ts ├── index.tsx ├── react-app-env.d.ts └── serviceWorker.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'eslint:recommended', 5 | 'react-app', 6 | 'plugin:react/recommended', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'prettier/@typescript-eslint', 9 | 'plugin:prettier/recommended', 10 | ], 11 | plugins: [ 12 | 'react', 13 | '@typescript-eslint', 14 | 'prettier' 15 | ], 16 | env: { 17 | 'browser': true, 18 | 'jasmine': true, 19 | 'jest': true, 20 | 'node': true, 21 | }, 22 | rules: { 23 | 'prettier/prettier': ['error', { 'singleQuote': true }], 24 | '@typescript-eslint/explicit-function-return-type': [0], 25 | '@typescript-eslint/no-use-before-define': [0], 26 | '@typescript-eslint/no-empty-interface': [0], 27 | '@typescript-eslint/explicit-member-accessibility': [0], 28 | '@typescript-eslint/no-explicit-any': [0], 29 | '@typescript-eslint/no-angle-bracket-type-assertion': [0], 30 | '@typescript-eslint/no-object-literal-type-assertion': [0], 31 | '@typescript-eslint/no-triple-slash-reference': [0], 32 | '@typescript-eslint/prefer-interface': [0], 33 | 'react/prop-types': [0], 34 | 'react/display-name': [0], 35 | 'no-console': [0], 36 | }, 37 | settings: { 38 | react: { 39 | pragma: 'React', 40 | version: 'detect', 41 | } 42 | }, 43 | parserOptions: { 44 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 45 | sourceType: 'module', // Allows for the use of imports 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 90, 6 | tabWidth: 2, 7 | }; 8 | -------------------------------------------------------------------------------- /.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 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Modular Material Admin + React

2 | 3 |
4 | 5 | Free [MaterialUI](https://material-ui.com/) dashboard theme implemented by using **ReactJS** + **TypeScript** + **Redux** + **Rematch** + **React Hooks** 6 | 7 | **(in development)** 8 | 9 |
10 | 11 | [![demo](https://repository-images.githubusercontent.com/195131407/ffa79c80-b806-11e9-8aa8-75d9609bb9e5)](https://modular-material-admin-react.modularcode.io/) 12 | 13 |

14 | 15 | View Demo 16 | 17 |

18 | 19 | ## Getting Started 20 | 21 | In the project directory, you can run: 22 | 23 | ### `npm start` 24 | 25 | Runs the app in the development mode.
26 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 27 | 28 | The page will reload if you make edits.
29 | You will also see any lint errors in the console. 30 | 31 | ### `npm test` 32 | 33 | Launches the test runner in the interactive watch mode.
34 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 35 | 36 | ### `npm run build` 37 | 38 | Builds the app for production to the `build` folder.
39 | It correctly bundles React in production mode and optimizes the build for the best performance. 40 | 41 | The build is minified and the filenames include the hashes.
42 | Your app is ready to be deployed! 43 | 44 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 45 | 46 | ## Learn More 47 | 48 | 49 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 50 | 51 | To learn React, check out the [React documentation](https://reactjs.org/). 52 | -------------------------------------------------------------------------------- /docs/drafts.md: -------------------------------------------------------------------------------- 1 | ## This is just a draft document with a thoughts :) 2 | 3 | The points below are still in progress 4 | 5 | 6 | ### Features 7 | 8 | - Modular and scalable architecture 9 | - Data driven and function ready with pre-set REST API entities, sample data and state management system 10 | - Static typechecking with TypeScript for large scale projects 11 | - Built in authentication with JWT 12 | - Typed data entities 13 | - Uses react hooks 14 | - Simpler state management with Redux + Rematch + Immer for reducing Redux code boilerplate 15 | - Material UI kit 16 | - Smart base applications with real-world usage (sales management, content management) 17 | - Users/Organizations roles with many-to-many relations 18 | - Designed in SAAS setup in mind, with subscription planes and global system roles 19 | - and more... 20 | 21 | 22 | ### State management 23 | 24 | 25 | 26 | 27 | ## FAQ 28 | 29 | #### Who is Modular Material Admin for? 30 | 31 | Modular Material Admin is a good fit for advanced developers who want to build a large-scale dashboard application with a good coding practicies. 32 | 33 | #### How Modular Material Admin is different from hundreds of other admin templates out there? 34 | 35 | The most of the dashboard themes and templates are more focused on the UI components. This brings to the situation, that for the most dashboard you need to fully rewrite or reimplement things like authorization, state management and api entities services setup. Modular Material Admin aims to provide that basic architecture setup out of the box 36 | 37 | 38 | #### How Modular Material Admin is different from other Admin UI ready CMS systems like Keystonejs, Strapi, WordPress, etc? 39 | 40 | Even though there are similarities and can be similar use cases there are major differences between ready-made CMS systems and Modular Material Admin 41 | 42 | CMS systems usually adopt the configuration over coding principle which means that in order to extend the existing functionality you need to make lot's of configurations and extend the functionality by the CMS plugins API. Another thing is that the Admin UI layout, style and design is usually predefined without the ability to customize it. 43 | 44 | On the controversary the Modular Material Admin + React is a **starter project template** which is meant to be a solid foundation to **build your own** admin system with the best React + TypeScript + MaterialUI coding practices. 45 | 46 | So the AdminUI ready CMS systems are really a good fit if 47 | 48 | - Your primary objective is content management 49 | - You want to prototype something quickly 50 | - You're fine with extending the AdminUI via CMS plugins API 51 | - You don't need to customize the AdminUI layout 52 | - You don't need to add a lot's of custom business logic which isn't supported via the CMS 53 | 54 | Modular Material Admin + React is a good fit if 55 | 56 | - You want to write your AdminUI code and business logic 57 | - You want to use React, React Hooks and TypeScript for your project 58 | - You want to extend the existing functionality via your own code 59 | - You don't want to depend on the CMS ecosystem 60 | 61 | 62 | #### Summing up: what is the Modular Material Admin + React usage niche? 63 | 64 | 65 | Modular Material Admin + React stand beetween a basic React Admin UI templates and a Admin UI ready CMS systems. 66 | 67 | | Basic Admin Template | | | Modular Material Admin | | | AdminUI Ready CMS | 68 | |----------------------|---|---|:------------------------:|---|---|--------------------:| 69 | | Less features | | | Moderate features | | | More Features | 70 | | High customizability | | | Moderate customizability | | | Low customizability | 71 | 72 | #### Why do you call it modular? 73 | 74 | I've been working on different large scale apps for years already, and have developed my own way of structuring and naming the application parts. This turned out a really successfull approach, which helped me to ofocus on a single functionality module. The modules are like lego building blocks and mostly represent the application logical structure. If you think about our app in a small pieces, it's all about a tree! There are also directories starting with underscore, e.g. `_services`, which combine files or modules of the same type or purpose. 75 | 76 | 77 | #### Why do use TypeScript? 78 | 79 | I beleive that the fucture of the JavaScript in large scale applications is TypeScript. 80 | It improves the code quality, helps to avoid of lot's of runtime errors, during refactoring and maintenance. In addition it provides a great intelisense, which boosts the productivity. TypeScript comes with it's overhead cost though, so if you want to build something small, it might be a bit of complicated to use. 81 | 82 | #### Why do you use MaterialUI? 83 | 84 | Although I'm not a big fan of material design, but the MaterialUI gives a huge solid fundation for building user interfaces. I never enjoyed using any UI framework that much as I did with MaterialUI. Big appreciation and my credits to the MaterialUI team. 85 | 86 | #### Why Redux + Rematch for the state management? 87 | 88 | Redux has became de-facto standart solution for the React state management. Unfortunately you need to write lot's of boilerplate code by default. Rematch is a framework build on top of Redux which simplifies working with redux and eliminates lot's of boilerplate code. 89 | 90 | 91 | #### Can I use Apollo and GraphQL instead of Redux? 92 | 93 | The state shape withing the `_state` directories is designed in a way to be simillar to Appolo Client state provided by It's useState, useMutation hooks. 94 | 95 | To abstract away from the app state management solution implementation the Modular Material Admin components use custom hooks (e.g. useDashboardState, useDashboardEffects). 96 | 97 | So if you want to use Apollo Client with it's recommended way of state management instead of Redux, you should be able to use `useQuery` and `useMutation` hooks instead or inside `useDashboardState` and `useDashboardEffects` without any dramatical changes in UI 98 | 99 | The GraphQL + Apollo learning curve was too steep, that's why I decided to exclude it from the initial version of Modular Material Admin. 100 | 101 | ----- 102 | 103 | Not Valid 104 | 105 | #### Why do you use two different approaches for state management? 106 | 107 | Initially I was thinking about using Redux only as a state management with GraphQL Apollo Client as well, but it turns out that Appolo Client is also prefered way to manage the UI state. 108 | 109 | More info available here 110 | https://github.com/rematch/rematch/issues/366 111 | https://www.apollographql.com/docs/react/essentials/local-state/ 112 | 113 | 114 | #### So REST or GraphQL? 115 | 116 | I'm trying to build the Modular Material Admin in a way that you would be able to use your desired approach. Each component that needs an ASYNC data, has it's own data usage custom hook, which can be Redux state or Appolo state. 117 | The custom hook of the component abstracts away the state management implementation under the hood, so we can use the preferred method for the state management: Apollo GraphQL or Redux + Rematch 118 | -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | ## Useful links 2 | 3 | #### Icons 4 | https://material.io/resources/icons/ 5 | 6 | 7 | #### Adding polls 8 | https://github.com/isaacs/github/issues/1533 9 | 10 | 11 | #### React 12 | https://www.robinwieruch.de/react-state-usereducer-usestate-usecontext/ 13 | 14 | React with Redux and hooks 15 | https://medium.com/@koss_lebedev/rematch-with-hooks-50a8380c46e4 16 | 17 | Layouts 18 | https://stackoverflow.com/questions/33996484/using-multiple-layouts-for-react-router-components 19 | https://gist.github.com/avinmathew/e82fe7e757b20cb337d5219e0ab8dc2c 20 | 21 | Context API vs Redux 22 | https://daveceddia.com/context-api-vs-redux/ 23 | 24 | 25 | #### TypeScript 26 | 27 | Typescript Book: 28 | https://basarat.gitbooks.io/typescript/docs/promise.html 29 | 30 | Infering React properties TypeScript definition from React runtime PropTypes 31 | https://dev.to/busypeoples/notes-on-typescript-inferring-react-proptypes-1g88 32 | 33 | https://dev.to/ferdaber/typescript-and-jsx-part-iii---typing-the-props-for-a-component-1pg2 34 | https://dev.to/benweiser/how-to-set-up-eslint-typescript-prettier-with-create-react-app-3675 35 | https://medium.com/@dors718/linting-your-react-typescript-project-with-eslint-and-prettier-2423170c3d42 36 | https://medium.com/quick-code/how-to-integrate-eslint-prettier-in-react-6efbd206d5c4 37 | https://dev.to/robertcoopercode/using-eslint-and-prettier-in-a-typescript-project-53jb 38 | 39 | 40 | #### REST api 41 | 42 | Query interface 43 | https://www.odata.org/ 44 | https://swagger.io/ 45 | https://swagger.io/resources/open-api/ 46 | https://swagger.io/tools/swaggerhub/ 47 | https://www.npmjs.com/package/typeorm-express-query-builder 48 | https://www.npmjs.com/package/odata-v4-typeorm 49 | https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ 50 | 51 | 52 | #### Misc 53 | 54 | https://archfirst.org/domain-driven-design/ 55 | -------------------------------------------------------------------------------- /docs/roadmap.md: -------------------------------------------------------------------------------- 1 | # Services module 2 | 3 | The services module allows to manage different institutions provided services 4 | 5 | - See the services overview dashboard 6 | - See and manage the orders 7 | - See and manage the payments 8 | - See and manage the provided services and categories 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modularcode/material-admin-react", 3 | "version": "0.1.0", 4 | "description": "Modular Material Admin + React: Free MaterialUI, ReactJS, TypeScript theme", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/modularcode/modular-material-admin-react.git" 8 | }, 9 | "author": "Gevorg Harutyunnyan ", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/modularcode/modular-material-admin-react/issues" 13 | }, 14 | "homepage": "http://modular-material-admin-react.modularcode.io", 15 | "dependencies": { 16 | "@material-ui/core": "^4.5.2", 17 | "@material-ui/icons": "^4.5.1", 18 | "@rematch/core": "^1.2.0", 19 | "@rematch/immer": "^1.2.0", 20 | "@types/axios": "^0.14.0", 21 | "@types/chart.js": "^2.8.10", 22 | "@types/jest": "^24.0.21", 23 | "@types/lodash": "^4.14.144", 24 | "@types/moment": "^2.13.0", 25 | "@types/node": "^12.12.3", 26 | "@types/react": "^16.9.11", 27 | "@types/react-dom": "^16.9.3", 28 | "@types/react-redux": "^7.1.5", 29 | "@types/react-router-dom": "^5.1.1", 30 | "@types/store": "^2.0.2", 31 | "@types/uuid": "^3.4.6", 32 | "axios": "^0.19.0", 33 | "axios-mock-adapter": "^1.17.0", 34 | "chart.js": "^2.9.1", 35 | "cross-env": "^6.0.3", 36 | "disqus-react": "^1.0.7", 37 | "eslint-config-react": "^1.1.7", 38 | "gh-pages": "^2.1.1", 39 | "graphql": "^14.5.8", 40 | "lodash": "^4.17.15", 41 | "moment": "^2.24.0", 42 | "node-sass": "^4.13.0", 43 | "opencollective": "^1.0.3", 44 | "react": "^16.11.0", 45 | "react-chartist": "^0.13.3", 46 | "react-dom": "^16.11.0", 47 | "react-redux": "^7.1.1", 48 | "react-router-dom": "^5.1.2", 49 | "react-scripts": "^3.2.0", 50 | "recharts": "^1.8.5", 51 | "redux": "^4.0.4", 52 | "rematch": "^0.1.3", 53 | "reselect": "^4.0.0", 54 | "store": "^2.0.12", 55 | "typeface-roboto": "0.0.75", 56 | "typescript": "^3.6.4", 57 | "uuid": "^3.3.3" 58 | }, 59 | "scripts": { 60 | "start": "cross-env TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling react-scripts start", 61 | "build": "react-scripts build", 62 | "test": "react-scripts test", 63 | "eject": "react-scripts eject", 64 | "postinstall": "opencollective postinstall", 65 | "deploy": "npm run build && echo modular-material-admin-react.modularcode.io > ./build/CNAME && gh-pages -d build --repo git@github.com:modularcode/modular-material-admin-react.git --branch gh-pages" 66 | }, 67 | "eslintConfig": { 68 | "extends": "react-app" 69 | }, 70 | "browserslist": { 71 | "production": [ 72 | ">0.2%", 73 | "not dead", 74 | "not op_mini all" 75 | ], 76 | "development": [ 77 | "last 1 chrome version", 78 | "last 1 firefox version", 79 | "last 1 safari version" 80 | ] 81 | }, 82 | "devDependencies": { 83 | "eslint-config-prettier": "^6.5.0", 84 | "eslint-plugin-prettier": "^3.1.1", 85 | "eslint-plugin-react": "^7.16.0", 86 | "prettier": "^1.18.2" 87 | }, 88 | "collective": { 89 | "type": "opencollective", 90 | "url": "https://opencollective.com/modular-admin", 91 | "logo": "https://opencollective.com/opencollective/logo.txt" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Modular Material Admin + React 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Modular Material Admin + React", 3 | "name": "Modular Material Admin + React", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/Administration/Accounts/AccountsEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Administration/Accounts/AccountsEditor/.gitkeep -------------------------------------------------------------------------------- /src/Administration/Accounts/AccountsList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Administration/Accounts/AccountsList/.gitkeep -------------------------------------------------------------------------------- /src/Administration/Administration.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Admin = () => { 4 | return
Admin
5 | } 6 | 7 | export default Admin 8 | -------------------------------------------------------------------------------- /src/Administration/README.md: -------------------------------------------------------------------------------- 1 | # Admininstration module 2 | 3 | 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 4 | 5 | - View all accounts 6 | - Edit all accounts 7 | - Veiw all users 8 | - Manage all users 9 | -------------------------------------------------------------------------------- /src/Administration/Users/UsersEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Administration/Users/UsersEditor/.gitkeep -------------------------------------------------------------------------------- /src/Administration/Users/UsersList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Administration/Users/UsersList/.gitkeep -------------------------------------------------------------------------------- /src/Administration/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Administration' 2 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | ReactDOM.unmountComponentAtNode(div) 9 | }) 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { ThemeProvider } from '@material-ui/styles' 4 | 5 | import CssBaseline from '@material-ui/core/CssBaseline' 6 | 7 | import store from './_state' 8 | import theme from './_theme' 9 | 10 | import AppRouter from './AppRouter' 11 | 12 | const App: React.FC = () => { 13 | return ( 14 |
15 | 16 | 17 |
18 | ) 19 | } 20 | export default () => ( 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | -------------------------------------------------------------------------------- /src/AppRouter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { 3 | HashRouter, 4 | BrowserRouter, 5 | Route, 6 | RouteProps, 7 | Redirect, 8 | Switch, 9 | } from 'react-router-dom' // 10 | import LinearProgress from '@material-ui/core/LinearProgress' 11 | 12 | import config from './_config' 13 | import authService from './_services/authService' 14 | import { useAppState, useAppStateMethods } from './_state/appState' 15 | 16 | import DashboardLayout from '_layouts/DashboardLayout' 17 | 18 | // Import application modules 19 | import Sales from './Sales' 20 | import Content from './Content' 21 | import Administration from './Administration' 22 | 23 | // Import core modules 24 | import Auth from './Auth/Auth' 25 | import Profile from './Profile' 26 | import Organization from './Organization' 27 | import NotFound from './Misc/NotFound' 28 | 29 | // Theme demo module 30 | import Demo from './Demo' 31 | 32 | const LoggedInRouter = () => { 33 | const { loading, error } = useAppState() 34 | const appStateMethods = useAppStateMethods() 35 | 36 | useEffect(() => { 37 | appStateMethods.request() 38 | }, [appStateMethods]) 39 | 40 | if (loading) return 41 | if (error) return

Error :(

42 | 43 | return ( 44 | 45 | } /> 46 | 47 | 48 | 53 | 54 | 59 | 60 | 61 | 62 | ) 63 | } 64 | 65 | // Use different router type depending on configuration 66 | const AppRouterComponent: React.ComponentType = 67 | config.navigationType === 'history' ? BrowserRouter : HashRouter 68 | 69 | const AppRouter: React.FC = () => ( 70 | 71 | 72 | 73 | 74 | 75 | 76 | ) 77 | 78 | const RouteWithLayout = ({ component: Component, layout: Layout, ...rest }: any) => ( 79 | { 82 | if (Layout) { 83 | return ( 84 | 85 | 86 | 87 | ) 88 | } else { 89 | return 90 | } 91 | }} 92 | /> 93 | ) 94 | 95 | // See https://reacttraining.com/react-router/web/example/auth-workflow 96 | const RoutePrivate: React.FC = ({ 97 | component: Component, 98 | ...rest 99 | }: RouteProps) => { 100 | if (!Component) { 101 | return 102 | } 103 | 104 | return ( 105 | 108 | authService.isAuthenticated() ? ( 109 | 110 | ) : ( 111 | 116 | ) 117 | } 118 | /> 119 | ) 120 | } 121 | 122 | export default AppRouter 123 | -------------------------------------------------------------------------------- /src/Auth/Auth.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Auth = () => { 4 | return
Auth
5 | } 6 | 7 | export default Auth 8 | -------------------------------------------------------------------------------- /src/Auth/Login/Login.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Login = () => { 4 | return
5 | } 6 | 7 | export default Login 8 | -------------------------------------------------------------------------------- /src/Auth/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Auth/README.md -------------------------------------------------------------------------------- /src/Auth/Recover/Recover.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Recover = () => { 4 | return
Recover
5 | } 6 | 7 | export default Recover 8 | -------------------------------------------------------------------------------- /src/Auth/Reset/Reset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Reset = () => { 4 | return
Reset
5 | } 6 | 7 | export default Reset 8 | -------------------------------------------------------------------------------- /src/Auth/Signup/Signup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Signup = () => { 4 | return
Signup
5 | } 6 | 7 | export default Signup 8 | -------------------------------------------------------------------------------- /src/Auth/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Auth' 2 | -------------------------------------------------------------------------------- /src/Content/CategoriesEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Content/CategoriesEditor/.gitkeep -------------------------------------------------------------------------------- /src/Content/CategoriesList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Content/CategoriesList/.gitkeep -------------------------------------------------------------------------------- /src/Content/Content.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Content = () => { 4 | return
Content
5 | } 6 | 7 | export default Content 8 | -------------------------------------------------------------------------------- /src/Content/ItemsEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Content/ItemsEditor/.gitkeep -------------------------------------------------------------------------------- /src/Content/ItemsList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Content/ItemsList/.gitkeep -------------------------------------------------------------------------------- /src/Content/README.md: -------------------------------------------------------------------------------- 1 | # Content module 2 | 3 | Allows you to manage your content items. Items migh be books, articles, pages, movies, events, and any other content entity. 4 | 5 | You can 6 | 7 | - Veiw content items 8 | - View content categories 9 | - Create/edit content items 10 | - Create/edit content categories 11 | -------------------------------------------------------------------------------- /src/Content/_api/_data/itemsCategoriesData.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Content/_api/_data/itemsData.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Content/_api/_data/itemsItemsCategoriesData.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Content/_api/_mocks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Content/_api/_mocks/.gitkeep -------------------------------------------------------------------------------- /src/Content/_api/itemsCategoriesService.tsx: -------------------------------------------------------------------------------- 1 | const itemsCategoriesService = {} 2 | 3 | export default itemsCategoriesService 4 | -------------------------------------------------------------------------------- /src/Content/_api/itemsService.tsx: -------------------------------------------------------------------------------- 1 | const itemsService = {} 2 | 3 | export default itemsService 4 | -------------------------------------------------------------------------------- /src/Content/_types/Item.tsx: -------------------------------------------------------------------------------- 1 | export default interface Item {} 2 | -------------------------------------------------------------------------------- /src/Content/_types/ItemCategory.tsx: -------------------------------------------------------------------------------- 1 | export default interface ItemCategory {} 2 | -------------------------------------------------------------------------------- /src/Content/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Content' 2 | -------------------------------------------------------------------------------- /src/Demo/Components/Components.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import PageContainer from '../../_common/PageContainer' 4 | 5 | const Components = () => { 6 | return Components 7 | } 8 | 9 | export default Components 10 | -------------------------------------------------------------------------------- /src/Demo/Components/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Components' 2 | -------------------------------------------------------------------------------- /src/Demo/Demo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch, Route, RouteComponentProps } from 'react-router-dom' 3 | 4 | import Features from './Features' 5 | import Docs from './Docs' 6 | import Supporters from './Supporters' 7 | import Discuss from './Discuss' 8 | 9 | export interface DemoProps extends RouteComponentProps {} 10 | 11 | const Demo: React.FC = ({ match }) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Demo 23 | -------------------------------------------------------------------------------- /src/Demo/Discuss/Discuss.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles, createStyles } from '@material-ui/core/styles' 3 | import Disqus from 'disqus-react' 4 | // import Typography from '@material-ui/core/Typography' 5 | import Paper from '@material-ui/core/Paper' 6 | 7 | import { Theme } from '_theme' 8 | import PageContainer from '../../_common/PageContainer' 9 | 10 | const Discuss = () => { 11 | const classes = useStyles() 12 | 13 | const disqusShortname = 'modularcode-material-admin-react' 14 | const disqusConfig = { 15 | url: 'https://modular-material-admin-react.modularcode.io', 16 | identifier: 'discussion', 17 | title: 'Modular Material Admin + React: Discussion', 18 | } 19 | 20 | return ( 21 | 22 |

Let's discuss this!

23 | 24 | {/* 25 | Discuss 26 | */} 27 | 28 | 29 |
30 | ) 31 | } 32 | 33 | const useStyles = makeStyles((theme: Theme) => 34 | createStyles({ 35 | content: { 36 | padding: theme.spacing(3, 2), 37 | }, 38 | }), 39 | ) 40 | 41 | export default Discuss 42 | -------------------------------------------------------------------------------- /src/Demo/Discuss/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Discuss' 2 | -------------------------------------------------------------------------------- /src/Demo/Docs/Docs.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import PageContainer from '../../_common/PageContainer' 4 | 5 | const Docs = () => { 6 | return ( 7 | 8 |

Docs

9 |
10 | ) 11 | } 12 | 13 | export default Docs 14 | -------------------------------------------------------------------------------- /src/Demo/Docs/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Docs' 2 | -------------------------------------------------------------------------------- /src/Demo/Features/Features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import PageContainer from '../../_common/PageContainer' 4 | 5 | const Features = () => { 6 | return ( 7 | 8 |

Features

9 |
10 | ) 11 | } 12 | 13 | export default Features 14 | -------------------------------------------------------------------------------- /src/Demo/Features/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Features' 2 | -------------------------------------------------------------------------------- /src/Demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo module 2 | 3 | This module is only for the demo purposes and demonstrating theme features, etc 4 | -------------------------------------------------------------------------------- /src/Demo/Supporters/Supporters.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import PageContainer from '../../_common/PageContainer' 4 | 5 | const Supporters = () => { 6 | return ( 7 | 8 |

Supporters

9 |
14 | Support me on Patreon to release this project faster! 15 |
16 | 17 | Support me on patreon 21 | 22 |
23 | ) 24 | } 25 | 26 | export default Supporters 27 | -------------------------------------------------------------------------------- /src/Demo/Supporters/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Supporters' 2 | -------------------------------------------------------------------------------- /src/Demo/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Demo' 2 | -------------------------------------------------------------------------------- /src/Misc/NotFound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import PageContainer from '../../_common/PageContainer' 4 | 5 | const NotFound = () => { 6 | return Page Not Found! 7 | } 8 | 9 | export default NotFound 10 | -------------------------------------------------------------------------------- /src/Misc/NotFound/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './NotFound' 2 | -------------------------------------------------------------------------------- /src/Misc/README.md: -------------------------------------------------------------------------------- 1 | # Misc module 2 | -------------------------------------------------------------------------------- /src/Organization/Organization.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Account = () => { 4 | return
Account
5 | } 6 | 7 | export default Account 8 | -------------------------------------------------------------------------------- /src/Organization/Preview/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Organization/Preview/.gitkeep -------------------------------------------------------------------------------- /src/Organization/README.md: -------------------------------------------------------------------------------- 1 | # Account module 2 | 3 | Allows to manage a single account 4 | 5 | - Update account settings (profile picture, info, etc, account owner) 6 | - Manage account users 7 | - Preview the account 8 | -------------------------------------------------------------------------------- /src/Organization/Settings/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Organization/Settings/.gitkeep -------------------------------------------------------------------------------- /src/Organization/Users/UsersEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Organization/Users/UsersEditor/.gitkeep -------------------------------------------------------------------------------- /src/Organization/Users/UsersList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Organization/Users/UsersList/.gitkeep -------------------------------------------------------------------------------- /src/Organization/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Organization' 2 | -------------------------------------------------------------------------------- /src/Profile/Preview/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Profile/Preview/.gitkeep -------------------------------------------------------------------------------- /src/Profile/Profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Profile = () => { 4 | return
Profile
5 | } 6 | 7 | export default Profile 8 | -------------------------------------------------------------------------------- /src/Profile/README.md: -------------------------------------------------------------------------------- 1 | # Profile module 2 | 3 | Allows to manage a single profile 4 | 5 | - Update profile settings (email, password, notifications, profile picture, info, etc) 6 | - Preview the profile 7 | 8 | 9 | One profile can belong to multiple accounts 10 | -------------------------------------------------------------------------------- /src/Profile/Settings/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Profile/Settings/.gitkeep -------------------------------------------------------------------------------- /src/Profile/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Profile' 2 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # App module 2 | 3 | This is the application entry component. It handles the main routing and authentication redirects. 4 | Sub modules 5 | 6 | ### Account 7 | single account management module 8 | 9 | ### Admin 10 | all accounts and users management module (convenient for superusers, can be extended) 11 | 12 | ### Content 13 | content management module (useful for creating various entities like books, articles, posts, pages, etc) 14 | 15 | ### Demo 16 | theme demo pages 17 | 18 | ### NotFound 19 | Fallback page, if route doesn't match any module 20 | 21 | ### Profile 22 | logged-in user profile management module 23 | 24 | ### Sales 25 | can be used for managing sales/orders 26 | -------------------------------------------------------------------------------- /src/Sales/Customers/Customers.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import clsx from 'clsx' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | 5 | import PageContainer from '../../_common/PageContainer' 6 | 7 | // import Grid from '@material-ui/core/Grid' 8 | import Paper from '@material-ui/core/Paper' 9 | 10 | import { Theme } from '_theme' 11 | 12 | export default function Customers() { 13 | const classes = useStyles() 14 | 15 | return ( 16 | 17 | Customers 18 | 19 | ) 20 | } 21 | 22 | const useStyles = makeStyles((theme: Theme) => ({})) 23 | -------------------------------------------------------------------------------- /src/Sales/Customers/CustomersEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Customers/CustomersEditor/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Customers/CustomersList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Customers/CustomersList/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Customers/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Customers' 2 | -------------------------------------------------------------------------------- /src/Sales/Locations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Locations/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Orders/Orders.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Grid from '@material-ui/core/Grid' 4 | import Paper from '@material-ui/core/Paper' 5 | import Button from '@material-ui/core/Button' 6 | 7 | // import { Theme } from '_theme' 8 | 9 | import PageContainer from '../../_common/PageContainer' 10 | import PageToolbar from '../../_common/PageToolbar' 11 | 12 | const Orders = () => { 13 | const PageTitle = 'Orders' 14 | 15 | const PageActions = ( 16 | 19 | ) 20 | 21 | return ( 22 | 23 | 24 | 25 | ) 26 | } 27 | 28 | export default Orders 29 | -------------------------------------------------------------------------------- /src/Sales/Orders/OrdersEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Orders/OrdersEditor/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Orders/OrdersList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Orders/OrdersList/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Orders/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Orders' 2 | -------------------------------------------------------------------------------- /src/Sales/Overview/OrdersHistory/OrdersHistory.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useContext } from 'react' //useState 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Card from '@material-ui/core/Card' 4 | import CardContent from '@material-ui/core/CardContent' 5 | import { Chart } from 'chart.js' 6 | 7 | import overviewContext from '../overviewContext' 8 | import mainHistoryService from './ordersHistoryService' 9 | 10 | // ref: https://www.robinwieruch.de/react-hooks-fetch-data/ 11 | const MainHistory = () => { 12 | const { filter } = useContext(overviewContext) 13 | const canvasRef = useRef(null) 14 | const classes = useStyles() 15 | 16 | useEffect(() => { 17 | if (canvasRef.current === null) { 18 | return 19 | } 20 | 21 | async function renderChart(canvasRefNode: HTMLCanvasElement) { 22 | if (!canvasRef || !filter) { 23 | return 24 | } 25 | 26 | // console.log('filter', filter) 27 | 28 | const chartConfigurationRes = await mainHistoryService.getChartConfiguration({ 29 | filter, 30 | }) 31 | 32 | new Chart(canvasRefNode, chartConfigurationRes) 33 | } 34 | 35 | renderChart(canvasRef.current) 36 | }, [canvasRef, filter]) 37 | 38 | return ( 39 | 40 | 41 | Order History 42 | 43 | 44 | 45 | ) 46 | } 47 | 48 | const useStyles = makeStyles(theme => ({ 49 | content: {}, 50 | chart: { 51 | width: '100%', 52 | height: '200px', 53 | }, 54 | })) 55 | 56 | export default MainHistory 57 | -------------------------------------------------------------------------------- /src/Sales/Overview/OrdersHistory/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './OrdersHistory' 2 | -------------------------------------------------------------------------------- /src/Sales/Overview/OrdersHistory/ordersHistoryService.ts: -------------------------------------------------------------------------------- 1 | import { Chart } from 'chart.js' 2 | import { random } from 'lodash' 3 | import moment from 'moment' 4 | 5 | // import ordersData from '../../_api/_data/ordersData' 6 | import api from '../../_api' 7 | import { SalesDahsboardContextFilter } from '../overviewContext' 8 | 9 | export interface GetChartDataOptions { 10 | filter: SalesDahsboardContextFilter 11 | } 12 | 13 | export default { 14 | getRandomNumber(min = 0, max = 1): number { 15 | return random(min, max) 16 | }, 17 | async getChartData({ filter }: GetChartDataOptions): Promise { 18 | interface ChartDataSet extends Chart.ChartDataSets { 19 | data: number[] 20 | } 21 | 22 | const orders = await api.orders.getSum({ 23 | $select: [['func_sum', '']], 24 | $filter: { 25 | createdAt: { 26 | ge: filter.dateFrom, 27 | le: filter.dateTo, 28 | }, 29 | }, 30 | $groupBy: [['func_date_day', 'createdAt']], 31 | }) 32 | 33 | console.log('orders', orders) 34 | 35 | // const timeFormat = 'MM/DD/YYYY' 36 | 37 | // const today = moment().startOf('day') 38 | // const oneMonthAgo = moment(today).subtract(15, 'days') 39 | 40 | const labels: string[] = [] 41 | // const signups: ChartDataSet = { 42 | // type: 'bar', 43 | // label: 'Signups', 44 | // backgroundColor: '#e8e8e8', 45 | // data: [], 46 | // } 47 | const ordersCount: ChartDataSet = { 48 | type: 'line', 49 | label: 'Number of orders', 50 | backgroundColor: '#1e88e5', 51 | borderColor: '#1e88e5', 52 | fill: false, 53 | data: [], 54 | } 55 | const ordersSum: ChartDataSet = { 56 | type: 'line', 57 | label: 'Total orders $', 58 | backgroundColor: '#95de3c', 59 | borderColor: '#95de3c', 60 | fill: false, 61 | data: [], 62 | yAxisID: 'y-axis-2', 63 | } 64 | 65 | // for (let now = moment(oneMonthAgo); now.isSameOrBefore(today); now.add(1, 'day')) { 66 | // labels.push(now.format(timeFormat)) 67 | 68 | // const signupsValue = random(10, 150) 69 | // const ordersCountValue = random(0, 25) 70 | // const ordersSumValue = random(10, 315) 71 | 72 | // signups.data.push(signupsValue) 73 | // ordersCount.data.push(ordersCountValue) 74 | // ordersSum.data.push(ordersSumValue) 75 | // } 76 | 77 | return { 78 | labels, 79 | datasets: [ordersSum, ordersCount], // signups 80 | } 81 | }, 82 | getChartOptions(options: GetChartDataOptions): Chart.ChartOptions { 83 | const timeFormat = 'MM/DD/YYYY' 84 | 85 | return { 86 | // responsive: true, 87 | title: { 88 | text: 'Chart.js Combo Time Scale', 89 | }, 90 | scales: { 91 | xAxes: [ 92 | { 93 | type: 'time', 94 | display: true, 95 | time: { 96 | parser: timeFormat, 97 | }, 98 | }, 99 | ], 100 | yAxes: [ 101 | { 102 | type: 'linear', 103 | display: true, 104 | position: 'left', 105 | id: 'y-axis-1', 106 | }, 107 | { 108 | type: 'linear', 109 | display: true, 110 | position: 'right', 111 | id: 'y-axis-2', 112 | // grid line settings 113 | gridLines: { 114 | drawOnChartArea: false, // only want the grid lines for one axis to show up 115 | }, 116 | ticks: { 117 | // Include a dollar sign in the ticks 118 | callback: function(value, index, values) { 119 | return '$' + value 120 | }, 121 | }, 122 | }, 123 | ], 124 | }, 125 | tooltips: { 126 | mode: 'index', 127 | intersect: false, 128 | }, 129 | hover: { 130 | mode: 'index', 131 | intersect: false, 132 | }, 133 | } 134 | }, 135 | // In real life this would be an http call 136 | async getChartConfiguration( 137 | options: GetChartDataOptions, 138 | ): Promise { 139 | const configuration = { 140 | type: 'bar', 141 | options: this.getChartOptions(options), 142 | data: await this.getChartData(options), 143 | } 144 | 145 | return new Promise(resolve => setTimeout(() => resolve(configuration), 300)) 146 | }, 147 | } 148 | -------------------------------------------------------------------------------- /src/Sales/Overview/OrdersLatest/OrdersLatest.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-script-url */ 2 | 3 | import React from 'react' 4 | import Link from '@material-ui/core/Link' 5 | import { makeStyles } from '@material-ui/core/styles' 6 | import Card from '@material-ui/core/Card' 7 | import CardContent from '@material-ui/core/CardContent' 8 | import Table from '@material-ui/core/Table' 9 | import TableBody from '@material-ui/core/TableBody' 10 | import TableCell from '@material-ui/core/TableCell' 11 | import TableHead from '@material-ui/core/TableHead' 12 | import TableRow from '@material-ui/core/TableRow' 13 | 14 | // Generate Order Data 15 | function createData( 16 | id: number, 17 | date: string, 18 | name: string, 19 | shipTo: string, 20 | paymentMethod: string, 21 | amount: number, 22 | ) { 23 | return { id, date, name, shipTo, paymentMethod, amount } 24 | } 25 | 26 | const rows = [ 27 | createData(0, '16 Mar, 2019', 'Elvis Presley', 'Tupelo, MS', 'VISA ⠀•••• 3719', 312.44), 28 | createData( 29 | 1, 30 | '16 Mar, 2019', 31 | 'Paul McCartney', 32 | 'London, UK', 33 | 'VISA ⠀•••• 2574', 34 | 866.99, 35 | ), 36 | createData(2, '16 Mar, 2019', 'Tom Scholz', 'Boston, MA', 'MC ⠀•••• 1253', 100.81), 37 | createData(3, '16 Mar, 2019', 'Michael Jackson', 'Gary, IN', 'AMEX ⠀•••• 2000', 654.39), 38 | createData( 39 | 4, 40 | '15 Mar, 2019', 41 | 'Bruce Springsteen', 42 | 'Long Branch, NJ', 43 | 'VISA ⠀•••• 5919', 44 | 212.79, 45 | ), 46 | ] 47 | 48 | const MainOrders: React.FC = () => { 49 | const classes = useStyles() 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | Date 57 | Name 58 | Ship To 59 | Payment Method 60 | Sale Amount 61 | 62 | 63 | 64 | {rows.map(row => ( 65 | 66 | {row.date} 67 | {row.name} 68 | {row.shipTo} 69 | {row.paymentMethod} 70 | {row.amount} 71 | 72 | ))} 73 | 74 |
75 |
76 | 77 | See more orders 78 | 79 |
80 |
81 |
82 | ) 83 | } 84 | 85 | const useStyles = makeStyles(theme => ({ 86 | seeMore: { 87 | marginTop: theme.spacing(3), 88 | }, 89 | })) 90 | 91 | export default MainOrders 92 | -------------------------------------------------------------------------------- /src/Sales/Overview/OrdersLatest/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './OrdersLatest' 2 | -------------------------------------------------------------------------------- /src/Sales/Overview/Overview.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import moment from 'moment' 3 | // import clsx from 'clsx' 4 | // import { makeStyles } from '@material-ui/core/styles' 5 | 6 | import Grid from '@material-ui/core/Grid' 7 | 8 | import PageContainer from '../../_common/PageContainer' 9 | import PageToolbar from '../../_common/PageToolbar' 10 | 11 | import { SalesDashboardProvider } from './overviewContext' 12 | 13 | import SalesDashboardActions from './OverviewActions' 14 | import OrdersHistory from './OrdersHistory' 15 | import OrdersLatest from './OrdersLatest' 16 | 17 | export default function SalesDashboard() { 18 | const [filter, setFilter] = useState({ 19 | dateFrom: moment() 20 | .subtract(14, 'day') 21 | .startOf('day'), 22 | dateTo: moment().startOf('day'), 23 | }) 24 | 25 | const PageTitle = 'Sales Dashboard' 26 | 27 | return ( 28 | 29 | 30 | 34 | 35 | 36 | {/* Chart */} 37 | 38 | 39 | 40 | {/* Recent Deposits */} 41 | {/* 42 | 43 | 44 | 45 | */} 46 | {/* Recent Orders */} 47 | 48 | 49 | 50 | 51 | 52 | 53 | ) 54 | } 55 | 56 | // const useStyles = makeStyles((theme: Theme) => ({ 57 | // container: { 58 | // paddingTop: theme.spacing(4), 59 | // paddingBottom: theme.spacing(4), 60 | // }, 61 | // })) 62 | -------------------------------------------------------------------------------- /src/Sales/Overview/OverviewActions.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 { Theme } from '../../_theme' 12 | import salesDashboardContext from './overviewContext' 13 | 14 | const MainActions: React.FC = () => { 15 | const classes = useStyles() 16 | const { filter, setFilter } = useContext(salesDashboardContext) 17 | 18 | const dateFilterLabel = filter 19 | ? `${filter.dateFrom.format('ll')} - ${filter.dateTo.format('ll')}` 20 | : 'Date Filter' 21 | 22 | return ( 23 |
24 | 25 | 28 | 29 | 30 | 34 | 35 | 36 | 39 | 40 | 41 | 44 | 45 |
46 | ) 47 | } 48 | 49 | const useStyles = makeStyles((theme: Theme) => ({ 50 | iconNew: { 51 | marginRight: 5, 52 | }, 53 | })) 54 | 55 | export default MainActions 56 | -------------------------------------------------------------------------------- /src/Sales/Overview/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Overview' 2 | -------------------------------------------------------------------------------- /src/Sales/Overview/overviewContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import moment, { Moment } from 'moment' 3 | 4 | export interface SalesDahsboardContextFilter { 5 | dateFrom: Moment 6 | dateTo: Moment 7 | } 8 | 9 | export interface SalesDahsboardContext { 10 | filter?: SalesDahsboardContextFilter 11 | setFilter?: any 12 | } 13 | 14 | // The default context, which is used when there is no provider 15 | // (might be used for components testing) 16 | export const salesDashboardContextDefault: SalesDahsboardContext = { 17 | filter: { 18 | dateFrom: moment() 19 | .subtract(14, 'day') 20 | .startOf('day'), 21 | dateTo: moment().startOf('day'), 22 | }, 23 | } 24 | 25 | export const salesDashboardContext = React.createContext( 26 | salesDashboardContextDefault, 27 | ) 28 | 29 | export const SalesDashboardProvider = salesDashboardContext.Provider 30 | export const SalesDashboardConsumer = salesDashboardContext.Consumer 31 | 32 | export default salesDashboardContext 33 | -------------------------------------------------------------------------------- /src/Sales/Payments/Payments.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Patments = () => { 4 | return
5 | } 6 | 7 | export default Patments 8 | -------------------------------------------------------------------------------- /src/Sales/Products/ProductsCategoriesEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Products/ProductsCategoriesEditor/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Products/ProductsCategoriesList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Products/ProductsCategoriesList/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Products/ProductsEditor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Products/ProductsEditor/.gitkeep -------------------------------------------------------------------------------- /src/Sales/Products/ProductsList/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/Products/ProductsList/.gitkeep -------------------------------------------------------------------------------- /src/Sales/README.md: -------------------------------------------------------------------------------- 1 | # Sales module 2 | 3 | The sales module is a good if you're planning to sell some goods in your system. 4 | It allows you to 5 | 6 | - See the sales overview dashboard 7 | - See and manage the orders 8 | - See and manage the payments 9 | - See and manage the products and categories 10 | - See and manage the stock of products (for physical products) 11 | - See and manage your products locations (stores, warehouses, shops, trading points, branches, etc) 12 | 13 | Use cases 14 | 15 | - Single store 16 | - Stores network 17 | - Delivery service 18 | -------------------------------------------------------------------------------- /src/Sales/Sales.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch, Route, RouteComponentProps } from 'react-router-dom' 3 | 4 | import Overview from './Overview' 5 | import Orders from './Orders' 6 | import Customers from './Customers' 7 | 8 | export interface SalesProps extends RouteComponentProps {} 9 | 10 | const Sales = ({ match }: SalesProps) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | export default Sales 21 | -------------------------------------------------------------------------------- /src/Sales/SalesService.tsx: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/Stock/Stock.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Stock = () => { 4 | return
Products Stock
5 | } 6 | 7 | export default Stock 8 | -------------------------------------------------------------------------------- /src/Sales/Stores/Stores.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Warehouses = () => { 4 | return
Warehouses
5 | } 6 | 7 | export default Warehouses 8 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/categoriesData.ts: -------------------------------------------------------------------------------- 1 | import _keyBy from 'lodash/keyBy' 2 | import Category from '../../_types/ProductCategory' 3 | 4 | /* 5 | Electronics, Computers & Office 6 | ELECTRONICS 7 | TV & Video 8 | Home Audio & Theater 9 | Cell Phones & Accessories 10 | Wearable Technology 11 | COMPUTERS 12 | Computers, Tablets, & PC Products 13 | Monitors 14 | Drives & Storage 15 | Books & Audible 16 | Sports & Outdoors 17 | */ 18 | 19 | const list: Category[] = [ 20 | { 21 | id: 1, 22 | name: 'Electronics, Computers & Office', 23 | }, 24 | { 25 | id: 2, 26 | name: 'Electronics', 27 | parentId: 1, 28 | }, 29 | { 30 | id: 3, 31 | name: 'TV & Video', 32 | parentId: 2, 33 | }, 34 | { 35 | id: 4, 36 | name: 'Home Audio & Theater', 37 | parentId: 2, 38 | }, 39 | { 40 | id: 5, 41 | name: 'Cell Phones & Accessories', 42 | parentId: 2, 43 | }, 44 | { 45 | id: 6, 46 | name: 'Wearable Technology', 47 | parentId: 2, 48 | }, 49 | { 50 | id: 7, 51 | name: 'Computers', 52 | parentId: 1, 53 | }, 54 | { 55 | id: 8, 56 | name: 'Computers, Tablets, & PC Products', 57 | parentId: 7, 58 | }, 59 | { 60 | id: 9, 61 | name: 'Monitors', 62 | parentId: 7, 63 | }, 64 | { 65 | id: 10, 66 | name: 'Drives & Storage', 67 | parentId: 7, 68 | }, 69 | { 70 | id: 11, 71 | name: 'Books & Audible', 72 | }, 73 | { 74 | id: 11, 75 | name: 'Sports & Outdoors', 76 | }, 77 | ] 78 | 79 | const byId: { [key: number]: Category } = _keyBy(list, 'id') 80 | 81 | export default { 82 | list, 83 | byId, 84 | } 85 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/customersData.ts: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/locationsData.ts: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/ordersData.ts: -------------------------------------------------------------------------------- 1 | import uuidv4 from 'uuid/v4' 2 | import moment, { Moment } from 'moment' 3 | import _keyBy from 'lodash/keyBy' 4 | import _sortBy from 'lodash/sortBy' 5 | import _random from 'lodash/random' 6 | import _shuffle from 'lodash/shuffle' 7 | import Order, { OrderStatus } from '../../_types/Order' 8 | import productsData from './productsData' 9 | 10 | const orderStatuses: OrderStatus[] = [ 11 | 'received', 12 | 'preparing', 13 | 'shipped', 14 | 'delivered', 15 | 'rejected', 16 | 'refunded', 17 | ] 18 | 19 | // Generate orders data for lat 90 days (do it one per browser session) 20 | const list = generateRandomOrdersHistory(90) 21 | const byId: { [key: number]: Order } = _keyBy(list, 'id') 22 | 23 | export default { 24 | orderStatuses, 25 | list, 26 | byId, 27 | } 28 | 29 | export function generateRandomOrdersHistory(numDays: number) { 30 | let list: Order[] = [] 31 | const now = moment() 32 | 33 | for ( 34 | let date = moment().subtract(numDays, 'days'), dayNumber = 1; 35 | date.isSameOrBefore(now); 36 | date.add(1, 'day'), dayNumber++ 37 | ) { 38 | const dailyOrders = generateDailyRandomOrders(date) 39 | 40 | list = list.concat(dailyOrders) 41 | } 42 | 43 | return list 44 | } 45 | 46 | export function generateDailyRandomOrders(date: Moment): Order[] { 47 | // Ensure the orders are date sorted 48 | return _sortBy( 49 | new Array(_random(0, 10)).fill(undefined).map(num => generateRandomOrder(date)), 50 | order => moment(order.createdAt).toDate(), 51 | ) 52 | } 53 | 54 | export function generateRandomOrder(date: Moment): Order { 55 | const shuffledProducts = _shuffle(productsData.list) 56 | const numProducts = _random(1, 3) 57 | const orderProductItems = shuffledProducts.slice(0, numProducts) 58 | const orderProducts = orderProductItems.map(product => ({ 59 | id: product.id, 60 | price: product.price, 61 | quantity: _random(1, 2), 62 | })) 63 | const orderSum = orderProducts.reduce((sum, { price = 0, quantity = 0 }) => { 64 | return sum + price * quantity 65 | }, 0) 66 | const isSuccessfulOrder = _random(1, 10) > 2 67 | const orderStatus = isSuccessfulOrder 68 | ? orderStatuses[_random(0, 3)] 69 | : orderStatuses[_random(4, 5)] 70 | const orderDate = moment(date).set({ 71 | hour: _random(0, 23), 72 | minute: _random(0, 59), 73 | second: _random(0, 59), 74 | }) 75 | 76 | const order = { 77 | id: uuidv4(), 78 | products: orderProducts, 79 | sum: orderSum, 80 | status: orderStatus, 81 | createdAt: orderDate.format(), 82 | updatedAt: orderDate.format(), 83 | } 84 | 85 | return order 86 | } 87 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/paymentsData.ts: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/productsData.ts: -------------------------------------------------------------------------------- 1 | import _keyBy from 'lodash/keyBy' 2 | import Product from '../../_types/Product' 3 | 4 | const list: Product[] = [ 5 | { 6 | id: 1, 7 | name: 'Simple Mobile Prepaid - Apple iPhone 6s (32GB) - Space Gray', 8 | price: 300, 9 | }, 10 | { 11 | id: 2, 12 | name: 'iPhoneXS', 13 | price: 399, 14 | }, 15 | { 16 | id: 3, 17 | name: 'Apple iPhone 6, GSM Unlocked, 64GB - Space Gray (Renewed)', 18 | price: 500, 19 | }, 20 | { 21 | id: 4, 22 | name: 'All-New Fire 7 Tablet (7" display, 16 GB) - Black', 23 | images: [ 24 | { 25 | url: 'https://images-na.ssl-images-amazon.com/images/I/61N1v5re7SL._SL1000_.jpg', 26 | }, 27 | ], 28 | price: 99.99, 29 | }, 30 | { 31 | id: 5, 32 | name: 33 | 'Echo (2nd Generation) - Smart speaker with Alexa and Dolby processing - Sandstone Fabric', 34 | images: [ 35 | { 36 | url: 'https://images-na.ssl-images-amazon.com/images/I/610d8E4usyL._SL1000_.jpg', 37 | }, 38 | ], 39 | price: 49.99, 40 | }, 41 | ] 42 | 43 | const byId: { [key: number]: Product } = _keyBy(list, 'id') 44 | 45 | const productsData = { 46 | list, 47 | byId, 48 | } 49 | 50 | export default productsData 51 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/productsRelCategoriesData.ts: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/stockActionsData.ts: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/_api/_data/stocksData.ts: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /src/Sales/_api/_mocks/index.ts: -------------------------------------------------------------------------------- 1 | import MockAdapter from 'axios-mock-adapter' 2 | 3 | import ordersMocks from './ordersMocks' 4 | 5 | const init = (mockAdapter: MockAdapter) => { 6 | ordersMocks.init(mockAdapter) 7 | } 8 | 9 | export default { 10 | init, 11 | } 12 | -------------------------------------------------------------------------------- /src/Sales/_api/_mocks/ordersMocks.ts: -------------------------------------------------------------------------------- 1 | import uuidv4 from 'uuid/v4' 2 | import moment from 'moment' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import ordersData from '../_data/ordersData' 5 | 6 | export default { 7 | init(mock: MockAdapter) { 8 | mock.onGet('/orders').reply((config: any) => { 9 | console.log('request config:', config) 10 | 11 | const matchingOrders = ordersData.list 12 | const ordersRes = matchingOrders.slice(0, 49) 13 | 14 | return [ 15 | 200, 16 | { 17 | orders: ordersRes, 18 | count: matchingOrders.length, 19 | }, 20 | ] 21 | }) 22 | 23 | // 24 | mock.onGet(/\/orders\/.*?/).reply((config: any) => { 25 | console.log('request config:', config) 26 | 27 | const urlPaths = config.url.split('/') 28 | const orderId = urlPaths[urlPaths.length - 1] 29 | const order = ordersData.byId[orderId] 30 | 31 | if (order) { 32 | return [200, { ...order }] 33 | } else { 34 | return [404, { message: 'User not found ' }] 35 | } 36 | }) 37 | 38 | mock.onPut(/\/orders\/.*?/).reply((config: any) => { 39 | const urlPaths = config.url.split('/') 40 | const orderId = urlPaths[urlPaths.length - 1] 41 | const orderData = JSON.parse(config.data) 42 | const order = ordersData.byId[orderId] 43 | 44 | if (order) { 45 | return [200, { ...order, ...orderData }] 46 | } else { 47 | return [403, { message: 'Update not permitted' }] 48 | } 49 | }) 50 | 51 | mock.onPost(/\/orders/).reply((config: any) => { 52 | const orderData = JSON.parse(config.data) 53 | const order = { 54 | ...orderData, 55 | id: uuidv4(), 56 | createdAt: moment().format(), 57 | updatedAt: moment().format(), 58 | } 59 | 60 | return [200, order] 61 | }) 62 | 63 | mock.onDelete(/\/orders\/\d+/).reply((config: any) => { 64 | return [200, { message: 'Order removed' }] 65 | }) 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /src/Sales/_api/customers.ts: -------------------------------------------------------------------------------- 1 | const customersService = {} 2 | 3 | export default customersService 4 | -------------------------------------------------------------------------------- /src/Sales/_api/index.ts: -------------------------------------------------------------------------------- 1 | import { AxiosInstance } from 'axios' 2 | import { ApiInitOptions } from '_api' 3 | 4 | import mocks from './_mocks' 5 | import orders from './orders' 6 | import MockAdapter from 'axios-mock-adapter/types' 7 | 8 | export interface SalesApiInitOptions extends ApiInitOptions { 9 | instance: AxiosInstance 10 | mockAdapter?: MockAdapter 11 | } 12 | 13 | const init = (options: SalesApiInitOptions) => { 14 | if (options.useSampleData && options.mockAdapter) { 15 | mocks.init(options.mockAdapter) 16 | } 17 | } 18 | 19 | export default { init, orders } 20 | export { init, orders } 21 | -------------------------------------------------------------------------------- /src/Sales/_api/orders.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios' 2 | import Order, { OrderSubmissionData, OrderId } from '../_types/Order' 3 | import apiClient from '_api/client' 4 | 5 | export interface OrdersService { 6 | get(params?: any): Promise 7 | getList(params?: any): Promise 8 | getSum(params?: any): Promise 9 | getCount(params?: any): Promise 10 | getOne(orderId: OrderId): Promise 11 | create(order: OrderSubmissionData): Promise 12 | update(orderId: OrderId, order: OrderSubmissionData): Promise 13 | remove(orderId: OrderId): Promise 14 | } 15 | 16 | export interface OrdersListResponse { 17 | orders: Order[] 18 | count: number 19 | } 20 | 21 | const ordersService: OrdersService = { 22 | get(params: any) { 23 | return apiClient 24 | .get(`/orders`, { 25 | params, 26 | }) 27 | .then((res: AxiosResponse) => res.data) 28 | }, 29 | getList(params: any) { 30 | return apiClient 31 | .get(`/orders`, { 32 | params, 33 | }) 34 | .then((res: AxiosResponse) => res.data) 35 | }, 36 | getSum(params: any) { 37 | return apiClient 38 | .get(`/orders/sum`, { 39 | params, 40 | }) 41 | .then((res: AxiosResponse) => res.data) 42 | }, 43 | getCount(params: any) { 44 | return apiClient 45 | .get(`/orders/count`, { 46 | params, 47 | }) 48 | .then((res: AxiosResponse) => res.data) 49 | }, 50 | getOne(orderId) { 51 | return apiClient 52 | .get(`/orders/${orderId}`) 53 | .then((res: AxiosResponse) => res.data) 54 | }, 55 | create(order: OrderSubmissionData) { 56 | return apiClient.post(`/orders`, order).then((res: AxiosResponse) => res.data) 57 | }, 58 | update(orderId, order) { 59 | return apiClient 60 | .put(`/orders/${orderId}`, order) 61 | .then((res: AxiosResponse) => res.data) 62 | }, 63 | remove(orderId) { 64 | return apiClient 65 | .delete(`/orders/${orderId}`) 66 | .then((res: AxiosResponse) => res.data) 67 | }, 68 | } 69 | 70 | export default ordersService 71 | -------------------------------------------------------------------------------- /src/Sales/_api/products.ts: -------------------------------------------------------------------------------- 1 | const productsService = {} 2 | 3 | export default productsService 4 | -------------------------------------------------------------------------------- /src/Sales/_api/productsCategories.ts: -------------------------------------------------------------------------------- 1 | const productsCategoriesService = {} 2 | 3 | export default productsCategoriesService 4 | -------------------------------------------------------------------------------- /src/Sales/_services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/Sales/_services/.gitkeep -------------------------------------------------------------------------------- /src/Sales/_types/Customer.ts: -------------------------------------------------------------------------------- 1 | import { EntityId } from '_types/Entity' 2 | 3 | export type CustomerId = EntityId 4 | 5 | export default interface Customer { 6 | id?: CustomerId 7 | name?: string 8 | email?: string 9 | details?: object 10 | } 11 | -------------------------------------------------------------------------------- /src/Sales/_types/EntityOwned.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from '_types/Entity' 2 | import { UserId } from '_types/User' 3 | import { OrganizationId } from '_types/Organization' 4 | 5 | export type EntitiyOwnedId = EntityId 6 | 7 | // The entity may be owned by an organization or an individual user 8 | export default interface EntityOwned extends Entity { 9 | id?: EntityId 10 | ownerUserId?: UserId 11 | owenrOrganizationId?: OrganizationId 12 | } 13 | -------------------------------------------------------------------------------- /src/Sales/_types/Location.ts: -------------------------------------------------------------------------------- 1 | import EntityOwned, { EntitiyOwnedId } from './EntityOwned' 2 | 3 | export type LocationId = EntitiyOwnedId 4 | 5 | export default interface Location extends EntityOwned { 6 | id?: LocationId 7 | location?: { 8 | lat: number 9 | lng: number 10 | } 11 | name: string 12 | } 13 | -------------------------------------------------------------------------------- /src/Sales/_types/Order.ts: -------------------------------------------------------------------------------- 1 | import EntityOwned, { EntitiyOwnedId } from './EntityOwned' 2 | 3 | import { PaymentId } from './Payment' 4 | import { ProductId } from './Product' 5 | 6 | export type OrderId = EntitiyOwnedId 7 | export type OrderStatus = 8 | | 'received' 9 | | 'preparing' 10 | | 'shipped' 11 | | 'delivered' 12 | | 'rejected' 13 | | 'refunded' 14 | 15 | export interface OrderProduct { 16 | id?: ProductId 17 | price?: number 18 | quantity?: number 19 | } 20 | 21 | export interface OrderSubmissionData { 22 | products?: any[] 23 | customerNotes?: string 24 | } 25 | 26 | // Order can be owned by a single account or user 27 | export default interface Order extends EntityOwned { 28 | id?: OrderId 29 | name?: string 30 | status: OrderStatus 31 | customerNotes?: string 32 | staffNotes?: string 33 | paymentId?: PaymentId 34 | products?: any[] 35 | sum: number 36 | } 37 | -------------------------------------------------------------------------------- /src/Sales/_types/Payment.ts: -------------------------------------------------------------------------------- 1 | import { EntityId } from '_types/Entity' 2 | 3 | export type PaymentId = EntityId 4 | 5 | export default interface Payment { 6 | id?: PaymentId 7 | status?: string 8 | transactionId?: string 9 | transactionStatus?: string 10 | } 11 | -------------------------------------------------------------------------------- /src/Sales/_types/Product.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from '_types/Entity' 2 | import { UserId } from '_types/User' 3 | import { OrganizationId } from '_types/Organization' 4 | import ProductImage from './ProductImage' 5 | 6 | export type ProductId = EntityId 7 | 8 | export interface ProductVariation { 9 | name?: string 10 | price?: number 11 | details?: object 12 | } 13 | 14 | export default interface Product extends Entity { 15 | id?: ProductId 16 | name: string 17 | details?: object 18 | description?: string 19 | variations?: ProductVariation[] 20 | price: number 21 | priceDiscounted?: number 22 | images?: ProductImage[] 23 | ownerUserId?: UserId 24 | ownerOrganizationId?: OrganizationId 25 | } 26 | -------------------------------------------------------------------------------- /src/Sales/_types/ProductAttachment.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from '_types/Entity' 2 | 3 | export default interface ProductAttachment extends Entity { 4 | id?: EntityId 5 | name?: string 6 | url?: string 7 | } 8 | -------------------------------------------------------------------------------- /src/Sales/_types/ProductCategory.ts: -------------------------------------------------------------------------------- 1 | import EntityOwned, { EntitiyOwnedId } from './EntityOwned' 2 | import ProductImage from './ProductImage' 3 | 4 | export type ProductCategoryId = EntitiyOwnedId 5 | 6 | export default interface Category extends EntityOwned { 7 | name: string 8 | description?: string 9 | parentId?: EntitiyOwnedId 10 | images?: ProductImage[] 11 | } 12 | -------------------------------------------------------------------------------- /src/Sales/_types/ProductImage.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from '_types/Entity' 2 | 3 | export default interface ProductImage extends Entity { 4 | id?: EntityId 5 | name?: string 6 | url?: string 7 | } 8 | -------------------------------------------------------------------------------- /src/Sales/_types/ProductToProductCategory.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from './Product' 2 | import { ProductCategoryId } from './ProductCategory' 3 | 4 | export default interface ProductToProductCategory { 5 | productId: ProductId 6 | productCategoryId: ProductCategoryId 7 | } 8 | -------------------------------------------------------------------------------- /src/Sales/_types/Stock.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from './Product' 2 | import { LocationId } from './Location' 3 | 4 | export default interface Stock { 5 | productId: ProductId 6 | locationId?: LocationId 7 | qunatity: number 8 | } 9 | -------------------------------------------------------------------------------- /src/Sales/_types/StockAction.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from './Product' 2 | import { LocationId } from './Location' 3 | 4 | export default interface StockAction { 5 | productId: ProductId 6 | locationId?: LocationId 7 | qunatity: number 8 | action: string 9 | } 10 | -------------------------------------------------------------------------------- /src/Sales/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Sales' 2 | -------------------------------------------------------------------------------- /src/_api/_data/notificationsData.ts: -------------------------------------------------------------------------------- 1 | const notificationsData = {} 2 | 3 | export default notificationsData 4 | -------------------------------------------------------------------------------- /src/_api/_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/_data/organizationsToUsersData.ts: -------------------------------------------------------------------------------- 1 | import _groupBy from 'lodash/groupBy' 2 | import OrganizationToUser from '_types/OrganizationToUser' 3 | 4 | // import organizationsData from './organizationsData' 5 | // import usersData from './usersData' 6 | 7 | const list: OrganizationToUser[] = [ 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 | export default { 62 | list, 63 | byUserId, 64 | byOrganizationId, 65 | } 66 | -------------------------------------------------------------------------------- /src/_api/_data/usersData.ts: -------------------------------------------------------------------------------- 1 | import _keyBy from 'lodash/keyBy' 2 | import User from '_types/User' 3 | import organizationsToUsersData from './organizationsToUsersData' 4 | 5 | const list: User[] = [ 6 | { 7 | id: 1, 8 | firstName: 'Gevorg', 9 | lastName: 'H', 10 | username: 'johndoe1', 11 | email: 'john@doe.com', 12 | avatarUrl: 'https://avatars3.githubusercontent.com/u/3959008?v=3&s=40', 13 | userToOrganizations: organizationsToUsersData.byUserId[1], 14 | globalRole: 'admin', 15 | }, 16 | { 17 | id: 2, 18 | firstName: 'Jay', 19 | lastName: 'Nickolson', 20 | username: null, 21 | email: 'example@gmail.com', 22 | avatarUrl: 23 | 'https://tinyfac.es/data/avatars/475605E3-69C5-4D2B-8727-61B7BB8C4699-500w.jpeg', 24 | userToOrganizations: organizationsToUsersData.byUserId[2], 25 | }, 26 | { 27 | id: 3, 28 | firstName: 'Ana', 29 | lastName: 'De Armas', 30 | username: null, 31 | email: 'Ana+De+Armas@example.com', 32 | avatarUrl: 33 | 'https://images-na.ssl-images-amazon.com/images/M/MV5BMjA3NjYzMzE1MV5BMl5BanBnXkFtZTgwNTA4NDY4OTE@._V1_UX172_CR0,0,172,256_AL_.jpg', 34 | userToOrganizations: organizationsToUsersData.byUserId[3], 35 | }, 36 | { 37 | id: 4, 38 | firstName: 'Armas', 39 | lastName: 'De Ana', 40 | username: null, 41 | email: 'Ana+De+Armas@example.com', 42 | avatarUrl: 43 | 'https://images-na.ssl-images-amazon.com/images/M/MV5BMjA3NjYzMzE1MV5BMl5BanBnXkFtZTgwNTA4NDY4OTE@._V1_UX172_CR0,0,172,256_AL_.jpg', 44 | userToOrganizations: organizationsToUsersData.byUserId[4], 45 | }, 46 | { 47 | id: 5, 48 | firstName: 'Sonequa', 49 | lastName: 'Martin-Green', 50 | email: 'Sonequa+Martin+Green@example.com', 51 | avatarUrl: 52 | 'https://images-na.ssl-images-amazon.com/images/M/MV5BMTgxMTc1MTYzM15BMl5BanBnXkFtZTgwNzI5NjMwOTE@._V1_UY256_CR16,0,172,256_AL_.jpg', 53 | userToOrganizations: organizationsToUsersData.byUserId[5], 54 | }, 55 | ] 56 | 57 | const byId: { [key: number]: User } = _keyBy(list, 'id') 58 | 59 | const usersData = { 60 | list, 61 | byId, 62 | current: byId[1], 63 | } 64 | 65 | export default usersData 66 | -------------------------------------------------------------------------------- /src/_api/_mocks/index.ts: -------------------------------------------------------------------------------- 1 | import { AxiosInstance } from 'axios' 2 | import MockAdapter from 'axios-mock-adapter' 3 | 4 | import usersMocks from './usersMocks' 5 | import organizationsMocks from './organizationsMocks' 6 | 7 | const init = (instance: AxiosInstance) => { 8 | const mockAdapter = new MockAdapter(instance, { delayResponse: 200 }) 9 | 10 | usersMocks.init(mockAdapter) 11 | organizationsMocks.init(mockAdapter) 12 | 13 | return mockAdapter 14 | } 15 | 16 | export default { 17 | init, 18 | } 19 | -------------------------------------------------------------------------------- /src/_api/_mocks/organizationsMocks.ts: -------------------------------------------------------------------------------- 1 | import MockAdapter from 'axios-mock-adapter' 2 | import organizationsData from '../_data/organizationsData' 3 | 4 | export default { 5 | init(mock: MockAdapter) { 6 | mock.onGet('/organizations').reply(200, { 7 | organizations: { 8 | ...organizationsData.list, 9 | }, 10 | count: organizationsData.list.length, 11 | }) 12 | 13 | // 14 | mock.onGet(/\/organizations\/\d+/).reply((config: any) => { 15 | // console.log(config) 16 | const urlPaths = config.url.split('/') 17 | const organizationId = urlPaths[urlPaths.length - 1] 18 | const organization = organizationsData.byId[organizationId] 19 | 20 | if (organization) { 21 | return [200, { ...organization }] 22 | } else { 23 | return [404, { message: 'Organization not found ' }] 24 | } 25 | }) 26 | 27 | mock.onPut(/\/organizations\/\d+/).reply((config: any) => { 28 | const urlPaths = config.url.split('/') 29 | const organizationId = urlPaths[urlPaths.length - 1] 30 | const organizationData = JSON.parse(config.data) 31 | const organization = organizationsData.byId[organizationId] 32 | 33 | if (organization) { 34 | return [200, { ...organization, ...organizationData }] 35 | } else { 36 | return [403, { message: 'Update not permitted' }] 37 | } 38 | }) 39 | 40 | mock.onPost(/\/organizations/).reply((config: any) => { 41 | const organizationData = JSON.parse(config.data) 42 | 43 | return [200, { id: 100, ...organizationData }] 44 | }) 45 | 46 | mock.onDelete(/\/organizations\/\d+/).reply((config: any) => { 47 | return [200, { message: 'Organization deleted' }] 48 | }) 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /src/_api/_mocks/usersMocks.ts: -------------------------------------------------------------------------------- 1 | import MockAdapter from 'axios-mock-adapter' 2 | import usersData from '../_data/usersData' 3 | 4 | export default { 5 | init(mock: MockAdapter) { 6 | mock.onGet('/users/profile').reply(200, { 7 | ...usersData.current, 8 | }) 9 | 10 | mock.onGet('/users').reply(200, { 11 | users: { 12 | ...usersData.list, 13 | }, 14 | count: usersData.list.length, 15 | }) 16 | 17 | // 18 | mock.onGet(/\/users\/\d+/).reply((config: any) => { 19 | // console.log(config) 20 | const urlPaths = config.url.split('/') 21 | const userId = urlPaths[urlPaths.length - 1] 22 | const user = usersData.byId[userId] 23 | 24 | if (user) { 25 | return [200, { ...user }] 26 | } else { 27 | return [404, { message: 'User not found ' }] 28 | } 29 | }) 30 | 31 | mock.onPut(/\/users\/\d+/).reply((config: any) => { 32 | const urlPaths = config.url.split('/') 33 | const userId = urlPaths[urlPaths.length - 1] 34 | const userData = JSON.parse(config.data) 35 | const user = usersData.byId[userId] 36 | 37 | if (user) { 38 | return [200, { ...user, ...userData }] 39 | } else { 40 | return [403, { message: 'Update not permitted' }] 41 | } 42 | }) 43 | 44 | mock.onPost(/\/users/).reply((config: any) => { 45 | const userData = JSON.parse(config.data) 46 | 47 | console.log('config', config) 48 | console.log('userData', userData) 49 | 50 | return [200, { id: 100, ...userData }] 51 | }) 52 | 53 | mock.onDelete(/\/users\/\d+/).reply((config: any) => { 54 | return [200, { message: 'User removed' }] 55 | }) 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /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.baseUrl, 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 mocks from './_mocks' 3 | import organizations from './organizations' 4 | import users from './users' 5 | 6 | // Submodules 7 | import apiSales from '../Sales/_api' 8 | 9 | export interface ApiInitOptions { 10 | useSampleData?: boolean 11 | } 12 | 13 | const init = (options: ApiInitOptions = {}) => { 14 | const mockAdapter = options.useSampleData ? mocks.init(instance) : undefined 15 | 16 | apiSales.init({ 17 | ...options, 18 | instance, 19 | mockAdapter, 20 | }) 21 | 22 | return instance 23 | } 24 | 25 | export default { instance, organizations, users, init } 26 | export { init, instance, organizations, users } 27 | -------------------------------------------------------------------------------- /src/_api/notifications.ts: -------------------------------------------------------------------------------- 1 | const notificationsService = {} 2 | 3 | export default notificationsService 4 | -------------------------------------------------------------------------------- /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 OrganizationsService { 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: OrganizationsService = { 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/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Button = () => { 4 | return 5 | } 6 | 7 | export default Button 8 | -------------------------------------------------------------------------------- /src/_common/Dropdown/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Dropdown = () => { 4 | return
5 | } 6 | 7 | export default Dropdown 8 | -------------------------------------------------------------------------------- /src/_common/Logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | 5 | interface LogoProps { 6 | className?: string 7 | size?: number 8 | } 9 | 10 | const Logo = (props: LogoProps) => { 11 | const classes = useStyles(props) 12 | 13 | return ( 14 | 20 | Modular Material Admin + React 21 | 22 | 27 | 32 | 33 | 34 | ) 35 | } 36 | 37 | const useStyles = makeStyles(theme => ({ 38 | Logo: (props: LogoProps) => ({ 39 | display: 'inline-block', 40 | verticalAlign: 'text-bottom', 41 | width: props.size, 42 | height: props.size, 43 | }), 44 | path: { 45 | transition: 'all .3s ease', 46 | }, 47 | outline: { 48 | fill: 'currentColor', 49 | }, 50 | letter: { 51 | fill: 'currentColor', 52 | }, 53 | })) 54 | 55 | export default Logo 56 | -------------------------------------------------------------------------------- /src/_common/PageBreadcrumbs/PageBreadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Breadcrumbs from '@material-ui/core/Breadcrumbs' 3 | import Link from '@material-ui/core/Link' 4 | import Typography from '@material-ui/core/Typography' 5 | 6 | const PageBreadcrumbs = () => { 7 | return ( 8 | 9 | 10 | Material-UI 11 | 12 | 13 | Core 14 | 15 | Breadcrumb 16 | 17 | ) 18 | } 19 | 20 | export default PageBreadcrumbs 21 | -------------------------------------------------------------------------------- /src/_common/PageBreadcrumbs/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './PageBreadcrumbs' 2 | -------------------------------------------------------------------------------- /src/_common/PageContainer/PageContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import Container from '@material-ui/core/Container' 5 | 6 | import { Theme } from '_theme' 7 | 8 | interface PageContainerProps { 9 | children?: any 10 | } 11 | 12 | const PageContainer = ({ children }: PageContainerProps) => { 13 | const classes = useStyles() 14 | 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | 22 | const useStyles = makeStyles((theme: Theme) => ({ 23 | container: { 24 | flex: 1, 25 | paddingTop: theme.spacing(4), 26 | paddingBottom: theme.spacing(4), 27 | }, 28 | })) 29 | 30 | export default PageContainer 31 | -------------------------------------------------------------------------------- /src/_common/PageContainer/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './PageContainer' 2 | -------------------------------------------------------------------------------- /src/_common/PageTitle/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modularcode/modular-admin-react-pro/80104bdc06a5114a392f9cdd6ef5250f95ef50a3/src/_common/PageTitle/.gitkeep -------------------------------------------------------------------------------- /src/_common/PageToolbar/PageToolbar.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 | import { Theme } from '_theme' 9 | 10 | export interface PageToolbarProps { 11 | title?: React.ReactNode 12 | titleComponent?: React.ComponentType 13 | actions?: React.ReactElement 14 | actionsComponent?: React.ComponentType 15 | classes?: { 16 | container?: string 17 | titleContainer?: string 18 | actionsContainer?: string 19 | } 20 | } 21 | 22 | const PageToolbar: React.FC = (props: PageToolbarProps) => { 23 | const classes = useStyles() 24 | const userClasses = props.classes || {} 25 | 26 | const Title = props.title ? ( 27 | 28 | {props.title} 29 | 30 | ) : null 31 | const TitleComponent = props.titleComponent 32 | 33 | const Actions = props.actions 34 | const ActionsComponent = props.actionsComponent 35 | 36 | return ( 37 | 42 | 49 | {Title && Title} 50 | {TitleComponent && } 51 | 52 | 59 | {Actions && Actions} 60 | {ActionsComponent && } 61 | 62 | 63 | ) 64 | } 65 | 66 | const useStyles = makeStyles((theme: Theme) => 67 | createStyles({ 68 | container: { 69 | marginBottom: '1.5rem', 70 | }, 71 | titleContainer: {}, 72 | actionsContainer: { 73 | display: 'flex', 74 | justifyContent: 'flex-end', 75 | }, 76 | }), 77 | ) 78 | 79 | export default PageToolbar 80 | -------------------------------------------------------------------------------- /src/_common/PageToolbar/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './PageToolbar' 2 | -------------------------------------------------------------------------------- /src/_config/index.ts: -------------------------------------------------------------------------------- 1 | interface Config { 2 | navigationType: 'hash' | 'history' 3 | useSampleData?: boolean 4 | api: { 5 | baseUrl: string 6 | } 7 | } 8 | 9 | const config: Config = { 10 | navigationType: 'hash', 11 | useSampleData: true, 12 | api: { 13 | baseUrl: 'http://localhost:4000/api', 14 | }, 15 | } 16 | 17 | export default config 18 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/DashboardLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | 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 { Theme } from '_theme' 10 | import Header from './Header/Header' 11 | import Sidebar from './Sidebar/Sidebar' 12 | import Footer from './Footer' 13 | 14 | export interface DashboardProps { 15 | children?: any 16 | } 17 | 18 | export default function Dashboard({ children }: DashboardProps) { 19 | const classes = useStyles() 20 | const theme = useTheme() 21 | const isDesktop = useMediaQuery(theme.breakpoints.up('md')) 22 | const isMobile = !isDesktop 23 | 24 | const [isSidebarOpenMobile, setIsSidebarOpenMobile] = React.useState(false) 25 | const [isSidebarCollapsedDesktop, setIsSidebarCollapsedDesktop] = React.useState(false) 26 | 27 | function handleSidebarMobileToggle() { 28 | setIsSidebarOpenMobile(!isSidebarOpenMobile) 29 | } 30 | 31 | function handleSidebarToggle() { 32 | // Open/close on mobile 33 | if (isMobile) { 34 | setIsSidebarOpenMobile(!isSidebarOpenMobile) 35 | } 36 | // Collapse/uncollapse on desktop 37 | else { 38 | setIsSidebarCollapsedDesktop(!isSidebarCollapsedDesktop) 39 | } 40 | } 41 | 42 | return ( 43 |
44 |
53 |
54 |
55 |
65 | 66 | 78 | 84 | 85 | 86 | 87 | 97 | 103 | 104 | 105 |
106 |
107 |
108 | {children} 109 |
110 |
111 |
112 | ) 113 | } 114 | 115 | const useStyles = makeStyles((theme: Theme) => ({ 116 | dashboardContainer: { 117 | display: 'flex', 118 | background: '#f5f5f5', 119 | }, 120 | headerContainer: { 121 | top: 0, 122 | left: 0, 123 | right: 0, 124 | position: 'absolute', 125 | zIndex: theme.zIndex.drawer + 1, 126 | transition: theme.transitions.create(['width', 'margin'], { 127 | easing: theme.transitions.easing.sharp, 128 | duration: theme.transitions.duration.leavingScreen, 129 | }), 130 | }, 131 | headerContainerDesktop: { 132 | left: 'auto', 133 | width: `calc(100% - ${theme.sidebar.width}px)`, 134 | transition: theme.transitions.create(['width', 'margin'], { 135 | easing: theme.transitions.easing.sharp, 136 | duration: theme.transitions.duration.enteringScreen, 137 | }), 138 | }, 139 | headerContainerDesktopDrawerCollapsed: { 140 | width: `calc(100% - ${theme.sidebar.widthCollapsed}px)`, 141 | }, 142 | sidebarContainer: { 143 | position: 'relative', 144 | [theme.breakpoints.up('md')]: { 145 | width: theme.sidebar.width, 146 | flexShrink: 0, 147 | }, 148 | transition: theme.transitions.create('width', { 149 | easing: theme.transitions.easing.sharp, 150 | duration: theme.transitions.duration.leavingScreen, 151 | }), 152 | }, 153 | sidebarContainerMobile: { 154 | width: 0, 155 | }, 156 | sidebarContainerDesktop: { 157 | width: theme.sidebar.width, 158 | }, 159 | sidebarContainerDesktopDrawerCollapsed: { 160 | [theme.breakpoints.up('md')]: { 161 | width: theme.sidebar.widthCollapsed, 162 | }, 163 | }, 164 | drawer: {}, 165 | drawerMobile: { 166 | width: theme.sidebar.width, 167 | }, 168 | drawerDesktop: { 169 | width: '100%', 170 | position: 'absolute', 171 | }, 172 | drawerDesktopCollapsed: { 173 | overflowX: 'hidden', 174 | }, 175 | headerSpacer: theme.mixins.toolbar, 176 | content: { 177 | flexGrow: 1, 178 | height: '100vh', 179 | overflow: 'auto', 180 | flexDirection: 'column', 181 | display: 'flex', 182 | }, 183 | paper: { 184 | padding: theme.spacing(2), 185 | display: 'flex', 186 | overflow: 'auto', 187 | flexDirection: 'column', 188 | }, 189 | fixedHeight: { 190 | height: 240, 191 | }, 192 | })) 193 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React 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 | 6 | import { Theme } from '_theme' 7 | 8 | const Footer = () => { 9 | const classes = useStyles() 10 | 11 | return ( 12 |
13 | 14 | {'Find me on: '} 15 | 16 | GitHub 17 | 18 | {' | '} 19 | 20 | Twitter 21 | 22 | {' | '} 23 | 24 | LinkedIn 25 | 26 | 27 | 28 | {'Built with '} 29 | 30 | Material-UI 31 | 32 | {' by '} 33 | 34 | Gevorg Harutyunyan 35 | 36 | 37 |
38 | ) 39 | } 40 | 41 | const useStyles = makeStyles((theme: Theme) => ({ 42 | footer: { 43 | display: 'flex', 44 | background: '#fff', 45 | padding: '0.5rem 1rem', 46 | justifyContent: 'space-between', 47 | }, 48 | })) 49 | 50 | export default Footer 51 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Footer' 2 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React 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 | 9 | import IconMenu from '@material-ui/icons/Menu' 10 | 11 | import HeaderDemo from './HeaderDemo' 12 | 13 | import HeaderSearch from './HeaderSearch' 14 | import HeaderNotifications from './HeaderNotifications' 15 | import HeaderProfile from './HeaderProfile' 16 | 17 | interface HeaderProps { 18 | onToggle?: (event: React.MouseEvent) => void 19 | } 20 | 21 | const Header = ({ onToggle }: HeaderProps) => { 22 | const classes = useStyles() 23 | 24 | return ( 25 | 26 | 27 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | const useStyles = makeStyles(theme => ({ 48 | header: { 49 | background: '#fff', 50 | color: '#7b7b7b', 51 | boxShadow: 'none', 52 | }, 53 | toolbar: {}, 54 | menuButton: {}, 55 | actions: { 56 | marginLeft: 'auto', 57 | alignItems: 'center', 58 | display: 'flex', 59 | }, 60 | notificationsButton: { 61 | marginRight: 23, 62 | }, 63 | title: { 64 | flexGrow: 1, 65 | }, 66 | })) 67 | 68 | export default Header 69 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Header/HeaderDemo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | import { makeStyles } from '@material-ui/core/styles' 5 | import Tooltip from '@material-ui/core/Tooltip' 6 | import Button from '@material-ui/core/Button' 7 | import IconCode from '@material-ui/icons/Code' 8 | import IconFavorite from '@material-ui/icons/Favorite' 9 | import IconStar from '@material-ui/icons/Star' 10 | 11 | // const SupportLink = React.forwardRef((props, ref) => ( 12 | // 13 | // )) 14 | 15 | const HeaderDemo: React.FC = props => { 16 | const classes = useStyles(props) 17 | 18 | return ( 19 |
20 | 21 | 31 | 32 | 33 | 44 | 45 | 46 | 56 | 57 |
58 | ) 59 | } 60 | 61 | const useStyles = makeStyles(theme => ({ 62 | demo: { 63 | flex: 1, 64 | display: 'flex', 65 | justifyContent: 'center', 66 | alignItems: 'center', 67 | }, 68 | demoIcon: {}, 69 | demoName: { 70 | marginLeft: theme.spacing(1), 71 | [theme.breakpoints.down('md')]: { 72 | display: 'none', 73 | }, 74 | }, 75 | button: { 76 | margin: theme.spacing(1), 77 | [theme.breakpoints.down('md')]: { 78 | margin: 3, 79 | }, 80 | }, 81 | })) 82 | 83 | export default HeaderDemo 84 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Header/HeaderNotifications.tsx: -------------------------------------------------------------------------------- 1 | import React 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 | const notifications = [ 16 | { 17 | user: { 18 | name: 'Remy Sharp', 19 | image: 'https://material-ui.com/static/images/avatar/1.jpg', 20 | }, 21 | title: 'New Order', 22 | content: " — I'll be in your neighborhood doing errands this…", 23 | }, 24 | { 25 | user: { 26 | name: 'Travis Howard', 27 | image: 'https://material-ui.com//static/images/avatar/2.jpg', 28 | }, 29 | title: 'New Signup', 30 | content: " — Wish I could come, but I'm out of town this…", 31 | }, 32 | { 33 | user: { 34 | name: 'Oui Oui', 35 | image: 'https://material-ui.com//static/images/avatar/3.jpg', 36 | }, 37 | title: 'Refund Request', 38 | content: 'please provide me a refund for my order', 39 | }, 40 | ] 41 | 42 | const HeaderNotifications = () => { 43 | const classes = useStyles() 44 | const [anchorEl, setAnchorEl] = React.useState(null) 45 | 46 | function handleClick(event: React.MouseEvent) { 47 | setAnchorEl(event.currentTarget) 48 | } 49 | 50 | function handleClose() { 51 | setAnchorEl(null) 52 | } 53 | 54 | return ( 55 |
56 | 65 | 66 | 67 | 68 | 69 | 87 | 88 | {notifications.map((notification, index) => ( 89 | 90 | 91 | 92 | 93 | 97 | 103 | {notification.user.name} 104 | 105 | {notification.content} 106 | 107 | } 108 | /> 109 | 110 | ))} 111 | 112 | 113 |
114 | ) 115 | } 116 | 117 | // const HeaderNotificationsContent = () => { 118 | // const classes = useStyles() 119 | 120 | // return 121 | // } 122 | 123 | const useStyles = makeStyles(theme => ({ 124 | headerNotifications: { 125 | marginRight: 23, 126 | // position: 'relative', 127 | // position: 'absolute' 128 | }, 129 | notificationsContainer: { 130 | // position: 'relative', 131 | }, 132 | button: {}, 133 | badge: { 134 | color: '#fff', 135 | }, 136 | notifications: { 137 | // width: 360, 138 | maxWidth: 360, 139 | backgroundColor: theme.palette.background.paper, 140 | }, 141 | inline: { 142 | display: 'inline', 143 | }, 144 | })) 145 | 146 | export default HeaderNotifications 147 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Header/HeaderProfile.tsx: -------------------------------------------------------------------------------- 1 | import React 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 | import { useAppStateData } from '../../../_state/appState' 22 | 23 | const HeaderProfile = () => { 24 | const classes = useStyles() 25 | const [anchorEl, setAnchorEl] = React.useState(null) 26 | const { user } = useAppStateData() 27 | 28 | if (!user) { 29 | return
30 | } 31 | 32 | function handleClick(event: React.MouseEvent) { 33 | setAnchorEl(event.currentTarget) 34 | } 35 | 36 | function handleClose() { 37 | setAnchorEl(null) 38 | } 39 | 40 | return ( 41 |
42 | 51 | 56 | {user.firstName} 57 | 58 | 59 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 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 | })) 130 | 131 | export default HeaderProfile 132 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Header/HeaderSearch.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 HeaderSearch = () => { 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 | 64 | 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 HeaderSearch 83 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Header/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Header' 2 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Sidebar/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React 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 { Theme } from '_theme' 8 | import Logo from '_common/Logo/Logo' 9 | import SidebarNav from './SidebarNav' 10 | 11 | interface SidebarProps { 12 | onToggleClick?: (event: React.MouseEvent) => void 13 | isDesktop: boolean 14 | isMobile: boolean 15 | isSidebarCollapsedDesktop: boolean 16 | isSidebarOpenMobile: boolean 17 | } 18 | 19 | const Sidebar = (props: SidebarProps) => { 20 | const { isDesktop, isSidebarCollapsedDesktop } = props 21 | 22 | const classes = useStyles(props) 23 | 24 | return ( 25 | 43 | ) 44 | } 45 | 46 | const useStyles = makeStyles((theme: Theme) => ({ 47 | sidebar: { 48 | position: 'absolute', 49 | top: 0, 50 | bottom: 0, 51 | width: '100%', 52 | height: '100%', 53 | color: theme.sidebar.color, 54 | background: theme.sidebar.background, 55 | overflowX: 'hidden', 56 | overflowY: 'auto', 57 | }, 58 | sidebarHeader: { 59 | display: 'flex', 60 | alignItems: 'center', 61 | justifyContent: 'center', 62 | whiteSpace: 'nowrap', 63 | padding: '0 8px', 64 | ...theme.mixins.toolbar, 65 | }, 66 | sidebarTitleLink: { 67 | textDecoration: 'none', 68 | color: 'inherit', 69 | display: 'flex', 70 | // '&:hover': { 71 | // '& $logo': { 72 | // color: '#fff', 73 | // }, 74 | // }, 75 | }, 76 | logo: { 77 | color: theme.palette.primary.main, 78 | }, 79 | title: (props: SidebarProps) => ({ 80 | // fontSize: '20px', 81 | // fontWeight: 400, 82 | position: 'relative', 83 | overflow: 'visible', 84 | marginLeft: '5px', 85 | display: props.isDesktop && props.isSidebarCollapsedDesktop ? 'none' : 'block', 86 | }), 87 | name: {}, 88 | tagline: { 89 | fontSize: 8, 90 | fontWeight: 'bold', 91 | position: 'absolute', 92 | top: '100%', 93 | marginTop: -5, 94 | background: theme.palette.primary.main, 95 | color: '#fff', 96 | borderRadius: 2, 97 | padding: '1px 3px', 98 | right: 0, 99 | }, 100 | })) 101 | 102 | export default Sidebar 103 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Sidebar/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 IconSales from '@material-ui/icons/MonetizationOn' 7 | import IconProfile from '@material-ui/icons/AccountBox' 8 | import IconAccount from '@material-ui/icons/AccountBalance' // 9 | import IconAdmin from '@material-ui/icons/VpnKey' 10 | import IconMisc from '@material-ui/icons/MoreHoriz' 11 | 12 | import IconDashboard from '@material-ui/icons/Dashboard' 13 | import IconProducts from '@material-ui/icons/LocalMall' 14 | import IconOrders from '@material-ui/icons/ShoppingCart' 15 | import IconPeople from '@material-ui/icons/People' 16 | import IconPersonalVideo from '@material-ui/icons/PersonalVideo' 17 | import IconLibraryBooks from '@material-ui/icons/LibraryBooks' 18 | import IconQuestionAnswer from '@material-ui/icons/QuestionAnswer' 19 | import IconStars from '@material-ui/icons/Stars' 20 | import IconNewReleases from '@material-ui/icons/NewReleases' 21 | import IconSettings from '@material-ui/icons/Settings' 22 | import IconGroup from '@material-ui/icons/Group' 23 | import IconInfo from '@material-ui/icons/Info' // 24 | import IconPreson from '@material-ui/icons/Person' // 25 | // import IconSync from '@material-ui/icons/Sync' 26 | // import IconPhone from '@material-ui/icons/Phone' 27 | import IconStock from '@material-ui/icons/LocalShipping' 28 | import IconLocation from '@material-ui/icons/LocationOn' 29 | 30 | import { Theme } from '_theme' 31 | import SidebarNavItems from './SidebarNavItems' 32 | 33 | export interface SidebarNavProps { 34 | isCollapsed: boolean 35 | } 36 | 37 | const SidebarNav = (props: SidebarNavProps) => { 38 | const { isCollapsed } = props 39 | const classes = useStyles() 40 | 41 | const itemsSales = [ 42 | { 43 | name: 'Sales Dashboard', 44 | link: '/sales/dashboard', 45 | Icon: IconDashboard, 46 | }, 47 | { 48 | name: 'Orders', 49 | link: '/sales/orders', 50 | Icon: IconOrders, 51 | }, 52 | { 53 | name: 'Customers', 54 | link: '/sales/customers', 55 | Icon: IconPeople, 56 | }, 57 | { 58 | name: 'Products', 59 | Icon: IconProducts, 60 | items: [ 61 | { 62 | name: 'All Products', 63 | link: '/sales/products', 64 | }, 65 | { 66 | name: 'Add New', 67 | link: '/sales/products/new', 68 | }, 69 | { 70 | name: 'Categories', 71 | link: '/sales/products/categories', 72 | }, 73 | ], 74 | }, 75 | { 76 | name: 'Stock', 77 | link: '/sales/stock', 78 | Icon: IconStock, 79 | }, 80 | { 81 | name: 'Locations', 82 | link: '/sales/locations', 83 | Icon: IconLocation, 84 | }, 85 | ] 86 | 87 | // eslint-disable-next-line 88 | const itemsContent = [ 89 | { 90 | name: 'All Items', 91 | link: '/content/items', 92 | }, 93 | { 94 | name: 'Add New', 95 | link: '/content/items/new', 96 | }, 97 | 98 | { 99 | name: 'Categories', 100 | link: '/content/categories', 101 | }, 102 | ] 103 | 104 | const itemsProfile = [ 105 | { 106 | name: 'My Profile', 107 | link: '/profile', 108 | Icon: IconInfo, 109 | }, 110 | { 111 | name: 'Profile Settings', 112 | link: '/profile/settings', 113 | Icon: IconSettings, 114 | }, 115 | ] 116 | 117 | const itemsOrganizations = [ 118 | { 119 | name: 'My Organizations', 120 | link: '/organizations', 121 | Icon: IconInfo, 122 | }, 123 | { 124 | name: 'Organization Settings', 125 | link: '/organizations/settings', 126 | Icon: IconSettings, 127 | }, 128 | { 129 | name: 'Team', 130 | link: '/organizations/users', 131 | Icon: IconGroup, 132 | }, 133 | ] 134 | 135 | const itemsAuth = [ 136 | { 137 | name: 'Login', 138 | link: '/auth/login', 139 | }, 140 | { 141 | name: 'Signup', 142 | link: '/auth/signup', 143 | }, 144 | { 145 | name: 'Recover', 146 | link: '/auth/recover', 147 | }, 148 | { 149 | name: 'Reset', 150 | link: '/auth/reset', 151 | }, 152 | ] 153 | 154 | const itemsAdmin = [ 155 | { 156 | name: 'Admin Dashboard', 157 | link: '/admin/dashboard', 158 | Icon: IconDashboard, 159 | }, 160 | { 161 | name: 'All Organizations', 162 | link: '/admin/accounts', 163 | Icon: IconAccount, 164 | }, 165 | { 166 | name: 'All Users', 167 | link: '/admin/users', 168 | Icon: IconGroup, 169 | }, 170 | ] 171 | 172 | const itemsMisc = [ 173 | { 174 | name: 'Search', 175 | link: '/search', 176 | }, 177 | { 178 | name: 'Not Found', 179 | link: '/notfound', 180 | }, 181 | ] 182 | 183 | // eslint-disable-next-line 184 | const itemsAppModules = [ 185 | { 186 | name: 'Sales Management', 187 | link: '/sales', 188 | Icon: IconSales, 189 | items: itemsSales, 190 | }, 191 | // { 192 | // name: 'Customer Support', 193 | // link: '/support', 194 | // Icon: IconPhone, 195 | // }, 196 | // { 197 | // name: 'Content Management', 198 | // link: '/content', 199 | // Icon: IconContent, 200 | // items: itemsContent, 201 | // }, 202 | // { 203 | // name: 'Services', 204 | // link: '/services', 205 | // Icon: IconSync, 206 | // }, 207 | ] 208 | 209 | const itemsCoreModules = [ 210 | { 211 | name: 'Auth', 212 | items: itemsAuth, 213 | Icon: IconPreson, 214 | }, 215 | { 216 | name: 'Profile', 217 | items: itemsProfile, 218 | Icon: IconProfile, 219 | }, 220 | { 221 | name: 'Organizations', 222 | items: itemsOrganizations, 223 | Icon: IconAccount, 224 | }, 225 | { 226 | name: 'Administration', 227 | items: itemsAdmin, 228 | Icon: IconAdmin, 229 | }, 230 | { 231 | name: 'Misc Pages', 232 | items: itemsMisc, 233 | Icon: IconMisc, 234 | }, 235 | ] 236 | 237 | const itemsUI = [ 238 | { 239 | name: 'UI Components', 240 | link: '/demo/components', 241 | Icon: IconPersonalVideo, 242 | }, 243 | ] 244 | 245 | const itemsTheme = [ 246 | { 247 | name: 'Why Modular?', 248 | link: '/demo/features', 249 | Icon: IconNewReleases, 250 | IconClassName: classes.iconFeatures, 251 | }, 252 | { 253 | name: 'Docs', 254 | link: '/demo/docs', 255 | Icon: IconLibraryBooks, 256 | IconClassName: classes.iconDocs, 257 | }, 258 | { 259 | name: 'Supporters', 260 | link: '/demo/supporters', 261 | Icon: IconStars, 262 | IconClassName: classes.iconSupporters, 263 | }, 264 | { 265 | name: 'Discuss', 266 | link: '/demo/discuss', 267 | Icon: IconQuestionAnswer, 268 | IconClassName: classes.iconDiscuss, 269 | }, 270 | ] 271 | 272 | return ( 273 |
274 | 275 | {!isCollapsed && ( 276 | 277 | Applications 278 | 279 | )} 280 | 281 | 282 | 283 | 284 | {!isCollapsed && ( 285 | 286 | Basic Functionality 287 | 288 | )} 289 | 290 | 291 | 292 | 293 | {!isCollapsed && ( 294 | 295 | UI & Utils 296 | 297 | )} 298 | 299 | 300 | 301 | {!isCollapsed && ( 302 | 303 | Misc 304 | 305 | )} 306 | 307 | 308 |
309 | ) 310 | } 311 | 312 | const useStyles = makeStyles((theme: Theme) => 313 | createStyles({ 314 | navList: { 315 | width: theme.sidebar.width, 316 | fontSize: '1.1em', 317 | fontWeight: 400, 318 | lineHeight: 1.5, 319 | letterSpacing: '0.00938em', 320 | }, 321 | navListHeader: { 322 | textAlign: 'center', 323 | }, 324 | iconFeatures: { 325 | color: '#95de3c', 326 | }, 327 | iconDocs: { 328 | color: '#f8cda9', 329 | }, 330 | iconSupporters: { 331 | color: '#e3b546', 332 | }, 333 | iconDiscuss: { 334 | color: '#ccc', 335 | }, 336 | }), 337 | ) 338 | 339 | export default SidebarNav 340 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Sidebar/SidebarNavItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, HTMLAttributes } from 'react' 2 | import clsx from 'clsx' 3 | 4 | import { makeStyles, createStyles } from '@material-ui/core/styles' 5 | import { NavLink, NavLinkProps } from 'react-router-dom' 6 | import List from '@material-ui/core/List' 7 | import ListItem from '@material-ui/core/ListItem' 8 | import ListItemIcon from '@material-ui/core/ListItemIcon' 9 | import ListItemText from '@material-ui/core/ListItemText' 10 | import Tooltip from '@material-ui/core/Tooltip' 11 | import Collapse from '@material-ui/core/Collapse' 12 | 13 | import { SvgIconProps } from '@material-ui/core/SvgIcon' 14 | import IconExpandLess from '@material-ui/icons/ExpandLess' 15 | import IconExpandMore from '@material-ui/icons/ExpandMore' 16 | import IconSpacer from '@material-ui/icons/FiberManualRecord' 17 | 18 | import { Theme } from '_theme' 19 | 20 | export interface SidebarNavItemProps { 21 | name: string 22 | link?: string 23 | Icon?: React.ComponentType 24 | IconStyles?: React.CSSProperties 25 | IconClassName?: string 26 | IconClassNameActive?: string 27 | isCollapsed?: boolean 28 | isOpen?: boolean 29 | isNested?: boolean 30 | nestingLevel?: number 31 | nestingOffset?: number 32 | className?: string 33 | items?: SidebarNavItemProps[] 34 | match?: object 35 | } 36 | 37 | export interface ListItemLinkProps extends NavLinkProps {} 38 | 39 | export interface ListItemComponentProps extends HTMLAttributes { 40 | link?: string | null 41 | children?: any 42 | isCollapsed?: boolean | null 43 | } 44 | 45 | // ---------------------------------------------------------------------- 46 | 47 | export const ListItemLink: React.ExoticComponent = forwardRef( 48 | (props: ListItemLinkProps, ref: React.Ref) => ( 49 | 50 | ), 51 | ) 52 | 53 | // Can be a link, or button 54 | export const ListItemComponent: React.ExoticComponent< 55 | ListItemComponentProps 56 | > = forwardRef((props: ListItemComponentProps, ref: React.Ref) => { 57 | // Omit isCollapsed 58 | const { isCollapsed, ...newProps } = props 59 | const classes = useStyles() 60 | 61 | const component = 62 | typeof props.link === 'string' ? ( 63 | 64 | ) : ( 65 | 66 | ) 67 | 68 | return ( 69 |
70 | {component} 71 |
72 | ) 73 | }) 74 | 75 | const SidebarNavItem: React.FC = (props: SidebarNavItemProps) => { 76 | const { 77 | name, 78 | link, 79 | Icon, 80 | IconStyles = {}, 81 | IconClassName = '', 82 | isCollapsed, 83 | // isNested, 84 | nestingLevel = 0, 85 | nestingOffset = 16, 86 | className, 87 | items = [], 88 | } = props 89 | const isTooltipEnabeld = isCollapsed 90 | const classes = useStyles() 91 | const hasChildren = items && items.length > 0 92 | 93 | // Flattened array of all children 94 | function getItemsAll(items: SidebarNavItemProps[]): SidebarNavItemProps[] { 95 | return items.reduce((allItems: SidebarNavItemProps[], item: SidebarNavItemProps) => { 96 | // let res = allItems.concat([item]) 97 | 98 | if (item.items && item.items.length) { 99 | return allItems.concat([item], getItemsAll(item.items)) 100 | } else { 101 | return allItems.concat([item]) 102 | } 103 | }, []) 104 | } 105 | 106 | const itemsAll = getItemsAll(items) 107 | const hasChildrenAndIsActive = 108 | hasChildren && 109 | itemsAll.filter(item => `#${item.link}` === window.location.hash).length > 0 110 | const isOpen = hasChildrenAndIsActive || false 111 | const [open, setOpen] = React.useState(isOpen) 112 | 113 | function handleClick() { 114 | setOpen(!open) 115 | } 116 | 117 | const ListItemIconInner = 118 | (!!Icon && ) || 119 | (isCollapsed && ) || 120 | '' 121 | 122 | const nestingOffsetChildren = !isCollapsed ? nestingOffset + 16 : 16 123 | 124 | const ListItemElement = ( 125 | 141 | {!!ListItemIconInner && ( 142 | 146 | {ListItemIconInner} 147 | 148 | )} 149 | 150 | {hasChildren && !open && } 151 | {hasChildren && open && } 152 | 153 | ) 154 | 155 | const ListItemRoot = isTooltipEnabeld ? ( 156 | 163 | {ListItemElement} 164 | 165 | ) : ( 166 | ListItemElement 167 | ) 168 | 169 | const ListItemChildren = hasChildren ? ( 170 |
171 | 172 | {/* */} 173 | 174 | {items.map(item => ( 175 | 184 | ))} 185 | 186 | 187 |
188 | ) : null 189 | 190 | return ( 191 |
197 | {ListItemRoot} 198 | {ListItemChildren} 199 |
200 | ) 201 | } 202 | 203 | const useStyles = makeStyles((theme: Theme) => 204 | createStyles({ 205 | // nested: { 206 | // paddingLeft: theme.spacing(10), 207 | // }, 208 | navItemWrapper: { 209 | position: 'relative', 210 | }, 211 | navItemWrapperActive: { 212 | // background: 'rgba(0, 0, 0, 0.08)', 213 | }, 214 | navItemWrapperActiveCollapsed: { 215 | background: 'rgba(0, 0, 0, 0.08)', 216 | }, 217 | navItem: { 218 | position: 'relative', 219 | transition: 'background .23s ease', 220 | '&.active': { 221 | color: theme.palette.secondary.main, 222 | // background: 'rgba(0, 0, 0, 0.08)', 223 | '& .MuiListItemIcon-root': { 224 | // color: '#fff', 225 | color: theme.palette.secondary.main, 226 | }, 227 | }, 228 | }, 229 | navItemChildren: { 230 | transition: 'background .23s ease', 231 | // position: 'absolute', 232 | }, 233 | navItemChildrenActive: { 234 | // background: 'rgba(0, 0, 0, 0.1)', 235 | }, 236 | navItemCollapsed: { 237 | whiteSpace: 'nowrap', 238 | flexWrap: 'nowrap', 239 | width: theme.sidebar.widthCollapsed, 240 | '& $iconToggle': { 241 | position: 'absolute', 242 | bottom: -1, 243 | fontSize: 14, 244 | left: '50%', 245 | marginLeft: '-0.5em', 246 | }, 247 | '&.active': { 248 | background: 'rgba(0, 0, 0, 0.08)', 249 | }, 250 | }, 251 | navItemCollapsedWrapper: { 252 | width: theme.sidebar.widthCollapsed, 253 | }, 254 | navItemIcon: { 255 | minWidth: 40, 256 | }, 257 | iconToggle: {}, 258 | iconSpacer: { 259 | fontSize: 13, 260 | marginLeft: 6, 261 | }, 262 | }), 263 | ) 264 | 265 | export default SidebarNavItem 266 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Sidebar/SidebarNavItems.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import SidebarNavItem, { SidebarNavItemProps } from './SidebarNavItem' 4 | 5 | export interface SidebarNavItemsProps { 6 | items: SidebarNavItemProps[] 7 | isNested?: boolean 8 | isCollapsed?: boolean 9 | } 10 | 11 | const SidebarNavItems: React.FC = (props: SidebarNavItemsProps) => { 12 | const { items = [], isCollapsed = false, isNested = false } = props 13 | // const classes = useStyles() 14 | 15 | return ( 16 | <> 17 | {items.map((item, index) => ( 18 | 24 | ))} 25 | 26 | ) 27 | } 28 | 29 | export default SidebarNavItems 30 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Sidebar' 2 | -------------------------------------------------------------------------------- /src/_layouts/DashboardLayout/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './DashboardLayout' 2 | -------------------------------------------------------------------------------- /src/_layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import DashboardLayout from './DashboardLayout' 2 | 3 | export { DashboardLayout } 4 | -------------------------------------------------------------------------------- /src/_services/authService.tsx: -------------------------------------------------------------------------------- 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 | 8 | // import config from '../config' 9 | 10 | const sampleToken = 11 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Db8fjZU7MkBZoJDjmjuvv2EeDgG9RSaZ1xKm__qHelw' 12 | 13 | export interface AuthService { 14 | token: string | null 15 | init(options?: AuthServiceInitOptions): void 16 | auth(token: string): void 17 | unauth(): void 18 | isAuthenticated(): boolean 19 | getToken(): string | null 20 | } 21 | 22 | interface AuthServiceInitOptions { 23 | useSampleData?: boolean 24 | } 25 | 26 | const authService: AuthService = { 27 | token: null, 28 | init({ useSampleData = false } = {}) { 29 | if (useSampleData) { 30 | this.token = sampleToken 31 | } else { 32 | this.token = store.get('token') || null 33 | } 34 | }, 35 | auth(token) { 36 | store.set('token', token) 37 | }, 38 | unauth() { 39 | store.remove('token') 40 | }, 41 | isAuthenticated() { 42 | return !!this.token 43 | }, 44 | getToken() { 45 | return this.token 46 | }, 47 | } 48 | 49 | export default authService 50 | -------------------------------------------------------------------------------- /src/_services/helpersService.tsx: -------------------------------------------------------------------------------- 1 | // Helper type to ovverride types 2 | // https://stackoverflow.com/questions/43080547/how-to-override-type-properties-in-typescript?rq=1 3 | export type Override = Pick> & U 4 | 5 | const helpersService = {} 6 | 7 | export default helpersService 8 | -------------------------------------------------------------------------------- /src/_state/appModel.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from '@rematch/core' // RematchDispatch 2 | import User from '_types/User' 3 | import users from '_api/users' 4 | 5 | export interface AppStateStatus { 6 | loading?: boolean 7 | error?: Error 8 | } 9 | 10 | export interface AppStateData { 11 | user?: User 12 | } 13 | export interface AppState extends AppStateStatus { 14 | data: AppStateData 15 | } 16 | 17 | const initialState: AppState = { 18 | loading: true, 19 | error: undefined, 20 | data: {}, 21 | } 22 | 23 | const model = createModel({ 24 | state: initialState, 25 | reducers: { 26 | setStatus: ( 27 | state: AppState, 28 | payload: { loading?: boolean; error?: any }, 29 | ): AppState => ({ 30 | ...state, 31 | ...payload, 32 | }), 33 | setData: (state: AppState, payload: AppStateData): AppState => ({ 34 | ...state, 35 | data: { 36 | ...state.data, 37 | ...payload, 38 | }, 39 | }), 40 | }, 41 | // dispatch: RematchDispatch 42 | effects: () => ({ 43 | // payload, rootState 44 | async request() { 45 | this.setStatus({ 46 | loading: true, 47 | error: null, 48 | }) 49 | 50 | try { 51 | const currentUser = await users.getProfile() 52 | 53 | this.setData({ 54 | user: currentUser, 55 | }) 56 | } catch (e) { 57 | this.setStatus({ 58 | error: e, 59 | }) 60 | } 61 | 62 | this.setStatus({ 63 | loading: false, 64 | }) 65 | }, 66 | }), 67 | }) 68 | 69 | export default model 70 | -------------------------------------------------------------------------------- /src/_state/appState.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from 'react-redux' 2 | import { RootState, RootDispatch } from '_state' 3 | 4 | import dashboard, { AppStateData } from './appModel' //AppState 5 | 6 | export function useAppState() { 7 | return useSelector((state: RootState) => state.dashboard) 8 | } 9 | 10 | export function useAppStateData(): AppStateData { 11 | const { data } = useAppState() 12 | 13 | return data 14 | } 15 | 16 | export function useAppStateMethods() { 17 | const dispatch = useDispatch() 18 | return dispatch.dashboard 19 | } 20 | 21 | export default dashboard 22 | -------------------------------------------------------------------------------- /src/_state/index.ts: -------------------------------------------------------------------------------- 1 | import { init, RematchRootState, RematchDispatch } from '@rematch/core' 2 | import immerPlugin from '@rematch/immer' 3 | 4 | import dashboard from '_state/appState' 5 | 6 | const immer = immerPlugin() 7 | 8 | const models = { 9 | dashboard, 10 | } 11 | 12 | export const store = init({ 13 | models, 14 | plugins: [immer], 15 | }) 16 | 17 | export type Store = typeof store 18 | export type RootState = RematchRootState 19 | export type RootDispatch = RematchDispatch 20 | 21 | export default store 22 | -------------------------------------------------------------------------------- /src/_theme/index.ts: -------------------------------------------------------------------------------- 1 | import { Theme as MuiTheme, createMuiTheme } from '@material-ui/core/styles' 2 | import { blue } from '@material-ui/core/colors' 3 | 4 | export interface Theme extends MuiTheme { 5 | sidebar: { 6 | width: number 7 | widthCollapsed: number 8 | background: string 9 | color: string 10 | } 11 | header: { 12 | background: string 13 | } 14 | } 15 | 16 | const baseTheme = createMuiTheme({ 17 | props: { 18 | MuiPaper: { 19 | elevation: 0, 20 | }, 21 | MuiAppBar: { 22 | elevation: 1, 23 | }, 24 | MuiButton: { 25 | // elevation: 0, 26 | }, 27 | MuiMenu: { 28 | elevation: 1, 29 | }, 30 | MuiCard: { 31 | elevation: 0, 32 | }, 33 | }, 34 | overrides: { 35 | MuiButton: { 36 | root: { 37 | minWidth: 0, 38 | }, 39 | contained: { 40 | boxShadow: 'none', 41 | '&:active': { 42 | boxShadow: 'none', 43 | }, 44 | '&:focus': { 45 | boxShadow: 'none', 46 | }, 47 | }, 48 | containedSecondary: { 49 | color: '#fff', 50 | '&:hover': { 51 | backgroundColor: 'rgb(118, 195, 21)', 52 | }, 53 | }, 54 | }, 55 | MuiButtonGroup: { 56 | root: { 57 | boxShadow: 'none', 58 | }, 59 | contained: { 60 | boxShadow: 'none', 61 | '&:active': { 62 | boxShadow: 'none', 63 | }, 64 | '&:focus': { 65 | boxShadow: 'none', 66 | }, 67 | }, 68 | }, 69 | MuiListItemIcon: { 70 | root: { 71 | minWidth: 40, 72 | }, 73 | }, 74 | }, 75 | palette: { 76 | secondary: { 77 | main: '#8cd136', //indigo[600], 78 | }, 79 | primary: { 80 | main: blue[600], //'#619f30', 81 | }, 82 | }, 83 | typography: { 84 | h1: { 85 | fontSize: '2rem', 86 | }, 87 | h2: { 88 | fontSize: '1.8rem', 89 | }, 90 | h3: { 91 | fontSize: '1.6rem', 92 | }, 93 | h4: { 94 | fontSize: '1.4rem', 95 | }, 96 | h5: { 97 | fontSize: '1.2rem', 98 | }, 99 | h6: { 100 | fontSize: '1rem', 101 | }, 102 | }, 103 | }) 104 | 105 | const adminTheme = { 106 | header: { 107 | background: '#fff', 108 | }, 109 | sidebar: { 110 | width: 255, 111 | widthCollapsed: baseTheme.spacing(7), 112 | background: '#4a4d5a;', 113 | color: '#fff', 114 | }, 115 | } 116 | 117 | const theme = { 118 | ...baseTheme, 119 | ...adminTheme, 120 | } 121 | 122 | export default theme 123 | -------------------------------------------------------------------------------- /src/_types/Entity.ts: -------------------------------------------------------------------------------- 1 | export type EntityId = number | string 2 | 3 | export default interface Entity { 4 | createdAt?: string 5 | updatedAt?: string 6 | } 7 | -------------------------------------------------------------------------------- /src/_types/Organization.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from '_types/Entity' 2 | import User from './User' 3 | import OrganizationToUser from './OrganizationToUser' 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 | organizationToUsers?: OrganizationToUser[] 22 | } 23 | -------------------------------------------------------------------------------- /src/_types/OrganizationToUser.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from '_types/Entity' 2 | 3 | import Organization, { OrganizationId } from './Organization' 4 | import User, { UserId } from './User' 5 | 6 | export type OrganizationToUserId = EntityId 7 | export type OrganizationUserRole = 'member' | 'admin' | 'owner' 8 | 9 | export default interface OrganizationToUser extends Entity { 10 | id: OrganizationToUserId 11 | organizationId: OrganizationId 12 | userId: UserId 13 | role: OrganizationUserRole 14 | organization?: Organization 15 | user?: User 16 | } 17 | -------------------------------------------------------------------------------- /src/_types/User.ts: -------------------------------------------------------------------------------- 1 | import Entity, { EntityId } from './Entity' 2 | 3 | import Organization from './Organization' 4 | import OrganizationToUser from './OrganizationToUser' 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?: UserGlobalRole 21 | } 22 | 23 | export default interface User extends UserSubmissionData, Entity { 24 | id: UserId 25 | organizations?: Organization[] 26 | userToOrganizations?: OrganizationToUser[] 27 | } 28 | -------------------------------------------------------------------------------- /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 | import * as serviceWorker from './serviceWorker' 7 | 8 | import config from './_config' 9 | import authService from './_services/authService' 10 | import api from './_api' 11 | 12 | // Init the API service 13 | authService.init({ 14 | useSampleData: config.useSampleData, 15 | }) 16 | 17 | // Init rest API client 18 | api.init({ 19 | useSampleData: config.useSampleData, 20 | }) 21 | 22 | ReactDOM.render(, document.getElementById('root')) 23 | 24 | // If you want your app to work offline and load faster, you can change 25 | // unregister() to register() below. Note this comes with some pitfalls. 26 | // Learn more about service workers: https://bit.ly/CRA-PWA 27 | serviceWorker.unregister() 28 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), 19 | ) 20 | 21 | interface Config { 22 | onSuccess?: (registration: ServiceWorkerRegistration) => void 23 | onUpdate?: (registration: ServiceWorkerRegistration) => void 24 | } 25 | 26 | export function register(config?: Config): void { 27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 28 | // The URL constructor is available in all browsers that support SW. 29 | const publicUrl = new URL((process as { env: { [key: string]: string } }).env.PUBLIC_URL, window.location.href) 30 | if (publicUrl.origin !== window.location.origin) { 31 | // Our service worker won't work if PUBLIC_URL is on a different origin 32 | // from what our page is served on. This might happen if a CDN is used to 33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 34 | return 35 | } 36 | 37 | window.addEventListener('load', (): void => { 38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` 39 | 40 | if (isLocalhost) { 41 | // This is running on localhost. Let's check if a service worker still exists or not. 42 | checkValidServiceWorker(swUrl, config) 43 | 44 | // Add some additional logging to localhost, pointing developers to the 45 | // service worker/PWA documentation. 46 | navigator.serviceWorker.ready.then((): void => { 47 | console.log( 48 | 'This web app is being served cache-first by a service ' + 49 | 'worker. To learn more, visit https://bit.ly/CRA-PWA', 50 | ) 51 | }) 52 | } else { 53 | // Is not localhost. Just register service worker 54 | registerValidSW(swUrl, config) 55 | } 56 | }) 57 | } 58 | } 59 | 60 | function registerValidSW(swUrl: string, config?: Config): void { 61 | navigator.serviceWorker 62 | .register(swUrl) 63 | .then(registration => { 64 | registration.onupdatefound = () => { 65 | const installingWorker = registration.installing 66 | if (installingWorker == null) { 67 | return 68 | } 69 | installingWorker.onstatechange = () => { 70 | if (installingWorker.state === 'installed') { 71 | if (navigator.serviceWorker.controller) { 72 | // At this point, the updated precached content has been fetched, 73 | // but the previous service worker will still serve the older 74 | // content until all client tabs are closed. 75 | console.log( 76 | 'New content is available and will be used when all ' + 77 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', 78 | ) 79 | 80 | // Execute callback 81 | if (config && config.onUpdate) { 82 | config.onUpdate(registration) 83 | } 84 | } else { 85 | // At this point, everything has been precached. 86 | // It's the perfect time to display a 87 | // "Content is cached for offline use." message. 88 | console.log('Content is cached for offline use.') 89 | 90 | // Execute callback 91 | if (config && config.onSuccess) { 92 | config.onSuccess(registration) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | }) 99 | .catch(error => { 100 | console.error('Error during service worker registration:', error) 101 | }) 102 | } 103 | 104 | function checkValidServiceWorker(swUrl: string, config?: Config) { 105 | // Check if the service worker can be found. If it can't reload the page. 106 | fetch(swUrl) 107 | .then(response => { 108 | // Ensure service worker exists, and that we really are getting a JS file. 109 | const contentType = response.headers.get('content-type') 110 | if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload() 115 | }) 116 | }) 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config) 120 | } 121 | }) 122 | .catch(() => { 123 | console.log('No internet connection found. App is running in offline mode.') 124 | }) 125 | } 126 | 127 | export function unregister() { 128 | if ('serviceWorker' in navigator) { 129 | navigator.serviceWorker.ready.then(registration => { 130 | registration.unregister() 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /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": "preserve", 22 | "typeRoots" : ["./node_modules/@types/", "./src/_types"] 23 | }, 24 | "include": [ 25 | "src" 26 | ], 27 | "exclude": [ 28 | "node_modules/@nivo/line/index.d.ts" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------