├── .babelrc.js ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config-overrides.js ├── jsconfig.json ├── nginx.conf ├── package.json ├── public ├── assets │ └── images │ │ ├── illustrations │ │ └── dreamer.svg │ │ ├── logo-circle.png │ │ ├── logo-circle.svg │ │ ├── logo.png │ │ ├── logo.svg │ │ └── sidebar │ │ └── sidebar-bg-dark.jpg ├── favicon.ico ├── index.html ├── locales │ ├── en │ │ └── translation.json │ └── pl │ │ └── translation.json └── manifest.json ├── src ├── _index.scss ├── app │ ├── App.jsx │ ├── MatxLayout │ │ ├── Layout1 │ │ │ ├── Layout1.jsx │ │ │ ├── Layout1Settings.js │ │ │ ├── Layout1Sidenav.jsx │ │ │ └── Layout1Topbar.jsx │ │ ├── MatxLayout.jsx │ │ ├── MatxLayoutSFC.jsx │ │ ├── MatxTheme │ │ │ ├── EchartTheme.jsx │ │ │ ├── MatxCssVars.jsx │ │ │ ├── MatxTheme.jsx │ │ │ ├── SecondarySidenavTheme │ │ │ │ └── SecondarySidenavTheme.jsx │ │ │ ├── SidenavTheme.jsx │ │ │ ├── SidenavTheme │ │ │ │ ├── SidenavTheme.jsx │ │ │ │ └── SidenavThemeStyles.jsx │ │ │ ├── themeColors.js │ │ │ └── themeOptions.js │ │ ├── SharedCompoents │ │ │ ├── Brand.jsx │ │ │ ├── Footer.jsx │ │ │ ├── MatxCustomizer │ │ │ │ ├── BadgeSelected.jsx │ │ │ │ ├── Layout1Customizer.jsx │ │ │ │ ├── Layout2Customizer.jsx │ │ │ │ ├── MatxCustomizer.jsx │ │ │ │ └── customizerOptions.js │ │ │ ├── SecondarySidebar │ │ │ │ ├── SecondarySidebar.jsx │ │ │ │ ├── SecondarySidebarContent.jsx │ │ │ │ └── SecondarySidebarToggle.jsx │ │ │ ├── Sidenav.jsx │ │ │ └── TopbarMenu.jsx │ │ ├── index.js │ │ └── settings.js │ ├── NotificationDisplay.jsx │ ├── RootRoutes.jsx │ ├── appContext.js │ ├── auth │ │ ├── Auth.jsx │ │ ├── AuthGuard.jsx │ │ └── authRoles.js │ ├── navigations.js │ ├── redux │ │ ├── Store.js │ │ ├── actions │ │ │ ├── BotCreateActions.js │ │ │ ├── BotRightsActions.js │ │ │ ├── BotSettingsActions.js │ │ │ ├── BotsActions.js │ │ │ ├── LayoutActions.js │ │ │ ├── LoginActions.js │ │ │ ├── NavigationAction.js │ │ │ ├── NavigationActions.js │ │ │ ├── NotificationsActions.js │ │ │ ├── UserActions.js │ │ │ ├── UserCreateActions.js │ │ │ ├── UserRemoveActions.js │ │ │ └── UsersActions.js │ │ └── reducers │ │ │ ├── BotCreateReducer.js │ │ │ ├── BotRightsReducer.js │ │ │ ├── BotSettingsReducer.js │ │ │ ├── BotsReducer.js │ │ │ ├── LayoutReducer.js │ │ │ ├── LoginReducer.js │ │ │ ├── NavigationReducer.js │ │ │ ├── NotificationReducer.js │ │ │ ├── NotificationsReducer.js │ │ │ ├── RootReducer.js │ │ │ ├── ScrumBoardReducer.js │ │ │ ├── UserCreateReducer.js │ │ │ ├── UserReducer.js │ │ │ └── UsersReducer.js │ ├── services │ │ └── jwtAuthService.js │ ├── ui │ │ ├── AdvanceTable.jsx │ │ ├── BlockButton.jsx │ │ └── Spinner.jsx │ └── views │ │ ├── bot │ │ ├── AddRightModel.jsx │ │ ├── Bot.jsx │ │ ├── BotRoutes.js │ │ ├── RightsTable.jsx │ │ ├── SettingFormHeader.jsx │ │ └── SettingsFormBody.jsx │ │ ├── botcreate │ │ ├── BotCreate.jsx │ │ └── BotCreateRoutes.js │ │ ├── dashboard │ │ ├── Analytics.jsx │ │ ├── DashboardRoutes.js │ │ ├── OtherBotsTable.jsx │ │ ├── RemoveBotModal.jsx │ │ ├── StatsCard.jsx │ │ ├── TransferBotModal.jsx │ │ └── UserBotsTable.jsx │ │ ├── sessions │ │ ├── NotFound.jsx │ │ ├── SessionRoutes.js │ │ └── SignIn.jsx │ │ └── users │ │ ├── PasswordChangeModal.jsx │ │ ├── RemoveUserModal.jsx │ │ ├── Users.jsx │ │ └── UsersRoutes.js ├── axios.js ├── history.js ├── i18n.js ├── index.jsx ├── matx │ ├── components │ │ ├── Breadcrumb.jsx │ │ ├── ConfirmationDialog.jsx │ │ ├── LoaderBounce.jsx │ │ ├── MatxHorizontalNav │ │ │ └── MatxHorizontalNav.jsx │ │ ├── MatxListItem1.jsx │ │ ├── MatxLoadable │ │ │ ├── Loading.js │ │ │ └── MatxLoadable.jsx │ │ ├── MatxLoading │ │ │ └── MatxLoading.jsx │ │ ├── MatxMenu.jsx │ │ ├── MatxProgressBar.jsx │ │ ├── MatxSearchBox.jsx │ │ ├── MatxSidenav │ │ │ ├── MatxSidenav.jsx │ │ │ ├── MatxSidenavContainer.jsx │ │ │ └── MatxSidenavContent.jsx │ │ ├── MatxSnackbar.jsx │ │ ├── MatxSuspense │ │ │ └── MatxSuspense.jsx │ │ ├── MatxToolbarMenu.jsx │ │ ├── MatxVerticalNav │ │ │ ├── MatxVerticalNav.jsx │ │ │ └── MatxVerticalNavExpansionPanel.jsx │ │ ├── RectangleAvatar.jsx │ │ ├── RichTextEditor.jsx │ │ └── cards │ │ │ ├── CardWidget1.jsx │ │ │ └── SimpleCard.jsx │ ├── index.js │ └── theme │ │ └── EchartTheme.jsx ├── serviceWorker.js ├── styles │ ├── _app.scss │ ├── _mixins.scss │ ├── _reboot.scss │ ├── _variables.scss │ ├── components │ │ ├── _avatar.scss │ │ ├── _customizer.scss │ │ ├── _index.scss │ │ ├── _list.scss │ │ ├── _loader.scss │ │ ├── _matx-search-box.scss │ │ ├── _matx-sidenav.scss │ │ ├── _matx-tootbar-menu.scss │ │ ├── _notification.scss │ │ └── _shopping-cart.scss │ ├── layouts │ │ ├── _index.scss │ │ ├── layout1 │ │ │ ├── _index.scss │ │ │ └── _layout1.scss │ │ ├── layout2 │ │ │ ├── _index.scss │ │ │ ├── _layout2.scss │ │ │ ├── _navbar.scss │ │ │ ├── _topbar.scss │ │ │ └── _variables.scss │ │ └── shared │ │ │ ├── _footer.scss │ │ │ ├── _index.scss │ │ │ ├── _layout.scss │ │ │ └── _sidenav.scss │ ├── utilities │ │ ├── _animations.scss │ │ ├── _border.scss │ │ ├── _carousel.scss │ │ ├── _color.scss │ │ ├── _common.scss │ │ ├── _functions.scss │ │ ├── _misc.scss │ │ ├── _positionings.scss │ │ ├── _shadow.scss │ │ ├── _spacing.scss │ │ ├── _typography.scss │ │ └── _utilities.scss │ └── views │ │ ├── _CRUDTable.scss │ │ ├── _calendar.scss │ │ ├── _carousel.scss │ │ ├── _chat-box.scss │ │ ├── _dashboard.scss │ │ ├── _ecommerce.scss │ │ ├── _form.scss │ │ ├── _inbox.scss │ │ ├── _index.scss │ │ ├── _invoice.scss │ │ ├── _landing.scss │ │ ├── _list.scss │ │ ├── _page-layouts.scss │ │ ├── _pricing.scss │ │ ├── _sales.scss │ │ ├── _scrum-board.scss │ │ ├── _sessions.scss │ │ ├── _todo.scss │ │ └── _topbar.scss └── utils.js └── yarn.lock /.babelrc.js: -------------------------------------------------------------------------------- 1 | const plugins = [ 2 | [ 3 | "babel-plugin-import", 4 | { 5 | libraryName: "@material-ui/core", 6 | // Use "'libraryDirectory': ''," if your bundler does not support ES modules 7 | libraryDirectory: "esm", 8 | camel2DashComponentName: false 9 | }, 10 | "core" 11 | ], 12 | [ 13 | "babel-plugin-import", 14 | { 15 | libraryName: "@material-ui/icons", 16 | // Use "'libraryDirectory': ''," if your bundler does not support ES modules 17 | libraryDirectory: "esm", 18 | camel2DashComponentName: false 19 | }, 20 | "icons" 21 | ] 22 | ]; 23 | 24 | module.exports = { plugins }; 25 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/069dd3c1d72391db25847392eba49a08ea574f6d/.dockerignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /docs/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18-alpine 2 | WORKDIR /app 3 | COPY ./package.json ./ 4 | RUN npm install 5 | COPY . . 6 | RUN npm run build 7 | 8 | FROM nginx:alpine 9 | COPY --from=0 /app/build /var/www 10 | COPY nginx.conf /etc/nginx/nginx.conf 11 | EXPOSE 80 12 | ENTRYPOINT ["nginx","-g","daemon off;"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © Mateusz Budaj 4 | Copyright © 2020 UI LIB 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TS3AudioBot-Control-Panel 2 | 3 | # [Instalacja po polsku](https://egcforum.pl/topic/3027-ts3audiobot-control-panel/) 4 | 5 | TS3AudioBot Control Panel allows you to easily create and manipulate bots. Assign them between users and allow users to add play rights to own bots. 6 | 7 | ### Suported languages 8 | 9 | - English 10 | - Polish 11 | 12 | Feel free to pull request with translation :) 13 | 14 | ### Tech 15 | 16 | - reactjs.org 17 | - nodejs.org 18 | - nestjs.com 19 | - [Matx](https://github.com/uilibrary/matx-react) - styling for dashboard 20 | 21 | ### Installation v2 22 | 23 | TS3AudioBot Control Panel requires [Docker](https://docs.docker.com/engine/install/) 24 | 25 | ```sh 26 | $ mkdir abdash 27 | $ cd abdash 28 | $ wget https://github.com/elipeF/TS3AudioBot-Control-Panel/releases/download/2.0.0/kickstartv2.tar.gz 29 | $ tar -xvf kickstartv2.tar.gz 30 | $ chown -R 9999:9999 $(pwd)/ts3ab 31 | !IMPORTANT: Edit docker-compose and change JWT_SECRET 32 | $ docker-compose up -d 33 | ``` 34 | 35 | Create admin user 36 | 37 | ```sh 38 | $ wget https://gist.githubusercontent.com/elipeF/192e10d114696c6771b29466169cefd5/raw/64b960776c78a11aa30304ad71aa554d73429790/addadmin.sh 39 | $ chmod +x addadmin.sh 40 | !IMPORTANT: Default port 80, if you have changed, also change below 41 | $ ./addadmin.sh 80 PASS_HERE 42 | ``` 43 | 44 | ### Upgrade from v1 45 | 46 | Example docker-compose: https://gist.githubusercontent.com/elipeF/b54b70c36c023e76ccc14c060b0f680c/raw/4ca8561c3eca881397aed1e772fdb60f661e5f94/docker-compose.yml 47 | 48 | ```sh 49 | $ cd abdash 50 | $ docker-compose down 51 | $ rm docker-compose.yml 52 | $ wget https://gist.githubusercontent.com/elipeF/b54b70c36c023e76ccc14c060b0f680c/raw/4ca8561c3eca881397aed1e772fdb60f661e5f94/docker-compose.yml 53 | !IMPORTANT: Edit docker-compose and change JWT_SECRET 54 | $ docker-compose pull 55 | $ docker-compose up -d 56 | ``` 57 | 58 | 59 | ### Screenshots 60 | 61 | ![Dashboard](https://i.imgur.com/qRQufwL.png) 62 | ![Botsettings](https://i.imgur.com/Iuk1HbI.png) 63 | ![Botcreate](https://i.imgur.com/8xObKLQ.png) 64 | ![Usercreate](https://i.imgur.com/b0LjaLc.png) 65 | 66 | ## License 67 | 68 | MIT 69 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { useBabelRc, override } = require("customize-cra"); 2 | 3 | module.exports = override(useBabelRc()); 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | } 5 | } -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | # auto detects a good number of processes to run 2 | worker_processes auto; 3 | 4 | #Provides the configuration file context in which the directives that affect connection processing are specified. 5 | events { 6 | # Sets the maximum number of simultaneous connections that can be opened by a worker process. 7 | worker_connections 8000; 8 | # Tells the worker to accept multiple connections at a time 9 | multi_accept on; 10 | } 11 | 12 | 13 | http { 14 | # what times to include 15 | include /etc/nginx/mime.types; 16 | # what is the default one 17 | default_type application/octet-stream; 18 | 19 | # Sets the path, format, and configuration for a buffered log write 20 | log_format compression '$remote_addr - $remote_user [$time_local] ' 21 | '"$request" $status $upstream_addr ' 22 | '"$http_referer" "$http_user_agent"'; 23 | 24 | server { 25 | # listen on port 80 26 | listen 80; 27 | # save logs here 28 | access_log /var/log/nginx/access.log compression; 29 | 30 | # where the root here 31 | root /var/www; 32 | # what file to server as index 33 | index index.html index.htm; 34 | 35 | location / { 36 | # First attempt to serve request as file, then 37 | # as directory, then fall back to redirecting to index.html 38 | try_files $uri $uri/ /index.html; 39 | } 40 | 41 | location /api/ { 42 | proxy_pass http://api/; 43 | } 44 | 45 | # Media: images, icons, video, audio, HTC 46 | location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ { 47 | expires 1M; 48 | access_log off; 49 | add_header Cache-Control "public"; 50 | } 51 | 52 | # Javascript and CSS files 53 | location ~* \.(?:css|js)$ { 54 | try_files $uri =404; 55 | expires 1y; 56 | access_log off; 57 | add_header Cache-Control "public"; 58 | } 59 | 60 | # Any route containing a file extension (e.g. /devicesfile.js) 61 | location ~ ^.+\..+$ { 62 | try_files $uri =404; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matx-react", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@date-io/core": "^1.3.13", 7 | "@date-io/date-fns": "^1.3.6", 8 | "@material-ui/codemod": "^4.5.0", 9 | "@material-ui/core": "^4.3.1", 10 | "@material-ui/icons": "^4.2.1", 11 | "@material-ui/lab": "^4.0.0-alpha.22", 12 | "@material-ui/pickers": "^3.2.2", 13 | "autosuggest-highlight": "^3.1.1", 14 | "axios": "^0.19.0", 15 | "babel-polyfill": "^6.26.0", 16 | "clsx": "^1.0.4", 17 | "cross-fetch": "^3.0.4", 18 | "css-vars-ponyfill": "^2.1.2", 19 | "date-fns": "^2.0.0-alpha.34", 20 | "downshift": "^3.2.10", 21 | "globalize": "^0.1.1", 22 | "history": "^4.10.0", 23 | "i18next": "^19.7.0", 24 | "i18next-browser-languagedetector": "^6.0.1", 25 | "i18next-http-backend": "^1.0.20", 26 | "lodash": "^4.17.15", 27 | "material-table": "^1.69.0", 28 | "node-sass": "^4.13.1", 29 | "notistack": "^0.8.8", 30 | "prop-types": "^15.7.2", 31 | "react": "^16.8.6", 32 | "react-autosuggest": "^9.4.3", 33 | "react-beautiful-dnd": "^11.0.4", 34 | "react-dom": "^16.8.6", 35 | "react-highlight": "^0.12.0", 36 | "react-html-parser": "^2.0.2", 37 | "react-i18next": "^11.7.2", 38 | "react-infinite-scroller": "^1.2.4", 39 | "react-loadable": "^5.5.0", 40 | "react-material-ui-form-validator": "^2.0.9", 41 | "react-perfect-scrollbar": "^1.5.2", 42 | "react-redux": "^7.0.3", 43 | "react-router-config": "^5.0.1", 44 | "react-router-dom": "^5.0.0", 45 | "react-scripts": "^3.3.1", 46 | "react-select": "^3.0.4", 47 | "react-vis": "^1.11.7", 48 | "redux": "^4.0.1", 49 | "redux-thunk": "^2.3.0" 50 | }, 51 | "scripts": { 52 | "start": "react-app-rewired start", 53 | "build": "NODE_ENV=production GENERATE_SOURCEMAP=false react-scripts build", 54 | "test": "react-scripts test", 55 | "eject": "react-scripts eject", 56 | "ghp": "react-scripts build && gh-pages -d build" 57 | }, 58 | "eslintConfig": { 59 | "extends": "react-app" 60 | }, 61 | "browserslist": [ 62 | ">0.2%", 63 | "not dead", 64 | "not ie <= 11", 65 | "not op_mini all" 66 | ], 67 | "devDependencies": { 68 | "@babel/runtime": "^7.5.5", 69 | "axios-mock-adapter": "^1.17.0", 70 | "babel-plugin-import": "^1.13.0", 71 | "cross-env": "^5.2.0", 72 | "customize-cra": "^0.9.1", 73 | "eslint-plugin-react-hooks": "0.0.0-8d7535e54", 74 | "gh-pages": "^2.1.1", 75 | "react-app-rewired": "^2.1.5" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /public/assets/images/logo-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/069dd3c1d72391db25847392eba49a08ea574f6d/public/assets/images/logo-circle.png -------------------------------------------------------------------------------- /public/assets/images/logo-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/069dd3c1d72391db25847392eba49a08ea574f6d/public/assets/images/logo.png -------------------------------------------------------------------------------- /public/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 | -------------------------------------------------------------------------------- /public/assets/images/sidebar/sidebar-bg-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/069dd3c1d72391db25847392eba49a08ea574f6d/public/assets/images/sidebar/sidebar-bg-dark.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/069dd3c1d72391db25847392eba49a08ea574f6d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 21 | 25 | 26 | 35 | TS3AudioBot Control Panel 36 | 37 | 38 | 89 | 90 | 91 | 92 | 93 |
94 |
95 | 96 |
97 |
98 |
99 |
100 |
101 |
102 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 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/_index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | font-size: 16px; 9 | overflow: hidden; 10 | position: relative; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 15 | monospace; 16 | color: rgba(255, 0, 221, 0.863); 17 | font-size: 16px; 18 | // color: rgba(255, 162, 0, 0.842); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/App.jsx: -------------------------------------------------------------------------------- 1 | import "../styles/_app.scss"; 2 | /* eslint-disable react-hooks/exhaustive-deps */ 3 | import React, { Suspense } from "react"; 4 | import { Provider } from "react-redux"; 5 | import { Router } from "react-router-dom"; 6 | import MatxTheme from "./MatxLayout/MatxTheme/MatxTheme"; 7 | import AppContext from "./appContext"; 8 | import history from "history.js"; 9 | 10 | import routes from "./RootRoutes"; 11 | import { Store } from "./redux/Store"; 12 | import Auth from "./auth/Auth"; 13 | import MatxLayout from "./MatxLayout/MatxLayoutSFC"; 14 | import AuthGuard from "./auth/AuthGuard"; 15 | import { SnackbarProvider } from "notistack"; 16 | import NotificationDisplay from "./NotificationDisplay"; 17 | import { MatxSuspense } from "matx"; 18 | 19 | const App = () => { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /src/app/MatxLayout/Layout1/Layout1.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { setLayoutSettings } from "app/redux/actions/LayoutActions"; 5 | import { withStyles, ThemeProvider } from "@material-ui/core/styles"; 6 | import Scrollbar from "react-perfect-scrollbar"; 7 | import { classList } from "utils"; 8 | import { renderRoutes } from "react-router-config"; 9 | import Layout1Topbar from "./Layout1Topbar"; 10 | import Layout1Sidenav from "./Layout1Sidenav"; 11 | import Footer from "../SharedCompoents/Footer"; 12 | import SecondarySidebar from "../SharedCompoents/SecondarySidebar/SecondarySidebar"; 13 | import AppContext from "app/appContext"; 14 | import { MatxSuspense } from "matx"; 15 | 16 | const styles = theme => { 17 | return { 18 | layout: { 19 | backgroundColor: theme.palette.background.default 20 | } 21 | }; 22 | }; 23 | 24 | const Layout1 = props => { 25 | const { routes } = useContext(AppContext); 26 | let { settings, classes, theme } = props; 27 | let { layout1Settings } = settings; 28 | const topbarTheme = settings.themes[layout1Settings.topbar.theme]; 29 | let layoutClasses = { 30 | [classes.layout]: true, 31 | [`${settings.activeLayout} sidenav-${layout1Settings.leftSidebar.mode} theme-${theme.palette.type} flex`]: true, 32 | "topbar-fixed": layout1Settings.topbar.fixed 33 | }; 34 | 35 | return ( 36 |
37 | {layout1Settings.leftSidebar.show && } 38 | 39 |
40 | {layout1Settings.topbar.show && layout1Settings.topbar.fixed && ( 41 | 42 | 43 | 44 | )} 45 | 46 | {settings.perfectScrollbar && ( 47 | 48 | {layout1Settings.topbar.show && !layout1Settings.topbar.fixed && ( 49 | 50 | 51 | 52 | )} 53 |
54 | {renderRoutes(routes)} 55 |
56 |
57 | {settings.footer.show && !settings.footer.fixed &&
} 58 | 59 | )} 60 | 61 | {!settings.perfectScrollbar && ( 62 |
63 | {layout1Settings.topbar.show && !layout1Settings.topbar.fixed && ( 64 | 65 | )} 66 |
67 | {renderRoutes(routes)} 68 |
69 |
70 | {settings.footer.show && !settings.footer.fixed &&
} 71 |
72 | )} 73 | 74 | {settings.footer.show && settings.footer.fixed &&
} 75 |
76 | {settings.secondarySidebar.show && } 77 |
78 | ); 79 | }; 80 | 81 | Layout1.propTypes = { 82 | settings: PropTypes.object.isRequired 83 | }; 84 | 85 | const mapStateToProps = state => ({ 86 | setLayoutSettings: PropTypes.func.isRequired, 87 | settings: state.layout.settings, 88 | }); 89 | 90 | export default withStyles(styles, { withTheme: true })( 91 | connect(mapStateToProps, { setLayoutSettings })(Layout1) 92 | ); 93 | -------------------------------------------------------------------------------- /src/app/MatxLayout/Layout1/Layout1Settings.js: -------------------------------------------------------------------------------- 1 | const Layout1Settings = { 2 | leftSidebar: { 3 | show: true, 4 | mode: 'full', // full, close, compact, mobile, 5 | theme: 'slateDark1', // View all valid theme colors inside MatxTheme/themeColors.js 6 | // bgOpacity: .96, // 0 ~ 1 7 | bgImgURL: '/assets/images/sidebar/sidebar-bg-dark.jpg' 8 | }, 9 | topbar: { 10 | show: true, 11 | fixed: true, 12 | theme: 'purpleDark1' // View all valid theme colors inside MatxTheme/themeColors.js 13 | } 14 | } 15 | 16 | export default Layout1Settings; -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxLayout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { MatxLayouts } from "./index"; 3 | import PropTypes from "prop-types"; 4 | import { withRouter } from "react-router-dom"; 5 | import { matchRoutes } from "react-router-config"; 6 | import { connect } from "react-redux"; 7 | import AppContext from "app/appContext"; 8 | import { 9 | setLayoutSettings, 10 | setDefaultSettings 11 | } from "app/redux/actions/LayoutActions"; 12 | import { isEqual, merge } from "lodash"; 13 | import { isMdScreen, getQueryParam } from "utils"; 14 | import { MatxSuspense } from "matx"; 15 | 16 | class MatxLayout extends Component { 17 | constructor(props, context) { 18 | super(props); 19 | this.appContext = context; 20 | this.updateSettingsFromRouter(); 21 | 22 | // Set settings from query (Only for demo purpose) 23 | this.setLayoutFromQuery(); 24 | } 25 | 26 | componentDidUpdate(prevProps) { 27 | if (this.props.location.pathname !== prevProps.location.pathname) { 28 | this.updateSettingsFromRouter(); 29 | } 30 | } 31 | 32 | componentDidMount() { 33 | if (window) { 34 | // LISTEN WINDOW RESIZE 35 | window.addEventListener("resize", this.listenWindowResize); 36 | } 37 | } 38 | 39 | componentWillUnmount() { 40 | if (window) { 41 | window.removeEventListener("resize", this.listenWindowResize); 42 | } 43 | } 44 | 45 | setLayoutFromQuery = () => { 46 | try { 47 | let settingsFromQuery = getQueryParam("settings"); 48 | settingsFromQuery = settingsFromQuery 49 | ? JSON.parse(settingsFromQuery) 50 | : {}; 51 | let { settings, setLayoutSettings, setDefaultSettings } = this.props; 52 | let updatedSettings = merge({}, settings, settingsFromQuery); 53 | 54 | setLayoutSettings(updatedSettings); 55 | setDefaultSettings(updatedSettings); 56 | } catch (e) { 57 | // console.log("Error! Set settings from query param", e); 58 | } 59 | }; 60 | 61 | listenWindowResize = () => { 62 | let { settings, setLayoutSettings } = this.props; 63 | 64 | if (settings.layout1Settings.leftSidebar.show) { 65 | let mode = isMdScreen() ? "close" : "full"; 66 | setLayoutSettings( 67 | merge({}, settings, { layout1Settings: { leftSidebar: { mode } } }) 68 | ); 69 | } 70 | }; 71 | 72 | updateSettingsFromRouter() { 73 | const { routes } = this.appContext; 74 | const matched = matchRoutes(routes, this.props.location.pathname)[0]; 75 | let { defaultSettings, settings, setLayoutSettings } = this.props; 76 | 77 | if (matched && matched.route.settings) { 78 | // ROUTE HAS SETTINGS 79 | const updatedSettings = merge({}, settings, matched.route.settings); 80 | if (!isEqual(settings, updatedSettings)) { 81 | setLayoutSettings(updatedSettings); 82 | // console.log('Route has settings'); 83 | } 84 | } else if (!isEqual(settings, defaultSettings)) { 85 | setLayoutSettings(defaultSettings); 86 | // console.log('reset settings', defaultSettings); 87 | } 88 | } 89 | 90 | render() { 91 | const { settings } = this.props; 92 | const Layout = MatxLayouts[settings.activeLayout]; 93 | 94 | return ( 95 | 96 | 97 | 98 | ); 99 | } 100 | } 101 | 102 | const mapStateToProps = state => ({ 103 | setLayoutSettings: PropTypes.func.isRequired, 104 | setDefaultSettings: PropTypes.func.isRequired, 105 | settings: state.layout.settings, 106 | defaultSettings: state.layout.defaultSettings 107 | }); 108 | 109 | MatxLayout.contextType = AppContext; 110 | 111 | export default withRouter( 112 | connect(mapStateToProps, { setLayoutSettings, setDefaultSettings })( 113 | MatxLayout 114 | ) 115 | ); 116 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxLayoutSFC.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from "react"; 2 | import { MatxLayouts } from "./index"; 3 | import PropTypes from "prop-types"; 4 | import { withRouter } from "react-router-dom"; 5 | import { matchRoutes } from "react-router-config"; 6 | import { connect } from "react-redux"; 7 | import AppContext from "app/appContext"; 8 | import { 9 | setLayoutSettings, 10 | setDefaultSettings, 11 | } from "app/redux/actions/LayoutActions"; 12 | import { isEqual, merge } from "lodash"; 13 | import { isMdScreen } from "utils"; 14 | import { MatxSuspense } from "matx"; 15 | 16 | let tempSettings; 17 | 18 | const MatxLayoutSFC = (props) => { 19 | let appContext = useContext(AppContext); 20 | const { settings, defaultSettings, setLayoutSettings } = props; 21 | 22 | tempSettings = settings; 23 | 24 | useEffect(() => { 25 | const listenWindowResize = () => { 26 | let settings = tempSettings; 27 | if (settings.layout1Settings.leftSidebar.show) { 28 | let mode = isMdScreen() ? "close" : "full"; 29 | setLayoutSettings( 30 | merge({}, settings, { layout1Settings: { leftSidebar: { mode } } }) 31 | ); 32 | } 33 | }; 34 | listenWindowResize(); 35 | if (window) { 36 | // LISTEN WINDOW RESIZE 37 | window.addEventListener("resize", listenWindowResize); 38 | } 39 | return () => { 40 | if (window) { 41 | window.removeEventListener("resize", listenWindowResize); 42 | } 43 | }; 44 | }, [setLayoutSettings]); 45 | 46 | useEffect(() => { 47 | const updateSettingsFromRouter = () => { 48 | const { routes } = appContext; 49 | const matched = matchRoutes(routes, props.location.pathname)[0]; 50 | 51 | if (matched && matched.route.settings) { 52 | // ROUTE HAS SETTINGS 53 | const updatedSettings = merge({}, tempSettings, matched.route.settings); 54 | if (!isEqual(tempSettings, updatedSettings)) { 55 | setLayoutSettings(updatedSettings); 56 | // console.log('Route has settings'); 57 | } 58 | } else if (!isEqual(tempSettings, defaultSettings)) { 59 | setLayoutSettings(defaultSettings); 60 | // console.log('reset settings', defaultSettings); 61 | } 62 | }; 63 | updateSettingsFromRouter(); 64 | }, [props.location, appContext, defaultSettings, setLayoutSettings]); 65 | 66 | const Layout = MatxLayouts[settings.activeLayout]; 67 | 68 | return ( 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | const mapStateToProps = (state) => ({ 76 | setLayoutSettings: PropTypes.func.isRequired, 77 | setDefaultSettings: PropTypes.func.isRequired, 78 | settings: state.layout.settings, 79 | defaultSettings: state.layout.defaultSettings, 80 | }); 81 | 82 | export default withRouter( 83 | connect(mapStateToProps, { setLayoutSettings, setDefaultSettings })( 84 | MatxLayoutSFC 85 | ) 86 | ); 87 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/EchartTheme.jsx: -------------------------------------------------------------------------------- 1 | import echarts from "echarts"; 2 | 3 | export const EchartTheme = { 4 | backgroundColor: "#ffffff", 5 | // Global palette: 6 | color: [ 7 | "#7467EF", 8 | "#ABA4F4", 9 | "#D3D0F4", 10 | "rgba(0, 255, 33, 1)", 11 | "#91c7ae", 12 | "#749f83", 13 | "#ca8622", 14 | "#bda29a", 15 | "#6e7074", 16 | "#546570", 17 | "#c4ccd3" 18 | ], 19 | series: [ 20 | { 21 | type: "bar" 22 | // A palette only work for the series: 23 | // itemStyle: { 24 | // normal: { 25 | // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 26 | // { offset: 0, color: "#83bff6" }, 27 | // { offset: 0.5, color: "#188df0" }, 28 | // { offset: 1, color: "#188df0" } 29 | // ]) 30 | // }, 31 | // emphasis: { 32 | // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 33 | // { offset: 0, color: "#2378f7" }, 34 | // { offset: 0.7, color: "#2378f7" }, 35 | // { offset: 1, color: "#83bff6" } 36 | // ]) 37 | // } 38 | // } 39 | }, 40 | { 41 | type: "pie", 42 | // A palette only work for the series: 43 | color: [ 44 | "#37A2DA", 45 | "#32C5E9", 46 | "#67E0E3", 47 | "#9FE6B8", 48 | "#FFDB5C", 49 | "#ff9f7f", 50 | "#fb7293", 51 | "#E062AE", 52 | "#E690D1", 53 | "#e7bcf3", 54 | "#9d96f5", 55 | "#8378EA", 56 | "#96BFFF" 57 | ] 58 | }, 59 | { 60 | type: "line", 61 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 62 | { offset: 0, color: "#83bff6" }, 63 | { offset: 0.5, color: "#188df0" }, 64 | { offset: 1, color: "#188df0" } 65 | ]) 66 | } 67 | ] 68 | }; 69 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/MatxCssVars.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "@material-ui/styles"; 3 | 4 | const generateFontProperty = fontObject => { 5 | return `${fontObject.fontWeight} ${fontObject.fontSize}/${fontObject.lineHeight} ${fontObject.fontFamily}`; 6 | }; 7 | 8 | const generateShadowVars = theme => { 9 | return theme.shadows.reduce(function(result, item, index, array) { 10 | result[`--elevation-z${index}`] = item; 11 | return result; 12 | }, {}); 13 | }; 14 | 15 | const styles = theme => ({ 16 | "@global": { 17 | ":root": { 18 | ...{ 19 | "--primary": theme.palette.primary.main, 20 | "--secondary": theme.palette.secondary.main, 21 | "--error": theme.palette.error.main, 22 | "--bg-default": theme.palette.background.default, 23 | "--bg-paper": theme.palette.background.paper, 24 | "--text-body": theme.palette.text.primary, 25 | "--text-muted": theme.palette.text.secondary, 26 | "--text-disabled": theme.palette.text.disabled, 27 | "--text-hint": theme.palette.text.hint, 28 | "--font": theme.typography.fontFamily, 29 | "--font-caption": generateFontProperty(theme.typography.caption), 30 | "--font-h1": generateFontProperty(theme.typography.h1), 31 | "--font-h2": generateFontProperty(theme.typography.h2), 32 | "--font-h3": generateFontProperty(theme.typography.h3), 33 | "--font-h4": generateFontProperty(theme.typography.h4), 34 | "--font-h5": generateFontProperty(theme.typography.h5), 35 | "--font-h6": generateFontProperty(theme.typography.h6), 36 | "--font-overline": generateFontProperty(theme.typography.overline), 37 | "--font-body-1": generateFontProperty(theme.typography.body1), 38 | "--font-body-2": generateFontProperty(theme.typography.body2), 39 | "--font-subtitle-1": generateFontProperty(theme.typography.subtitle1), 40 | "--font-subtitle-2": generateFontProperty(theme.typography.subtitle2), 41 | "--font-button": generateFontProperty(theme.typography.button), 42 | "--font-headline": "400 24px/32px var(--font)", 43 | "--font-title": "500 18px/26px var(--font)", 44 | "--font-display-1": "400 34px/40px var(--font)", 45 | "--font-display-2": "400 45px/48px var(--font)", 46 | "--font-display-3": "400 56px/56px var(--font)", 47 | "--font-display-4": "300 112px/112px var(--font)" 48 | }, 49 | ...generateShadowVars(theme) 50 | } 51 | } 52 | }); 53 | 54 | const MatxCssVars = ({ children }) => { 55 | return {children}; 56 | }; 57 | 58 | export default withStyles(styles, { withTheme: true })(MatxCssVars); 59 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/MatxTheme.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ThemeProvider } from "@material-ui/core/styles"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | import { setLayoutSettings } from "app/redux/actions/LayoutActions"; 6 | import CssBaseline from "@material-ui/core/CssBaseline"; 7 | import MatxCssVars from "./MatxCssVars"; 8 | 9 | // import cssVars from "css-vars-ponyfill"; 10 | 11 | const MatxTheme = ({ children, settings }) => { 12 | let activeTheme = { ...settings.themes[settings.activeTheme] }; 13 | // console.log(activeTheme); 14 | // cssVars(); 15 | // activeTheme.direction = settings.direction; 16 | return ( 17 | 18 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | MatxTheme.propTypes = { 25 | setLayoutSettings: PropTypes.func.isRequired, 26 | settings: PropTypes.object.isRequired 27 | }; 28 | 29 | const mapStateToProps = state => ({ 30 | settings: state.layout.settings, 31 | setLayoutSettings: PropTypes.func.isRequired 32 | }); 33 | 34 | export default connect(mapStateToProps, { setLayoutSettings })(MatxTheme); 35 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/SecondarySidenavTheme/SecondarySidenavTheme.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ThemeProvider } from "@material-ui/core/styles"; 3 | 4 | const SecondarySidenavTheme = ({ theme, classes, children, open }) => { 5 | return ( 6 | 7 | {children} 8 | 9 | ); 10 | }; 11 | export default SecondarySidenavTheme 12 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/SidenavTheme.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Helmet } from "react-helmet"; 3 | 4 | const SidenavTheme = ({ theme, settings }) => { 5 | 6 | function darkHoverStyle() { 7 | return theme.palette.type === "dark" 8 | ? `.navigation .nav-item:hover, 9 | .navigation .nav-item.active { 10 | color: ${theme.palette.text.primary}; 11 | }` 12 | : ""; 13 | } 14 | 15 | function lightHoverStyle() { 16 | return theme.palette.type === "light" 17 | ? `.navigation .nav-item:hover, 18 | .navigation .nav-item.active, 19 | .navigation .submenu { 20 | background: rgba(0, 0, 0, .08); 21 | }` 22 | : ""; 23 | } 24 | 25 | return ( 26 | 27 | 64 | 65 | ); 66 | }; 67 | 68 | export default SidenavTheme; 69 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/SidenavTheme/SidenavTheme.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ThemeProvider } from "@material-ui/core/styles"; 3 | import SidenavThemeStyles from "./SidenavThemeStyles"; 4 | 5 | const SidenavTheme = ({ theme, settings, children }) => { 6 | return ( 7 | 8 | 9 | {children} 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default SidenavTheme; 16 | -------------------------------------------------------------------------------- /src/app/MatxLayout/MatxTheme/SidenavTheme/SidenavThemeStyles.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles } from "@material-ui/core/styles" 3 | 4 | const styles = theme => ({ 5 | root: { 6 | backgroundColor: theme.palette.background.default, 7 | color: theme.palette.text.primary, 8 | "& .sidenav": { 9 | "& .sidenav__hold": { 10 | opacity: "1 !important", 11 | "&::after": { 12 | background: theme.palette.primary.main, 13 | opacity: .96 14 | }, 15 | "& .nav-item:not(.badge)": { 16 | color: theme.palette.text.primary 17 | }, 18 | "& .nav-item": { 19 | "&.active, &.active:hover": { 20 | background: theme.palette.secondary.main 21 | }, 22 | "& .icon-text::after": { 23 | background: theme.palette.text.primary 24 | } 25 | } 26 | } 27 | } 28 | } 29 | }); 30 | 31 | const SidenavThemeStyles = ({ children, classes }) => { 32 | return
{children}
; 33 | }; 34 | 35 | export default withStyles(styles, { withTheme: true })(SidenavThemeStyles); 36 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/Brand.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Brand = ({ children }) => { 4 | return ( 5 |
6 |
7 | company-logo 8 |
9 | TS3AudioBot 10 | Control Panel 11 |
12 |
13 | {children} 14 |
15 | ); 16 | }; 17 | 18 | export default Brand; 19 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withStyles, ThemeProvider } from "@material-ui/core/styles"; 3 | import { Button, Toolbar, AppBar } from "@material-ui/core"; 4 | import PropTypes from "prop-types"; 5 | import { connect } from "react-redux"; 6 | 7 | const Footer = ({ theme, settings }) => { 8 | const footerTheme = settings.themes[settings.footer.theme] || theme; 9 | return ( 10 | 11 | 12 | 13 |
14 | 18 | 19 | 20 | 21 | 24 | 25 | 26 |

27 | Design and Developed by UI Lib 28 |

29 |
30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | Footer.propTypes = { 37 | settings: PropTypes.object.isRequired 38 | }; 39 | 40 | const mapStateToProps = state => ({ 41 | settings: state.layout.settings 42 | }); 43 | 44 | export default withStyles( 45 | {}, 46 | { withTheme: true } 47 | )(connect(mapStateToProps, {})(Footer)); 48 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/MatxCustomizer/BadgeSelected.jsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@material-ui/core"; 2 | import { withStyles } from "@material-ui/core/styles" 3 | 4 | const BadgeSelected = withStyles(theme => ({ 5 | badge: { 6 | top: "0", 7 | right: "0", 8 | height: "32px", 9 | width: "32px", 10 | borderRadius: "50%" 11 | } 12 | }))(Badge); 13 | 14 | export default BadgeSelected; 15 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/MatxCustomizer/Layout2Customizer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { 3 | Tooltip, 4 | Radio, 5 | RadioGroup, 6 | Icon, 7 | FormControlLabel, 8 | FormControl, 9 | FormLabel 10 | } from "@material-ui/core"; 11 | import { themeColors } from "../../MatxTheme/themeColors"; 12 | 13 | const Layout2Customizer = ({ settings, handleChange, handleControlChange }) => { 14 | return ( 15 | 16 |
17 |
Topbar theme
18 |
19 | {Object.keys(themeColors).map((color, i) => ( 20 | 21 |
24 | handleChange("layout2Settings.topbar.theme", color) 25 | } 26 | style={{ 27 | backgroundColor: themeColors[color].palette.primary.main 28 | }} 29 | > 30 | {settings.layout2Settings.topbar.theme === color && ( 31 | done 32 | )} 33 |
34 |
35 |
36 | ))} 37 |
38 |
39 | 40 |
41 |
Navbar theme
42 |
43 | {Object.keys(themeColors).map((color, i) => ( 44 | 45 |
48 | handleChange("layout2Settings.navbar.theme", color) 49 | } 50 | style={{ 51 | backgroundColor: themeColors[color].palette.primary.main 52 | }} 53 | > 54 | {settings.layout2Settings.navbar.theme === color && ( 55 | done 56 | )} 57 |
58 |
59 |
60 | ))} 61 |
62 |
63 | 64 |
65 | 66 | Layout mode 67 | 73 | } label="Full" /> 74 | } 77 | label="Contained" 78 | /> 79 | } label="Boxed" /> 80 | 81 | 82 |
83 |
84 | ); 85 | }; 86 | 87 | export default Layout2Customizer; 88 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/MatxCustomizer/customizerOptions.js: -------------------------------------------------------------------------------- 1 | 2 | export const mainThemes = ['purple1', 'purple2', 'blue', 'purpleDark1', 'purpleDark2', 'blueDark']; 3 | 4 | export const mainSidebarThemes = ['white', 'slateDark1', 'slateDark2', 'purpleDark1', 'purpleDark2', 'blueDark']; 5 | 6 | export const topbarThemes = ['white', 'slateDark1', 'slateDark2', 'purpleDark1', 'purpleDark2', 'blueDark']; -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/SecondarySidebar/SecondarySidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import SecondarySidebarToggle from "./SecondarySidebarToggle"; 4 | import SecondarySidebarContent from "./SecondarySidebarContent"; 5 | import SecondarySidenavTheme from "../../MatxTheme/SecondarySidenavTheme/SecondarySidenavTheme"; 6 | 7 | const SecondarySidebar = ({ settings }) => { 8 | const secondarySidebarTheme = 9 | settings.themes[settings.secondarySidebar.theme]; 10 | 11 | return ( 12 | 13 | {settings.secondarySidebar.open && ( 14 | 15 | )} 16 | 17 | 18 | ); 19 | }; 20 | 21 | const mapStateToProps = state => ({ 22 | settings: state.layout.settings 23 | }); 24 | 25 | export default connect(mapStateToProps, {})(SecondarySidebar); 26 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/SecondarySidebar/SecondarySidebarContent.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { IconButton, Icon } from "@material-ui/core"; 3 | import { withStyles } from "@material-ui/core/styles"; 4 | import { withRouter, Link } from "react-router-dom"; 5 | import { connect } from "react-redux"; 6 | import { classList } from "utils"; 7 | import MatxCustomizer from "../MatxCustomizer/MatxCustomizer"; 8 | const width = "50px"; 9 | 10 | const styles = (theme) => ({ 11 | root: { 12 | position: "fixed", 13 | height: "100vh", 14 | width, 15 | right: 0, 16 | bottom: 0, 17 | display: "flex", 18 | flexDirection: "column", 19 | alignItems: "center", 20 | justifyContent: "center", 21 | boxShadow: theme.shadows[8], 22 | backgroundColor: theme.palette.primary.main, 23 | zIndex: 98, 24 | transition: "all 0.15s ease", 25 | }, 26 | "@global": { 27 | "@media screen and (min-width: 767px)": { 28 | ".content-wrap, .layout2.layout-contained, .layout2.layout-full": { 29 | marginRight: width, 30 | }, 31 | ".matx-customizer": { 32 | right: width, 33 | }, 34 | }, 35 | "@media screen and (max-width: 959px)": { 36 | ".toolbar-menu-wrap .menu-area": { 37 | width: `calc(100% - ${width})`, 38 | }, 39 | }, 40 | }, 41 | }); 42 | 43 | class SecondarySidebarContent extends Component { 44 | render() { 45 | let { classes } = this.props; 46 | 47 | return ( 48 |
57 | 58 | 59 | 60 | 61 | {/* */} 62 | 63 | 64 | 65 | comments 66 | 67 | 68 | 69 | 70 |
71 | ); 72 | } 73 | } 74 | 75 | const mapStateToProps = (state) => ({ 76 | settings: state.layout.settings, 77 | }); 78 | 79 | export default withStyles(styles, { withTheme: true })( 80 | withRouter(connect(mapStateToProps, {})(SecondarySidebarContent)) 81 | ); 82 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/SecondarySidebar/SecondarySidebarToggle.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useEffect } from "react"; 3 | import { connect } from "react-redux"; 4 | import { setLayoutSettings } from "app/redux/actions/LayoutActions"; 5 | import { Fab, IconButton, Icon, useMediaQuery } from "@material-ui/core"; 6 | import { withStyles } from "@material-ui/core/styles"; 7 | import { merge } from "lodash"; 8 | import PropTypes from "prop-types"; 9 | import { classList } from "utils"; 10 | 11 | const styles = theme => ({ 12 | toggle: { 13 | position: "fixed", 14 | right: "-30px", 15 | bottom: "20px", 16 | zIndex: 9999, 17 | transition: "all 0.15s ease", 18 | "&.open": { 19 | right: "10px" 20 | } 21 | } 22 | }); 23 | 24 | const SecondarySidebarToggle = ({ classes, settings, setLayoutSettings }) => { 25 | let isMobile = useMediaQuery("(max-width:767px)"); 26 | 27 | const toggle = () => { 28 | setLayoutSettings( 29 | merge({}, settings, { 30 | secondarySidebar: { open: !settings.secondarySidebar.open } 31 | }) 32 | ); 33 | }; 34 | 35 | useEffect(() => { 36 | setLayoutSettings( 37 | merge({}, settings, { 38 | secondarySidebar: { open: !isMobile } 39 | }) 40 | ); 41 | }, [isMobile, setLayoutSettings]); 42 | 43 | return ( 44 |
53 | {settings.secondarySidebar.open && ( 54 | 55 | arrow_right 56 | 57 | )} 58 | {!settings.secondarySidebar.open && ( 59 | 67 | arrow_left 68 | 69 | )} 70 |
71 | ); 72 | }; 73 | 74 | const mapStateToProps = state => ({ 75 | settings: state.layout.settings, 76 | setLayoutSettings: PropTypes.func.isRequired 77 | }); 78 | 79 | export default withStyles(styles, { withTheme: true })( 80 | connect(mapStateToProps, { setLayoutSettings })(SecondarySidebarToggle) 81 | ); 82 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/Sidenav.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import Scrollbar from "react-perfect-scrollbar"; 3 | import { withRouter } from "react-router-dom"; 4 | import { connect } from "react-redux"; 5 | import PropTypes from "prop-types"; 6 | 7 | import { navigations } from "../../navigations"; 8 | import { MatxVerticalNav } from "matx"; 9 | import { setLayoutSettings } from "app/redux/actions/LayoutActions"; 10 | 11 | const Sidenav = props => { 12 | const updateSidebarMode = sidebarSettings => { 13 | let { settings, setLayoutSettings } = props; 14 | let activeLayoutSettingsName = settings.activeLayout + "Settings"; 15 | let activeLayoutSettings = settings[activeLayoutSettingsName]; 16 | 17 | setLayoutSettings({ 18 | ...settings, 19 | [activeLayoutSettingsName]: { 20 | ...activeLayoutSettings, 21 | leftSidebar: { 22 | ...activeLayoutSettings.leftSidebar, 23 | ...sidebarSettings 24 | } 25 | } 26 | }); 27 | }; 28 | 29 | const renderOverlay = () => ( 30 |
updateSidebarMode({ mode: "close" })} 32 | className="sidenav__overlay" 33 | /> 34 | ); 35 | 36 | return ( 37 | 38 | 42 | {props.children} 43 | 44 | 45 | {renderOverlay()} 46 | 47 | ); 48 | }; 49 | 50 | Sidenav.propTypes = { 51 | setLayoutSettings: PropTypes.func.isRequired, 52 | settings: PropTypes.object.isRequired 53 | }; 54 | 55 | const mapStateToProps = state => ({ 56 | setLayoutSettings: PropTypes.func.isRequired, 57 | settings: state.layout.settings 58 | }); 59 | 60 | export default withRouter( 61 | connect(mapStateToProps, { 62 | setLayoutSettings 63 | })(Sidenav) 64 | ); 65 | -------------------------------------------------------------------------------- /src/app/MatxLayout/SharedCompoents/TopbarMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Icon, IconButton, Hidden } from "@material-ui/core"; 3 | import { classList } from "Utils"; 4 | 5 | const TopbarMenu = props => { 6 | let { offsetTop } = props; 7 | const [open, setOpen] = useState(false); 8 | 9 | const handleToggle = () => { 10 | setOpen(!open); 11 | }; 12 | 13 | return ( 14 |
20 | 21 | 22 | {open ? "close" : "more_vert"} 23 | 24 | 25 | 26 |
30 | {props.children} 31 |
32 |
33 | ); 34 | }; 35 | 36 | export default TopbarMenu; 37 | -------------------------------------------------------------------------------- /src/app/MatxLayout/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const MatxLayouts = { 4 | layout1: React.lazy(() => import("./Layout1/Layout1")) 5 | }; 6 | -------------------------------------------------------------------------------- /src/app/MatxLayout/settings.js: -------------------------------------------------------------------------------- 1 | import layout1Settings from "./Layout1/Layout1Settings"; 2 | import { themeColors } from "./MatxTheme/themeColors"; 3 | import { createMuiTheme } from "@material-ui/core/styles"; 4 | import { forEach, merge } from "lodash"; 5 | import themeOptions from "./MatxTheme/themeOptions"; 6 | 7 | function createMatxThemes() { 8 | let themes = {}; 9 | 10 | forEach(themeColors, (value, key) => { 11 | themes[key] = createMuiTheme(merge({}, themeOptions, value)); 12 | }); 13 | return themes; 14 | } 15 | const themes = createMatxThemes(); 16 | 17 | export const MatxLayoutSettings = { 18 | activeLayout: "layout1", // layout1, layout2 19 | activeTheme: "purple1", // View all valid theme colors inside MatxTheme/themeColors.js 20 | perfectScrollbar: true, 21 | 22 | themes: themes, 23 | layout1Settings, // open Layout1/Layout1Settings.js 24 | 25 | secondarySidebar: { 26 | show: false, 27 | open: false, 28 | theme: "slateDark1", // View all valid theme colors inside MatxTheme/themeColors.js 29 | }, 30 | // Footer options 31 | footer: { 32 | show: false, 33 | fixed: false, 34 | theme: "slateDark1", // View all valid theme colors inside MatxTheme/themeColors.js 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/app/NotificationDisplay.jsx: -------------------------------------------------------------------------------- 1 | import { withSnackbar } from "notistack"; 2 | import { useEffect } from "react"; 3 | import { useTranslation } from "react-i18next"; 4 | import { connect } from "react-redux"; 5 | import { removeNotification } from "./redux/actions/NotificationsActions"; 6 | 7 | const NotificationDisplay = (props) => { 8 | const { notification, enqueueSnackbar, onRemoveNotification } = props; 9 | const { t } = useTranslation(); 10 | useEffect(() => { 11 | if (notification.length > 0) { 12 | enqueueSnackbar(t("responses." + notification[0].message), { 13 | variant: notification[0].type, 14 | autoHideDuration: 2000, 15 | anchorOrigin: { 16 | vertical: "top", 17 | horizontal: "right", 18 | }, 19 | }); 20 | onRemoveNotification(); 21 | } 22 | }, [notification, enqueueSnackbar, onRemoveNotification, t]); 23 | return null; 24 | }; 25 | 26 | const mapStateToProps = (state) => { 27 | return { 28 | notification: state.notification.messages, 29 | }; 30 | }; 31 | 32 | const mapDispatchToProps = (dispatch) => { 33 | return { 34 | onRemoveNotification: () => dispatch(removeNotification()), 35 | }; 36 | }; 37 | 38 | export default connect( 39 | mapStateToProps, 40 | mapDispatchToProps 41 | )(withSnackbar(NotificationDisplay)); 42 | -------------------------------------------------------------------------------- /src/app/RootRoutes.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect } from "react-router-dom"; 3 | 4 | import dashboardRoutes from "./views/dashboard/DashboardRoutes"; 5 | import botRoutes from "./views/bot/BotRoutes"; 6 | import botCreateRoutes from "./views/botcreate/BotCreateRoutes"; 7 | import usersRoutes from "./views/users/UsersRoutes"; 8 | import sessionRoutes from "./views/sessions/SessionRoutes"; 9 | 10 | const redirectRoute = [ 11 | { 12 | path: "/", 13 | exact: true, 14 | component: () => , 15 | }, 16 | ]; 17 | 18 | const errorRoute = [ 19 | { 20 | component: () => , 21 | }, 22 | ]; 23 | 24 | const routes = [ 25 | ...sessionRoutes, 26 | ...dashboardRoutes, 27 | ...botRoutes, 28 | ...botCreateRoutes, 29 | ...usersRoutes, 30 | ...redirectRoute, 31 | ...errorRoute, 32 | ]; 33 | 34 | export default routes; 35 | -------------------------------------------------------------------------------- /src/app/appContext.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AppContext = React.createContext({}); 4 | 5 | export default AppContext; 6 | -------------------------------------------------------------------------------- /src/app/auth/Auth.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { Fragment, useEffect } from "react"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | import { setUserData } from "../redux/actions/UserActions"; 6 | import { getNavigationByUser } from "../redux/actions/NavigationAction"; 7 | import jwtAuthService from "../services/jwtAuthService"; 8 | import history from "history.js"; 9 | 10 | const checkJwtAuth = async (setUserData) => { 11 | // You need to send token to your server to check token is valid 12 | // modify loginWithToken method in jwtService 13 | let user; 14 | try { 15 | user = await jwtAuthService.loginWithToken(); 16 | } catch (e) {} 17 | if (user) setUserData(user); 18 | else 19 | history.push({ 20 | pathname: "/session/signin", 21 | }); 22 | return user; 23 | }; 24 | 25 | const Auth = ({ children, setUserData, getNavigationByUser, login }) => { 26 | setUserData(JSON.parse(window.localStorage.getItem("auth_user"))); 27 | useEffect(() => { 28 | checkJwtAuth(setUserData); 29 | getNavigationByUser(); 30 | }, [setUserData, getNavigationByUser, login]); 31 | 32 | return {children}; 33 | }; 34 | 35 | const mapStateToProps = (state) => ({ 36 | setUserData: PropTypes.func.isRequired, 37 | getNavigationByUser: PropTypes.func.isRequired, 38 | login: state.login, 39 | }); 40 | 41 | export default connect(mapStateToProps, { setUserData, getNavigationByUser })( 42 | Auth 43 | ); 44 | -------------------------------------------------------------------------------- /src/app/auth/AuthGuard.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState, useEffect, useContext } from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import AppContext from "app/appContext"; 5 | 6 | const redirectRoute = props => { 7 | const { location, history } = props; 8 | const { pathname } = location; 9 | history.push({ 10 | pathname: "/session/signin", 11 | state: { redirectUrl: pathname } 12 | }); 13 | }; 14 | 15 | const getAuthStatus = (props, routes) => { 16 | const { location, user } = props; 17 | const { pathname } = location; 18 | const matched = routes.find(r => r.path === pathname); 19 | const authenticated = 20 | matched && matched.auth && matched.auth.length 21 | ? matched.auth.includes(user.role) 22 | : true; 23 | 24 | return authenticated; 25 | }; 26 | 27 | const AuthGuard = ({ children, ...props }) => { 28 | const { routes } = useContext(AppContext); 29 | 30 | let [authenticated, setAuthenticated] = useState( 31 | getAuthStatus(props, routes) 32 | ); 33 | 34 | useEffect(() => { 35 | if (!authenticated) { 36 | redirectRoute(props); 37 | } 38 | setAuthenticated(getAuthStatus(props, routes)); 39 | }, [setAuthenticated, authenticated, routes, props]); 40 | 41 | return authenticated ? {children} : null; 42 | }; 43 | 44 | const mapStateToProps = state => ({ 45 | user: state.user 46 | }); 47 | 48 | export default withRouter(connect(mapStateToProps)(AuthGuard)); 49 | -------------------------------------------------------------------------------- /src/app/auth/authRoles.js: -------------------------------------------------------------------------------- 1 | export const authRoles = { 2 | sa: ['SA'], // Only Super Admin has access 3 | admin: ['SA', 'admin'], // Only SA & Admin has access 4 | editor: ['SA', 'admin', 'EDITOR'], // Only SA & Admin & Editor has access 5 | guest: ['SA', 'admin', 'EDITOR', 'GUEST'] // Everyone has access 6 | } 7 | 8 | // Check out app/views/dashboard/DashboardRoutes.js 9 | // Only SA & Admin has dashboard access 10 | 11 | // const dashboardRoutes = [ 12 | // { 13 | // path: "/dashboard/analytics", 14 | // component: Analytics, 15 | // auth: authRoles.admin <---------------- 16 | // } 17 | // ]; -------------------------------------------------------------------------------- /src/app/navigations.js: -------------------------------------------------------------------------------- 1 | export const navigations = [ 2 | { 3 | name: "navigation.dashboard", 4 | path: "/dashboard", 5 | icon: "dashboard", 6 | }, 7 | { 8 | name: "navigation.createbot", 9 | path: "/createbot", 10 | icon: "add_box", 11 | auth: "admin", 12 | }, 13 | { 14 | name: "navigation.users", 15 | path: "/users", 16 | icon: "account_box", 17 | auth: "admin", 18 | }, 19 | ]; 20 | -------------------------------------------------------------------------------- /src/app/redux/Store.js: -------------------------------------------------------------------------------- 1 | import thunk from "redux-thunk"; 2 | import { createStore, applyMiddleware, compose } from "redux"; 3 | import RootReducer from "./reducers/RootReducer"; 4 | 5 | const initialState = {}; 6 | const middlewares = [thunk]; 7 | let devtools = x => x; 8 | 9 | if ( 10 | process.env.NODE_ENV !== "production" && 11 | process.browser && 12 | window.__REDUX_DEVTOOLS_EXTENSION__ 13 | ) { 14 | devtools = window.__REDUX_DEVTOOLS_EXTENSION__(); 15 | } 16 | 17 | export const Store = createStore( 18 | RootReducer, 19 | initialState, 20 | compose(applyMiddleware(...middlewares), devtools) 21 | ); 22 | -------------------------------------------------------------------------------- /src/app/redux/actions/BotCreateActions.js: -------------------------------------------------------------------------------- 1 | import instance from "./../../../axios"; 2 | import { newNotification } from "./NotificationsActions"; 3 | 4 | export const CREATE_BOT_START = "CREATE_BOT_START"; 5 | export const CREATE_BOT_SUCCESS = "CREATE_BOT_SUCCESS"; 6 | export const CREATE_BOT_FAIL = "CREATE_BOT_FAIL"; 7 | 8 | export const createBot = (data) => (dispatch) => { 9 | dispatch({ 10 | type: CREATE_BOT_START, 11 | }); 12 | 13 | instance 14 | .post(`/api/bots/`, { ...data }) 15 | .then((res) => { 16 | dispatch({ 17 | type: CREATE_BOT_SUCCESS, 18 | }); 19 | dispatch( 20 | newNotification({ 21 | type: "success", 22 | message: CREATE_BOT_SUCCESS, 23 | }) 24 | ); 25 | }) 26 | .catch((e) => { 27 | dispatch( 28 | newNotification({ 29 | type: "error", 30 | message: e.response?.data.message 31 | ? e.response?.data.message 32 | : e.message, 33 | }) 34 | ); 35 | dispatch({ 36 | type: CREATE_BOT_FAIL, 37 | payload: e, 38 | }); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/app/redux/actions/BotsActions.js: -------------------------------------------------------------------------------- 1 | import instance from "./../../../axios"; 2 | import { newNotification } from "./NotificationsActions"; 3 | 4 | export const GET_BOTS_LIST_START = "GET_BOTS_LIST_START"; 5 | export const GET_BOTS_LIST_SUCCESS = "GET_BOTS_LIST_SUCCESS"; 6 | export const GET_BOTS_LIST_FAIL = "GET_BOTS_LIST_FAIL"; 7 | 8 | export const getBotsList = () => (dispatch) => { 9 | dispatch({ 10 | type: GET_BOTS_LIST_START, 11 | }); 12 | instance 13 | .get(`/api/bots/`) 14 | .then((res) => { 15 | dispatch({ 16 | type: GET_BOTS_LIST_SUCCESS, 17 | payload: res.data, 18 | }); 19 | }) 20 | .catch((e) => { 21 | console.log(e); 22 | dispatch( 23 | newNotification({ 24 | type: "error", 25 | message: e.response?.data.message 26 | ? e.response.data.message 27 | : e.message, 28 | }) 29 | ); 30 | dispatch({ 31 | type: GET_BOTS_LIST_FAIL, 32 | payload: e, 33 | }); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /src/app/redux/actions/LayoutActions.js: -------------------------------------------------------------------------------- 1 | export const SET_LAYOUT_SETTINGS = "LAYOUT_SET_SETTINGS"; 2 | export const SET_DEFAULT_LAYOUT_SETTINGS = "LAYOUT_SET_DEFAULT_SETTINGS"; 3 | 4 | export const setLayoutSettings = data => dispatch => { 5 | dispatch({ 6 | type: SET_LAYOUT_SETTINGS, 7 | data: data 8 | }); 9 | }; 10 | 11 | export const setDefaultSettings = data => dispatch => { 12 | dispatch({ 13 | type: SET_DEFAULT_LAYOUT_SETTINGS, 14 | data: data 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/redux/actions/LoginActions.js: -------------------------------------------------------------------------------- 1 | import jwtAuthService from "../../services/jwtAuthService"; 2 | import { setUserData } from "./UserActions"; 3 | import history from "history.js"; 4 | import { newNotification } from "./NotificationsActions"; 5 | import { INIT_USER_NAVIGATION } from "./NavigationAction"; 6 | 7 | export const LOGIN_ERROR = "LOGIN_ERROR"; 8 | export const LOGIN_SUCCESS = "LOGIN_SUCCESS"; 9 | export const LOGIN_LOADING = "LOGIN_LOADING"; 10 | export const RESET_PASSWORD = "RESET_PASSWORD"; 11 | 12 | export function loginWithUsernameAndPassword({ username, password }) { 13 | return (dispatch) => { 14 | dispatch({ 15 | type: LOGIN_LOADING, 16 | }); 17 | 18 | jwtAuthService 19 | .loginWithUsernameAndPassword(username, password) 20 | .then((user) => { 21 | dispatch(setUserData(user)); 22 | dispatch({ 23 | type: INIT_USER_NAVIGATION, 24 | }); 25 | 26 | history.push({ 27 | pathname: "/", 28 | }); 29 | 30 | return dispatch({ 31 | type: LOGIN_SUCCESS, 32 | }); 33 | }) 34 | .catch((e) => { 35 | dispatch( 36 | newNotification({ 37 | type: "error", 38 | message: e.response?.data.message 39 | ? e.response.data.message 40 | : e.message, 41 | }) 42 | ); 43 | return dispatch({ 44 | type: LOGIN_ERROR, 45 | payload: e, 46 | }); 47 | }); 48 | }; 49 | } 50 | export function resetPassword({ email }) { 51 | return (dispatch) => { 52 | dispatch({ 53 | payload: email, 54 | type: RESET_PASSWORD, 55 | }); 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/app/redux/actions/NavigationAction.js: -------------------------------------------------------------------------------- 1 | export const SET_USER_NAVIGATION = "SET_USER_NAVIGATION"; 2 | export const INIT_USER_NAVIGATION = "INIT_USER_NAVIGATION"; 3 | 4 | const getfilteredNavigations = (navList = [], role) => { 5 | return navList.reduce((array, nav) => { 6 | if (nav.auth) { 7 | if (nav.auth.includes(role)) { 8 | array.push(nav); 9 | } 10 | } else { 11 | if (nav.children) { 12 | nav.children = getfilteredNavigations(nav.children, role); 13 | array.push(nav); 14 | } else { 15 | array.push(nav); 16 | } 17 | } 18 | return array; 19 | }, []); 20 | }; 21 | 22 | export function getNavigationByUser() { 23 | return (dispatch, getState) => { 24 | let { user, navigations = [] } = getState(); 25 | let filteredNavigations = getfilteredNavigations(navigations, user.role); 26 | 27 | dispatch({ 28 | type: SET_USER_NAVIGATION, 29 | payload: [...filteredNavigations], 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/app/redux/actions/NavigationActions.js: -------------------------------------------------------------------------------- 1 | export const SET_NAVIGATION = "SET_NAVIGATION"; 2 | export const GET_NAVIGATION = "GET_NAVIGATION"; 3 | export const RESET_NAVIGATION = "RESET_NAVIGATION"; 4 | 5 | export function getNavigation(user) { 6 | return { 7 | type: GET_NAVIGATION, 8 | }; 9 | } 10 | 11 | export function logoutUser() { 12 | return (dispatch) => { 13 | jwtAuthService.logout(); 14 | 15 | history.push({ 16 | pathname: "/session/signin", 17 | }); 18 | 19 | dispatch({ 20 | type: USER_LOGGED_OUT, 21 | }); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/redux/actions/NotificationsActions.js: -------------------------------------------------------------------------------- 1 | 2 | export const ADD_NOTIFICATION = "ADD_NOTIFICATION"; 3 | export const REMOVE_NOTIFICATION = "REMOVE_NOTIFICATION"; 4 | 5 | 6 | 7 | 8 | export const newNotification = (data) => dispatch => { 9 | dispatch({ 10 | type: ADD_NOTIFICATION, 11 | payload: data 12 | }); 13 | }; 14 | 15 | export const removeNotification = () => dispatch => { 16 | dispatch({ 17 | type: REMOVE_NOTIFICATION, 18 | }); 19 | }; -------------------------------------------------------------------------------- /src/app/redux/actions/UserActions.js: -------------------------------------------------------------------------------- 1 | import history from "history.js"; 2 | import jwtAuthService from "../../services/jwtAuthService"; 3 | 4 | 5 | export const SET_USER_DATA = "USER_SET_DATA"; 6 | export const REMOVE_USER_DATA = "USER_REMOVE_DATA"; 7 | export const USER_LOGGED_OUT = "USER_LOGGED_OUT"; 8 | 9 | export function setUserData(user) { 10 | return (dispatch) => { 11 | dispatch({ 12 | type: SET_USER_DATA, 13 | data: user, 14 | }); 15 | }; 16 | } 17 | 18 | export function logoutUser() { 19 | return (dispatch) => { 20 | jwtAuthService.logout(); 21 | 22 | history.push({ 23 | pathname: "/session/signin", 24 | }); 25 | 26 | dispatch({ 27 | type: USER_LOGGED_OUT, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/redux/actions/UserCreateActions.js: -------------------------------------------------------------------------------- 1 | import instance from "./../../../axios"; 2 | import { newNotification } from "./NotificationsActions"; 3 | import { getUsersList } from "./UsersActions"; 4 | 5 | export const CREATE_USER_START = "CREATE_USER_START"; 6 | export const CREATE_USER_SUCCESS = "CREATE_USER_SUCCESS"; 7 | export const CREATE_USER_FAIL = "CREATE_USER_FAIL"; 8 | 9 | export const createUser = (name, password) => (dispatch) => { 10 | dispatch({ 11 | type: CREATE_USER_START, 12 | }); 13 | 14 | instance 15 | .post(`/api/auth/register`, { 16 | name, 17 | password, 18 | }) 19 | .then((res) => { 20 | dispatch({ 21 | type: CREATE_USER_SUCCESS, 22 | }); 23 | dispatch(getUsersList()); 24 | dispatch( 25 | newNotification({ 26 | type: "success", 27 | message: CREATE_USER_SUCCESS, 28 | }) 29 | ); 30 | }) 31 | .catch((e) => { 32 | dispatch( 33 | newNotification({ 34 | type: "error", 35 | message: e.response?.data.message 36 | ? e.response?.data.message 37 | : e.message, 38 | }) 39 | ); 40 | dispatch({ 41 | type: CREATE_USER_FAIL, 42 | payload: e, 43 | }); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /src/app/redux/actions/UserRemoveActions.js: -------------------------------------------------------------------------------- 1 | import instance from "./../../../axios"; 2 | import { newNotification } from "./NotificationsActions"; 3 | import { getUsersList } from "./UsersActions"; 4 | 5 | const REMOVE_USER_SUCCESS = "REMOVE_USER_SUCCESS"; 6 | 7 | export const removeUser = (id) => (dispatch) => { 8 | instance 9 | .delete(`/api/users/` + id) 10 | .then((res) => { 11 | dispatch(getUsersList()); 12 | dispatch( 13 | newNotification({ 14 | type: "success", 15 | message: REMOVE_USER_SUCCESS, 16 | }) 17 | ); 18 | }) 19 | .catch((e) => { 20 | dispatch( 21 | newNotification({ 22 | type: "error", 23 | message: e.response?.data.message 24 | ? e.response?.data.message 25 | : e.message, 26 | }) 27 | ); 28 | }); 29 | }; 30 | 31 | export const removeUserWithBotMigrate = (id, target) => (dispatch) => { 32 | instance 33 | .patch(`/api/bots/` + id + "/migrate", { 34 | id: target, 35 | }) 36 | .then((res) => { 37 | dispatch(removeUser(id)); 38 | }) 39 | .catch((e) => { 40 | dispatch( 41 | newNotification({ 42 | type: "error", 43 | message: e.response?.data.message 44 | ? e.response?.data.message 45 | : e.message, 46 | }) 47 | ); 48 | }); 49 | }; 50 | 51 | export const removeUserWithBotDelete = (id, target) => (dispatch) => { 52 | instance 53 | .delete(`/api/bots/` + id + "/all", { 54 | id: target, 55 | }) 56 | .then((res) => { 57 | dispatch(removeUser(id)); 58 | }) 59 | .catch((e) => { 60 | dispatch( 61 | newNotification({ 62 | type: "error", 63 | message: e.response?.data.message 64 | ? e.response?.data.message 65 | : e.message, 66 | }) 67 | ); 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /src/app/redux/actions/UsersActions.js: -------------------------------------------------------------------------------- 1 | import instance from "./../../../axios"; 2 | import { newNotification } from "./NotificationsActions"; 3 | 4 | export const GET_USERS_START = "GET_USERS_START"; 5 | export const GET_USERS_SUCCESS = "GET_USERS_SUCCESS"; 6 | export const GET_USERS_FAIL = "GET_USERS_FAIL"; 7 | 8 | export const getUsersList = () => (dispatch) => { 9 | dispatch({ 10 | type: GET_USERS_START, 11 | }); 12 | instance 13 | .get(`/api/users/`) 14 | .then((res) => { 15 | dispatch({ 16 | type: GET_USERS_SUCCESS, 17 | payload: res.data, 18 | }); 19 | }) 20 | .catch((e) => { 21 | dispatch( 22 | newNotification({ 23 | type: "error", 24 | message: e.response?.data.message 25 | ? e.response.data.message 26 | : e.message, 27 | }) 28 | ); 29 | dispatch({ 30 | type: GET_USERS_FAIL, 31 | payload: e, 32 | }); 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /src/app/redux/reducers/BotCreateReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | CREATE_BOT_FAIL, 3 | CREATE_BOT_START, 4 | CREATE_BOT_SUCCESS, 5 | } from "../actions/BotCreateActions"; 6 | 7 | const initialState = { 8 | loading: false, 9 | }; 10 | 11 | const BotCreateReducer = function (state = initialState, action) { 12 | switch (action.type) { 13 | case CREATE_BOT_START: { 14 | return { 15 | ...state, 16 | loading: true, 17 | }; 18 | } 19 | case CREATE_BOT_SUCCESS: { 20 | return { 21 | ...state, 22 | loading: false, 23 | }; 24 | } 25 | case CREATE_BOT_FAIL: { 26 | return { 27 | ...state, 28 | loading: false, 29 | }; 30 | } 31 | default: { 32 | return { 33 | ...state, 34 | }; 35 | } 36 | } 37 | }; 38 | 39 | export default BotCreateReducer; 40 | -------------------------------------------------------------------------------- /src/app/redux/reducers/BotRightsReducer.js: -------------------------------------------------------------------------------- 1 | import { GET_BOT_RIGHTS_START, GET_BOT_RIGHTS_SUCCESS, GET_BOT_RIGHTS_FAIL, ADD_BOT_RIGHTS_START, ADD_BOT_RIGHTS_FAIL, ADD_BOT_RIGHTS_SUCCESS, DEL_BOT_RIGHTS_START, DEL_BOT_RIGHTS_SUCCESS, DEL_BOT_RIGHTS_FAIL } from "../actions/BotRightsActions"; 2 | 3 | const initialState = { 4 | useruid: [], 5 | groupid: [], 6 | loading: true, 7 | }; 8 | 9 | const BotRightsReducer = function (state = initialState, action) { 10 | switch (action.type) { 11 | case GET_BOT_RIGHTS_START: { 12 | return { 13 | ...state, 14 | loading: true 15 | }; 16 | } 17 | case GET_BOT_RIGHTS_SUCCESS: { 18 | return { 19 | ...state, 20 | loading: false, 21 | useruid: [...action.payload.useruid], 22 | groupid: [...action.payload.groupid], 23 | }; 24 | } 25 | case GET_BOT_RIGHTS_FAIL: { 26 | return { 27 | ...state, 28 | loading: false, 29 | } 30 | } 31 | case ADD_BOT_RIGHTS_START: { 32 | return { 33 | ...state, 34 | loading: true 35 | }; 36 | } 37 | case ADD_BOT_RIGHTS_SUCCESS: { 38 | return { 39 | ...state, 40 | loading: false, 41 | }; 42 | } 43 | case ADD_BOT_RIGHTS_FAIL: { 44 | return { 45 | ...state, 46 | loading: false, 47 | } 48 | } 49 | case DEL_BOT_RIGHTS_START: { 50 | return { 51 | ...state, 52 | loading: true 53 | }; 54 | } 55 | case DEL_BOT_RIGHTS_SUCCESS: { 56 | return { 57 | ...state, 58 | loading: false, 59 | }; 60 | } 61 | case DEL_BOT_RIGHTS_FAIL: { 62 | return { 63 | ...state, 64 | loading: false, 65 | } 66 | } 67 | default: { 68 | return { 69 | ...state 70 | }; 71 | } 72 | } 73 | }; 74 | 75 | export default BotRightsReducer; 76 | -------------------------------------------------------------------------------- /src/app/redux/reducers/BotSettingsReducer.js: -------------------------------------------------------------------------------- 1 | import { EDIT_BOT_FAIL, EDIT_BOT_START, EDIT_BOT_SUCCESS, GET_BOT_FAIL, GET_BOT_START, GET_BOT_SUCCESS, START_BOT_FAIL, START_BOT_START, START_BOT_SUCCESS, STOP_BOT_FAIL, STOP_BOT_START, STOP_BOT_SUCCESS } from "../actions/BotSettingsActions"; 2 | 3 | const initialState = { 4 | bot: null, 5 | loading: true, 6 | }; 7 | 8 | const BotSettingsReducer = function (state = initialState, action) { 9 | switch (action.type) { 10 | case GET_BOT_START: { 11 | return { 12 | ...state, 13 | loading: true 14 | }; 15 | } 16 | case GET_BOT_SUCCESS: { 17 | return { 18 | ...state, 19 | loading: false, 20 | bot: { ...action.payload } 21 | }; 22 | } 23 | case GET_BOT_FAIL: { 24 | return { 25 | ...state, 26 | loading: false, 27 | } 28 | } 29 | case START_BOT_START: { 30 | return { 31 | ...state, 32 | loading: true 33 | } 34 | } 35 | case START_BOT_SUCCESS: { 36 | return { 37 | ...state, 38 | loading: false 39 | } 40 | } 41 | case START_BOT_FAIL: { 42 | return { 43 | ...state, 44 | loading: false 45 | } 46 | } 47 | case STOP_BOT_START: { 48 | return { 49 | ...state, 50 | loading: true 51 | } 52 | } 53 | case STOP_BOT_SUCCESS: { 54 | return { 55 | ...state, 56 | loading: false 57 | } 58 | } 59 | case STOP_BOT_FAIL: { 60 | return { 61 | ...state, 62 | loading: false 63 | } 64 | } 65 | case EDIT_BOT_START: { 66 | return { 67 | ...state, 68 | loading: true 69 | } 70 | } 71 | case EDIT_BOT_SUCCESS: { 72 | return { 73 | ...state, 74 | loading: false 75 | } 76 | } 77 | case EDIT_BOT_FAIL: { 78 | return { 79 | ...state, 80 | loading: false 81 | } 82 | } 83 | default: { 84 | return { 85 | ...state 86 | }; 87 | } 88 | } 89 | }; 90 | 91 | export default BotSettingsReducer; 92 | -------------------------------------------------------------------------------- /src/app/redux/reducers/BotsReducer.js: -------------------------------------------------------------------------------- 1 | import { GET_BOTS_LIST_FAIL, GET_BOTS_LIST_START, GET_BOTS_LIST_SUCCESS } from "../actions/BotsActions"; 2 | 3 | const initialState = { 4 | bots: [], 5 | loading: true, 6 | }; 7 | 8 | const BotsReducer = function (state = initialState, action) { 9 | switch (action.type) { 10 | case GET_BOTS_LIST_START: { 11 | return { 12 | ...state, 13 | loading: true 14 | }; 15 | } 16 | case GET_BOTS_LIST_SUCCESS: { 17 | return { 18 | ...state, 19 | loading: false, 20 | bots: [...action.payload] 21 | }; 22 | } 23 | case GET_BOTS_LIST_FAIL: { 24 | return { 25 | ...state, 26 | loading: false, 27 | } 28 | } 29 | default: { 30 | return { 31 | ...state 32 | }; 33 | } 34 | } 35 | }; 36 | 37 | export default BotsReducer; 38 | -------------------------------------------------------------------------------- /src/app/redux/reducers/LayoutReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_LAYOUT_SETTINGS, 3 | SET_DEFAULT_LAYOUT_SETTINGS 4 | } from "../actions/LayoutActions"; 5 | import { MatxLayoutSettings } from "../../MatxLayout/settings"; 6 | 7 | const initialState = { 8 | settings: { 9 | ...MatxLayoutSettings 10 | }, 11 | defaultSettings: { 12 | ...MatxLayoutSettings 13 | } 14 | }; 15 | 16 | const LayoutReducer = (state = initialState, action) => { 17 | switch (action.type) { 18 | case SET_LAYOUT_SETTINGS: 19 | return { 20 | ...state, 21 | settings: { ...action.data } 22 | }; 23 | case SET_DEFAULT_LAYOUT_SETTINGS: 24 | return { 25 | ...state, 26 | defaultSettings: { ...action.data } 27 | }; 28 | default: 29 | return { ...state }; 30 | } 31 | }; 32 | 33 | export default LayoutReducer; 34 | -------------------------------------------------------------------------------- /src/app/redux/reducers/LoginReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOGIN_SUCCESS, 3 | LOGIN_ERROR, 4 | LOGIN_LOADING, 5 | RESET_PASSWORD 6 | } from "../actions/LoginActions"; 7 | 8 | const initialState = { 9 | success: false, 10 | loading: false, 11 | error: { 12 | username: null, 13 | password: null 14 | } 15 | }; 16 | 17 | const LoginReducer = function(state = initialState, action) { 18 | switch (action.type) { 19 | case LOGIN_LOADING: { 20 | return { 21 | ...state, 22 | loading: true 23 | }; 24 | } 25 | case LOGIN_SUCCESS: { 26 | return { 27 | ...state, 28 | success: true, 29 | loading: false 30 | }; 31 | } 32 | case RESET_PASSWORD: { 33 | return { 34 | ...state, 35 | success: true, 36 | loading: false 37 | }; 38 | } 39 | case LOGIN_ERROR: { 40 | return { 41 | success: false, 42 | loading: false, 43 | error: action.data 44 | }; 45 | } 46 | default: { 47 | return state; 48 | } 49 | } 50 | }; 51 | 52 | export default LoginReducer; 53 | -------------------------------------------------------------------------------- /src/app/redux/reducers/NavigationReducer.js: -------------------------------------------------------------------------------- 1 | import { navigations } from "app/navigations"; 2 | import { 3 | INIT_USER_NAVIGATION, 4 | SET_USER_NAVIGATION, 5 | } from "../actions/NavigationAction"; 6 | 7 | const initialState = [...navigations]; 8 | 9 | const NavigationReducer = function (state = initialState, action) { 10 | switch (action.type) { 11 | case SET_USER_NAVIGATION: { 12 | return [...action.payload]; 13 | } 14 | case INIT_USER_NAVIGATION: { 15 | return [...navigations]; 16 | } 17 | default: { 18 | return [...state]; 19 | } 20 | } 21 | }; 22 | 23 | export default NavigationReducer; 24 | -------------------------------------------------------------------------------- /src/app/redux/reducers/NotificationReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_NOTIFICATION, 3 | CREATE_NOTIFICATION, 4 | DELETE_ALL_NOTIFICATION, 5 | DELETE_NOTIFICATION 6 | } from "../actions/NotificationActions"; 7 | 8 | const initialState = []; 9 | 10 | const NotificationReducer = function(state = initialState, action) { 11 | switch (action.type) { 12 | case GET_NOTIFICATION: { 13 | return [...action.payload]; 14 | } 15 | case CREATE_NOTIFICATION: { 16 | return [...action.payload]; 17 | } 18 | case DELETE_NOTIFICATION: { 19 | return [...action.payload]; 20 | } 21 | case DELETE_ALL_NOTIFICATION: { 22 | return [...action.payload]; 23 | } 24 | default: { 25 | return [...state]; 26 | } 27 | } 28 | }; 29 | 30 | export default NotificationReducer; 31 | -------------------------------------------------------------------------------- /src/app/redux/reducers/NotificationsReducer.js: -------------------------------------------------------------------------------- 1 | import { ADD_NOTIFICATION, REMOVE_NOTIFICATION } from "../actions/NotificationsActions"; 2 | 3 | const initialState = { 4 | messages: [], 5 | }; 6 | 7 | const NotificationsReducer = function (state = initialState, action) { 8 | switch (action.type) { 9 | case ADD_NOTIFICATION: { 10 | return { 11 | ...state, 12 | messages: state.messages.concat(action.payload) 13 | }; 14 | } 15 | case REMOVE_NOTIFICATION: { 16 | return { 17 | ...state, 18 | messages: [...state.messages.slice(0, 0), ...state.messages.slice(1)] 19 | }; 20 | } 21 | default: { 22 | return { 23 | ...state 24 | }; 25 | } 26 | } 27 | }; 28 | 29 | export default NotificationsReducer; 30 | -------------------------------------------------------------------------------- /src/app/redux/reducers/RootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import LoginReducer from "./LoginReducer"; 3 | import UserReducer from "./UserReducer"; 4 | import LayoutReducer from "./LayoutReducer"; 5 | import NavigationReducer from "./NavigationReducer"; 6 | import BotsReducer from "./BotsReducer"; 7 | import BotSettingsReducer from "./BotSettingsReducer"; 8 | import BotRightsReducer from "./BotRightsReducer"; 9 | import NotificationsReducer from "./NotificationsReducer"; 10 | import UsersReducer from "./UsersReducer"; 11 | import BotCreateReducer from "./BotCreateReducer"; 12 | import UserCreateReducer from "./UserCreateReducer"; 13 | 14 | const RootReducer = combineReducers({ 15 | bots: BotsReducer, 16 | botSettings: BotSettingsReducer, 17 | botCreate: BotCreateReducer, 18 | userCreate: UserCreateReducer, 19 | botRights: BotRightsReducer, 20 | notification: NotificationsReducer, 21 | login: LoginReducer, 22 | user: UserReducer, 23 | users: UsersReducer, 24 | layout: LayoutReducer, 25 | // scrumboard: ScrumBoardReducer, 26 | // ecommerce: EcommerceReducer, 27 | navigations: NavigationReducer, 28 | }); 29 | 30 | export default RootReducer; 31 | -------------------------------------------------------------------------------- /src/app/redux/reducers/ScrumBoardReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_ALL_BOARD, 3 | ADD_BOARD, 4 | GET_BOARD_BY_ID, 5 | ADD_LIST, 6 | RENAME_LIST, 7 | DELETE_LIST, 8 | ADD_CARD, 9 | GET_ALL_MEMBERS, 10 | GET_ALL_LABELS, 11 | ADD_MEMBER_IN_BOARD, 12 | DELETE_MEMBER_FROM_BOARD, 13 | UPDATE_CARD, 14 | MOVE_CARD, 15 | REORDER_LIST, 16 | REORDER_CARD_LIST 17 | } from "../actions/ScrumBoardActions"; 18 | 19 | const initialState = {}; 20 | 21 | const ScrumBoardReducer = function(state = initialState, action) { 22 | switch (action.type) { 23 | case GET_ALL_MEMBERS: { 24 | return { 25 | ...state, 26 | memberList: [...action.payload] 27 | }; 28 | } 29 | case GET_ALL_LABELS: { 30 | return { 31 | ...state, 32 | labelList: [...action.payload] 33 | }; 34 | } 35 | case GET_ALL_BOARD: { 36 | return { 37 | ...state, 38 | boardList: [...action.payload] 39 | }; 40 | } 41 | case ADD_BOARD: { 42 | return { 43 | ...state, 44 | boardList: [...action.payload] 45 | }; 46 | } 47 | case GET_BOARD_BY_ID: { 48 | return { 49 | ...state, 50 | board: { ...action.payload } 51 | }; 52 | } 53 | case ADD_MEMBER_IN_BOARD: { 54 | return { 55 | ...state, 56 | board: { ...action.payload } 57 | }; 58 | } 59 | case DELETE_MEMBER_FROM_BOARD: { 60 | return { 61 | ...state, 62 | board: { ...action.payload } 63 | }; 64 | } 65 | case ADD_LIST: { 66 | return { 67 | ...state, 68 | board: { ...action.payload } 69 | }; 70 | } 71 | case RENAME_LIST: { 72 | return { 73 | ...state, 74 | board: { ...action.payload } 75 | }; 76 | } 77 | case DELETE_LIST: { 78 | return { 79 | ...state, 80 | board: { ...action.payload } 81 | }; 82 | } 83 | case REORDER_LIST: { 84 | return { 85 | ...state, 86 | board: { ...action.payload } 87 | }; 88 | } 89 | case ADD_CARD: { 90 | return { 91 | ...state, 92 | board: { ...action.payload } 93 | }; 94 | } 95 | case UPDATE_CARD: { 96 | return { 97 | ...state, 98 | board: { ...action.payload } 99 | }; 100 | } 101 | case REORDER_CARD_LIST: { 102 | return { 103 | ...state, 104 | board: { ...action.payload } 105 | }; 106 | } 107 | case MOVE_CARD: { 108 | return { 109 | ...state, 110 | board: { ...action.payload } 111 | }; 112 | } 113 | default: { 114 | return state; 115 | } 116 | } 117 | }; 118 | 119 | export default ScrumBoardReducer; 120 | -------------------------------------------------------------------------------- /src/app/redux/reducers/UserCreateReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | CREATE_USER_FAIL, 3 | CREATE_USER_START, 4 | CREATE_USER_SUCCESS, 5 | } from "../actions/UserCreateActions"; 6 | 7 | const initialState = { 8 | loading: false, 9 | }; 10 | 11 | const UserCreateReducer = function (state = initialState, action) { 12 | switch (action.type) { 13 | case CREATE_USER_START: { 14 | return { 15 | ...state, 16 | loading: true, 17 | }; 18 | } 19 | case CREATE_USER_SUCCESS: { 20 | return { 21 | ...state, 22 | loading: false, 23 | }; 24 | } 25 | case CREATE_USER_FAIL: { 26 | return { 27 | ...state, 28 | loading: false, 29 | }; 30 | } 31 | default: { 32 | return { 33 | ...state, 34 | }; 35 | } 36 | } 37 | }; 38 | 39 | export default UserCreateReducer; 40 | -------------------------------------------------------------------------------- /src/app/redux/reducers/UserReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_USER_DATA, 3 | REMOVE_USER_DATA, 4 | USER_LOGGED_OUT 5 | } from "../actions/UserActions"; 6 | 7 | const initialState = {}; 8 | 9 | const userReducer = function (state = initialState, action) { 10 | switch (action.type) { 11 | case SET_USER_DATA: { 12 | return { 13 | ...state, 14 | ...action.data 15 | }; 16 | } 17 | case REMOVE_USER_DATA: { 18 | return { 19 | ...state 20 | }; 21 | } 22 | case USER_LOGGED_OUT: { 23 | return state; 24 | } 25 | default: { 26 | return state; 27 | } 28 | } 29 | }; 30 | 31 | export default userReducer; 32 | -------------------------------------------------------------------------------- /src/app/redux/reducers/UsersReducer.js: -------------------------------------------------------------------------------- 1 | import { GET_USERS_FAIL, GET_USERS_START, GET_USERS_SUCCESS } from "../actions/UsersActions"; 2 | 3 | const initialState = { 4 | users: [], 5 | loading: true, 6 | }; 7 | 8 | const UsersReducer = function (state = initialState, action) { 9 | switch (action.type) { 10 | case GET_USERS_START: { 11 | return { 12 | ...state, 13 | loading: true 14 | }; 15 | } 16 | case GET_USERS_SUCCESS: { 17 | return { 18 | ...state, 19 | loading: false, 20 | users: [...action.payload] 21 | }; 22 | } 23 | case GET_USERS_FAIL: { 24 | return { 25 | ...state, 26 | loading: false, 27 | } 28 | } 29 | default: { 30 | return { 31 | ...state 32 | }; 33 | } 34 | } 35 | }; 36 | 37 | export default UsersReducer; 38 | -------------------------------------------------------------------------------- /src/app/services/jwtAuthService.js: -------------------------------------------------------------------------------- 1 | import instance from "./../../axios"; 2 | 3 | class JwtAuthService { 4 | loginWithUsernameAndPassword = (username, password) => { 5 | return instance 6 | .post(`/api/auth/login`, { 7 | name: username, 8 | password, 9 | }) 10 | .then(({ data }) => { 11 | this.setSession(data.access_token); 12 | return instance.get( 13 | `/api/users/profile` 14 | ); 15 | }) 16 | .then(({ data }) => { 17 | this.setUser({ 18 | userId: data.id, 19 | displayName: data.name, 20 | role: data.admin ? "admin" : "user", 21 | }); 22 | return { 23 | userId: data.id, 24 | displayName: data.name, 25 | role: data.admin ? "admin" : "user", 26 | }; 27 | }); 28 | }; 29 | 30 | // You need to send http requst with existing token to your server to check token is valid 31 | // This method is being used when user logged in & app is reloaded 32 | loginWithToken = () => { 33 | if (window.localStorage.getItem("jwt_token")) { 34 | return instance 35 | .get(`/api/users/profile`) 36 | .then(({ data }) => { 37 | this.setUser({ 38 | userId: data.id, 39 | displayName: data.name, 40 | role: data.admin ? "admin" : "user", 41 | }); 42 | this.setSession(window.localStorage.getItem("jwt_token")); 43 | return { 44 | userId: data.id, 45 | displayName: data.name, 46 | role: data.admin ? "admin" : "user", 47 | }; 48 | }); 49 | } 50 | }; 51 | 52 | logout = () => { 53 | this.setSession(null); 54 | this.removeUser(); 55 | }; 56 | 57 | // Set token to all http request header, so you don't need to attach everytime 58 | setSession = (token) => { 59 | if (token) { 60 | instance.defaults.headers.common["Authorization"] = "bearer " + token; 61 | window.localStorage.setItem("jwt_token", token); 62 | } else { 63 | window.localStorage.removeItem("jwt_token"); 64 | delete instance.defaults.headers.common["Authorization"]; 65 | } 66 | }; 67 | 68 | // Save user to localstorage 69 | setUser = (user) => { 70 | window.localStorage.setItem("auth_user", JSON.stringify(user)); 71 | }; 72 | // Remove user from localstorage 73 | removeUser = () => { 74 | window.localStorage.removeItem("auth_user"); 75 | }; 76 | } 77 | 78 | export default new JwtAuthService(); 79 | -------------------------------------------------------------------------------- /src/app/ui/AdvanceTable.jsx: -------------------------------------------------------------------------------- 1 | import MaterialTable from "material-table"; 2 | import React from "react"; 3 | import { useTranslation } from "react-i18next"; 4 | 5 | const AdvanceTable = (props) => { 6 | const { t } = useTranslation(); 7 | return ( 8 | 64 | ); 65 | }; 66 | 67 | export default AdvanceTable; 68 | -------------------------------------------------------------------------------- /src/app/ui/BlockButton.jsx: -------------------------------------------------------------------------------- 1 | import { Button, CircularProgress, Icon } from "@material-ui/core"; 2 | import React from "react"; 3 | 4 | const BlockButton = (props) => { 5 | return ( 6 |
7 |
8 | 18 | {props.loading ? ( 19 | 30 | ) : null} 31 |
32 |
33 | ); 34 | }; 35 | 36 | export default BlockButton; 37 | -------------------------------------------------------------------------------- /src/app/ui/Spinner.jsx: -------------------------------------------------------------------------------- 1 | import { CircularProgress } from "@material-ui/core"; 2 | import React from "react"; 3 | 4 | const Spinner = (props) => ( 5 |
6 | 7 |
8 | ); 9 | 10 | export default Spinner; 11 | -------------------------------------------------------------------------------- /src/app/views/bot/AddRightModel.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Button from "@material-ui/core/Button"; 3 | import TextField from "@material-ui/core/TextField"; 4 | import Dialog from "@material-ui/core/Dialog"; 5 | import DialogActions from "@material-ui/core/DialogActions"; 6 | import DialogContent from "@material-ui/core/DialogContent"; 7 | import DialogTitle from "@material-ui/core/DialogTitle"; 8 | import { connect } from "react-redux"; 9 | import { FormControlLabel, Switch } from "@material-ui/core"; 10 | import { addBotRights } from "app/redux/actions/BotRightsActions"; 11 | import { useTranslation } from "react-i18next"; 12 | 13 | function AddRightModel(props) { 14 | const [value, setValue] = useState(""); 15 | const [admin, setAdmin] = useState(false); 16 | const { t } = useTranslation(); 17 | 18 | let field = ( 19 | setValue(+e.target.value)} 23 | id={props.type} 24 | value={value} 25 | label="Groupid" 26 | type="number" 27 | fullWidth 28 | /> 29 | ); 30 | if (props.type === "useruid") { 31 | field = ( 32 | setValue(e.target.value)} 36 | id={props.type} 37 | value={value} 38 | label="Useruid" 39 | type="text" 40 | fullWidth 41 | /> 42 | ); 43 | } 44 | 45 | let adminField = null; 46 | if (props.user.role === "admin") { 47 | adminField = ( 48 | setAdmin(!admin)} 53 | name="checkedB" 54 | color="primary" 55 | /> 56 | } 57 | label="Admin" 58 | /> 59 | ); 60 | } 61 | 62 | return ( 63 | props.handleClose()} 66 | aria-labelledby="form-dialog-title" 67 | > 68 | 69 | {props.type === "useruid" ? t("add-userid") : t("add-groupid")} 70 | 71 | 72 | {field} 73 | {adminField} 74 | 75 | 76 | 83 | 92 | 93 | 94 | ); 95 | } 96 | 97 | const mapStateToProps = (props) => { 98 | return { 99 | user: props.user, 100 | }; 101 | }; 102 | 103 | const mapDispatchToProps = (dispatch) => { 104 | return { 105 | onAddRight: (id, data) => dispatch(addBotRights(id, data)), 106 | }; 107 | }; 108 | 109 | export default connect(mapStateToProps, mapDispatchToProps)(AddRightModel); 110 | -------------------------------------------------------------------------------- /src/app/views/bot/BotRoutes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const botRoutes = [ 4 | { 5 | exact: true, 6 | path: "/bot/:id/edit", 7 | component: React.lazy(() => import("./Bot")), 8 | }, 9 | ]; 10 | 11 | export default botRoutes; 12 | -------------------------------------------------------------------------------- /src/app/views/bot/RightsTable.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from "react"; 2 | import { Button } from "@material-ui/core"; 3 | import { connect } from "react-redux"; 4 | import { delBotRights } from "app/redux/actions/BotRightsActions"; 5 | import AdvanceTable from "app/ui/AdvanceTable"; 6 | 7 | class RightsTable extends Component { 8 | state = { 9 | useruid: [ 10 | { 11 | title: "username", 12 | field: "id", 13 | cellStyle: { textAlign: "center" }, 14 | render: (rowData) => { 15 | return rowData.admin ? ( 16 | 17 | {rowData.id} 18 | 21 | 22 | ) : ( 23 | rowData.id 24 | ); 25 | }, 26 | }, 27 | ], 28 | groupid: [ 29 | { 30 | title: "id", 31 | field: "id", 32 | cellStyle: { textAlign: "center" }, 33 | render: (rowData) => { 34 | return rowData.admin ? ( 35 | 36 | {rowData.id} 37 | 40 | 41 | ) : ( 42 | rowData.id 43 | ); 44 | }, 45 | }, 46 | ], 47 | data: [], 48 | }; 49 | 50 | shouldComponentUpdate(nextProps, nextState) { 51 | if (this.props.data !== nextProps.data) { 52 | return true; 53 | } 54 | return false; 55 | } 56 | 57 | render() { 58 | return ( 59 |
60 | 69 | this.props.onRightDel(this.props.botId, { 70 | type: this.props.type, 71 | admin: rowData.admin, 72 | value: rowData.id, 73 | }), 74 | }, 75 | ]} 76 | options={{ 77 | paginationType: "stepped", 78 | headerStyle: { 79 | display: "none", 80 | }, 81 | }} 82 | /> 83 |
84 | ); 85 | } 86 | } 87 | 88 | const mapDispatchToProps = (dispatch) => { 89 | return { 90 | onRightDel: (id, data) => dispatch(delBotRights(id, data)), 91 | }; 92 | }; 93 | 94 | export default connect(null, mapDispatchToProps)(RightsTable); 95 | -------------------------------------------------------------------------------- /src/app/views/bot/SettingFormHeader.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Icon, IconButton } from "@material-ui/core"; 2 | import { 3 | startBotAndReloadBot, 4 | stopBotAndReloadBot, 5 | } from "app/redux/actions/BotSettingsActions"; 6 | import React, { Fragment } from "react"; 7 | import { useTranslation } from "react-i18next"; 8 | import { connect } from "react-redux"; 9 | import Spinner from "../../ui/Spinner"; 10 | 11 | const SettingsFormHeader = (props) => { 12 | const { t } = useTranslation(); 13 | let style = {}; 14 | let status = "UNKNOW"; 15 | switch (props.botStatus) { 16 | case 0: 17 | status = "OFFLINE"; 18 | style = { 19 | color: "#f44336 ", 20 | border: "1px solid rgba(244, 67, 54, 0.5)", 21 | }; 22 | break; 23 | case 2: 24 | style = { 25 | color: "#08ad6c ", 26 | border: "1px solid rgba(8, 173, 108, 0.5)", 27 | }; 28 | status = "ONLINE"; 29 | break; 30 | case 1: 31 | status = "DISCONNECTED"; 32 | style = { 33 | color: "#ff9e43 ", 34 | border: "1px solid rgba(255, 158, 67, 0.5)", 35 | }; 36 | break; 37 | default: 38 | return style; 39 | } 40 | 41 | const handlePowerOnOff = (id, status) => { 42 | switch (status) { 43 | case 0: 44 | return props.onStartBot(id); 45 | case 1: 46 | return props.onStopBot(id); 47 | case 2: 48 | return props.onStopBot(id); 49 | default: 50 | return true; 51 | } 52 | }; 53 | 54 | let controls = ( 55 |
56 | 59 | handlePowerOnOff(props.botId, props.botStatus)} 61 | aria-label="Delete" 62 | > 63 | power_settings_new 64 | 65 |
66 | ); 67 | if (props.loading) { 68 | controls = ( 69 |
70 | 71 |
72 | ); 73 | } 74 | 75 | return ( 76 | 77 |
78 |

{t("bot-settings-title")}

79 | {controls} 80 |
81 |
Bot ID: {props.botId}
82 |
83 | ); 84 | }; 85 | 86 | const mapDispatchToProps = (dispatch) => { 87 | return { 88 | onStartBot: (id) => dispatch(startBotAndReloadBot(id)), 89 | onStopBot: (id) => dispatch(stopBotAndReloadBot(id)), 90 | }; 91 | }; 92 | 93 | export default connect(null, mapDispatchToProps)(SettingsFormHeader); 94 | -------------------------------------------------------------------------------- /src/app/views/botcreate/BotCreateRoutes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { authRoles } from "../../auth/authRoles"; 3 | 4 | const botCreateRoutes = [ 5 | { 6 | exact: true, 7 | path: "/createbot", 8 | component: React.lazy(() => import("./BotCreate")), 9 | auth: authRoles.admin, 10 | }, 11 | ]; 12 | 13 | export default botCreateRoutes; 14 | -------------------------------------------------------------------------------- /src/app/views/dashboard/Analytics.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Card, Grid } from "@material-ui/core"; 3 | import { connect } from "react-redux"; 4 | 5 | import { withStyles } from "@material-ui/styles"; 6 | import { getBotsList } from "app/redux/actions/BotsActions"; 7 | import UserBotsTable from "./UserBotsTable"; 8 | import OtherBotsTable from "./OtherBotsTable"; 9 | import Spinner from "../../ui/Spinner"; 10 | import StatsCard from "./StatsCard"; 11 | 12 | class Dashboard1 extends Component { 13 | state = { 14 | myBots: [], 15 | otherBots: [], 16 | }; 17 | 18 | componentDidMount() { 19 | this.props.onFetchBots(); 20 | } 21 | 22 | componentDidUpdate(previousProps, previousState) { 23 | if (previousProps.bots !== this.props.bots) { 24 | const filterMyBots = this.props.bots.filter( 25 | (el) => el.owner === this.props.user.userId 26 | ); 27 | const filterOtherBots = this.props.bots.filter( 28 | (el) => el.owner !== this.props.user.userId 29 | ); 30 | this.setState({ 31 | myBots: [...filterMyBots], 32 | otherBots: [...filterOtherBots], 33 | }); 34 | } 35 | } 36 | 37 | render() { 38 | let myBots = ; 39 | let otherBots = ; 40 | if (!this.props.loading) { 41 | myBots = ; 42 | otherBots = ; 43 | } 44 | return ( 45 |
46 | 47 | 48 | {this.props.user.role === "admin" ? ( 49 | 50 | 51 | {otherBots} 52 | 53 | 54 | ) : null} 55 | 56 | 57 | {myBots} 58 | 59 | 60 | 61 |
62 | ); 63 | } 64 | } 65 | 66 | const mapStateToProps = (state) => { 67 | return { 68 | loading: state.bots.loading, 69 | bots: state.bots.bots, 70 | user: state.user, 71 | }; 72 | }; 73 | 74 | const mapDispatchToProps = (dispatch) => { 75 | return { 76 | onFetchBots: () => { 77 | dispatch(getBotsList()); 78 | }, 79 | }; 80 | }; 81 | 82 | export default connect( 83 | mapStateToProps, 84 | mapDispatchToProps 85 | )(withStyles({}, { withTheme: true })(Dashboard1)); 86 | -------------------------------------------------------------------------------- /src/app/views/dashboard/DashboardRoutes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const dashboardRoutes = [ 4 | { 5 | exact: true, 6 | path: "/dashboard", 7 | component: React.lazy(() => import("./Analytics")), 8 | }, 9 | ]; 10 | 11 | export default dashboardRoutes; 12 | -------------------------------------------------------------------------------- /src/app/views/dashboard/RemoveBotModal.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Dialog, 4 | DialogActions, 5 | DialogContent, 6 | DialogTitle, 7 | } from "@material-ui/core"; 8 | import { removeBot } from "app/redux/actions/BotSettingsActions"; 9 | import React from "react"; 10 | import { useTranslation } from "react-i18next"; 11 | import { connect } from "react-redux"; 12 | 13 | const RemoveBotModal = (props) => { 14 | const { t } = useTranslation(); 15 | return ( 16 | props.handleClose()} 19 | aria-labelledby="form-dialog-title" 20 | > 21 | {t("bot.remove")} 22 | 23 | 24 | 31 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | const mapDispatchToProps = (dispatch) => { 46 | return { 47 | onBotRemove: (botId) => dispatch(removeBot(botId)), 48 | }; 49 | }; 50 | 51 | export default connect(null, mapDispatchToProps)(RemoveBotModal); 52 | -------------------------------------------------------------------------------- /src/app/views/dashboard/StatsCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { Grid, Card, Icon, Fab } from "@material-ui/core"; 3 | import { useTranslation } from "react-i18next"; 4 | 5 | 6 | const StatsCard = (props) => { 7 | 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 | 12 | 13 | 14 |
15 | 19 | power 20 | 21 |

{t('bot-stats.online')}{props.data.reduce((acc, cur) => cur.status === 2 ? ++acc : acc, 0)}

22 |
23 |
24 |
25 | 26 | 27 |
28 | 32 | power_off 33 | 34 |

{t('bot-stats.offline')}{props.data.reduce((acc, cur) => cur.status !== 2 ? ++acc : acc, 0)}

35 |
36 |
37 |
38 |
39 | ); 40 | }; 41 | 42 | export default StatsCard; -------------------------------------------------------------------------------- /src/app/views/dashboard/TransferBotModal.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | CircularProgress, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | NativeSelect, 9 | } from "@material-ui/core"; 10 | import { transferBot } from "app/redux/actions/BotSettingsActions"; 11 | import { getUsersList } from "app/redux/actions/UsersActions"; 12 | import React, { Component } from "react"; 13 | import { withTranslation } from "react-i18next"; 14 | import { connect } from "react-redux"; 15 | 16 | class TransferBotModal extends Component { 17 | state = { 18 | uuid: this.props.user.userId, 19 | }; 20 | 21 | componentDidMount() { 22 | this.props.onUsersFetch(); 23 | } 24 | 25 | handleChange = (event) => { 26 | const name = event.target.name; 27 | this.setState({ 28 | ...this.state, 29 | [name]: event.target.value, 30 | }); 31 | }; 32 | 33 | render() { 34 | const { t } = this.props; 35 | return ( 36 | this.props.handleClose()} 39 | aria-labelledby="form-dialog-title" 40 | > 41 | {t("bot.transfer")} 42 | 43 | {this.props.loading ? ( 44 | 45 | ) : ( 46 | 54 | {this.props.users.map((e) => ( 55 | 58 | ))} 59 | 60 | )} 61 | 62 | 63 | 70 | 79 | 80 | 81 | ); 82 | } 83 | } 84 | 85 | const mapStateToProps = (state) => { 86 | return { 87 | user: state.user, 88 | users: state.users.users, 89 | loading: state.users.loading, 90 | }; 91 | }; 92 | 93 | const mapDispatchToProps = (dispatch) => { 94 | return { 95 | onUsersFetch: () => dispatch(getUsersList()), 96 | onOwnerChange: (botId, userId) => dispatch(transferBot(botId, userId)), 97 | }; 98 | }; 99 | 100 | export default connect( 101 | mapStateToProps, 102 | mapDispatchToProps 103 | )(withTranslation()(TransferBotModal)); 104 | -------------------------------------------------------------------------------- /src/app/views/sessions/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Button } from "@material-ui/core"; 3 | import { withStyles } from "@material-ui/styles"; 4 | 5 | const styles = theme => ({ 6 | flexCenter: { 7 | display: "flex", 8 | justifyContent: "center", 9 | alignItems: "center" 10 | }, 11 | wrapper: { 12 | width: "100%", 13 | height: "100vh" 14 | }, 15 | inner: { 16 | flexDirection: "column", 17 | maxWidth: "320px" 18 | } 19 | }); 20 | 21 | class NotFound extends Component { 22 | state = {}; 23 | 24 | render() { 25 | const { classes } = this.props; 26 | return ( 27 |
28 |
29 | 34 | 42 |
43 |
44 | ); 45 | } 46 | } 47 | 48 | export default withStyles(styles, { withTheme: true })(NotFound); 49 | -------------------------------------------------------------------------------- /src/app/views/sessions/SessionRoutes.js: -------------------------------------------------------------------------------- 1 | import SignIn from "./SignIn"; 2 | import NotFound from "./NotFound"; 3 | 4 | const settings = { 5 | activeLayout: "layout1", 6 | layout1Settings: { 7 | topbar: { 8 | show: false, 9 | }, 10 | leftSidebar: { 11 | show: false, 12 | mode: "close", 13 | }, 14 | }, 15 | layout2Settings: { 16 | mode: "full", 17 | topbar: { 18 | show: false, 19 | }, 20 | navbar: { show: false }, 21 | }, 22 | secondarySidebar: { show: false }, 23 | footer: { show: false }, 24 | }; 25 | 26 | const sessionRoutes = [ 27 | { 28 | path: "/session/signin", 29 | component: SignIn, 30 | settings, 31 | }, 32 | { 33 | path: "/session/404", 34 | component: NotFound, 35 | settings, 36 | }, 37 | ]; 38 | 39 | export default sessionRoutes; 40 | -------------------------------------------------------------------------------- /src/app/views/users/PasswordChangeModal.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Dialog, 4 | DialogActions, 5 | DialogContent, 6 | DialogTitle, 7 | Input, 8 | } from "@material-ui/core"; 9 | import { transferBot } from "app/redux/actions/BotSettingsActions"; 10 | import { getUsersList } from "app/redux/actions/UsersActions"; 11 | import React, { useState } from "react"; 12 | import { useTranslation } from "react-i18next"; 13 | import { connect } from "react-redux"; 14 | 15 | const TransferBotModal = (props) => { 16 | const [password, setPassword] = useState(""); 17 | 18 | const { t } = useTranslation(); 19 | 20 | return ( 21 | props.handleClose()} 24 | aria-labelledby="form-dialog-title" 25 | > 26 | {t("pass-change")} 27 | 28 | setPassword(e.target.value)} 33 | /> 34 | 35 | 36 | 43 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | const mapStateToProps = (state) => { 58 | return { 59 | user: state.user, 60 | users: state.users.users, 61 | loading: state.users.loading, 62 | }; 63 | }; 64 | 65 | const mapDispatchToProps = (dispatch) => { 66 | return { 67 | onUsersFetch: () => dispatch(getUsersList()), 68 | onOwnerChange: (botId, userId) => dispatch(transferBot(botId, userId)), 69 | }; 70 | }; 71 | 72 | export default connect(mapStateToProps, mapDispatchToProps)(TransferBotModal); 73 | -------------------------------------------------------------------------------- /src/app/views/users/UsersRoutes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { authRoles } from "../../auth/authRoles"; 3 | 4 | const usersRoutes = [ 5 | { 6 | exact: true, 7 | path: "/users", 8 | component: React.lazy(() => import("./Users")), 9 | auth: authRoles.admin, 10 | }, 11 | ]; 12 | 13 | export default usersRoutes; 14 | -------------------------------------------------------------------------------- /src/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const instance = axios.create(); 4 | 5 | if (window.localStorage.getItem("jwt_token")) { 6 | instance.defaults.headers.common["Authorization"] = 7 | "bearer " + window.localStorage.getItem("jwt_token"); 8 | } 9 | 10 | export default instance; 11 | -------------------------------------------------------------------------------- /src/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | 3 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | 4 | import Backend from "i18next-http-backend"; 5 | import LanguageDetector from "i18next-browser-languagedetector"; 6 | // don't want to use this? 7 | // have a look at the Quick start guide 8 | // for passing in lng and translations on init 9 | 10 | i18n 11 | // load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) 12 | // learn more: https://github.com/i18next/i18next-http-backend 13 | .use(Backend) 14 | // detect user language 15 | // learn more: https://github.com/i18next/i18next-browser-languageDetector 16 | .use(LanguageDetector) 17 | // pass the i18n instance to react-i18next. 18 | .use(initReactI18next) 19 | // init i18next 20 | // for all options read: https://www.i18next.com/overview/configuration-options 21 | .init({ 22 | fallbackLng: "en", 23 | debug: true, 24 | 25 | interpolation: { 26 | escapeValue: false, // not needed for react as it escapes by default 27 | }, 28 | }); 29 | 30 | export default i18n; 31 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./_index.scss"; 4 | import "./i18n"; 5 | 6 | import * as serviceWorker from "./serviceWorker"; 7 | import App from "./app/App"; 8 | 9 | // cssVars(); 10 | 11 | ReactDOM.render(, document.getElementById("root")); 12 | 13 | // for IE-11 support un-comment cssVars() and it's import in this file 14 | // and in MatxTheme file 15 | 16 | // If you want your app to work offline and load faster, you can change 17 | // unregister() to register() below. Note this comes with some pitfalls. 18 | // Learn more about service workers: https://bit.ly/CRA-PWA 19 | serviceWorker.unregister(); 20 | -------------------------------------------------------------------------------- /src/matx/components/Breadcrumb.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon, Breadcrumbs, Hidden } from "@material-ui/core"; 3 | import { NavLink } from "react-router-dom"; 4 | 5 | const Breadcrumb = ({ routeSegments }) => { 6 | return ( 7 |
8 | {routeSegments ? ( 9 | 10 |

11 | {routeSegments[routeSegments.length - 1]["name"]} 12 |

13 |

|

14 |
15 | ) : null} 16 | navigate_next} 18 | className="flex items-center position-relative" 19 | > 20 | 21 | 22 | home 23 | 24 | 25 | {routeSegments 26 | ? routeSegments.map((route, index) => { 27 | return index !== routeSegments.length - 1 ? ( 28 | 29 | {route.name} 30 | 31 | ) : ( 32 | 33 | {route.name} 34 | 35 | ); 36 | }) 37 | : null} 38 | 39 |
40 | ); 41 | }; 42 | 43 | export default Breadcrumb; 44 | -------------------------------------------------------------------------------- /src/matx/components/ConfirmationDialog.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Dialog, Button } from "@material-ui/core"; 3 | 4 | const ConfirmationDialog = ({ 5 | open, 6 | onConfirmDialogClose, 7 | text, 8 | title = "confirm", 9 | onYesClick 10 | }) => { 11 | return ( 12 | 18 |
19 |

{title}

20 |

{text}

21 |
22 | 25 | 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default ConfirmationDialog; 39 | -------------------------------------------------------------------------------- /src/matx/components/LoaderBounce.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const LoaderBounce = () => { 4 | return ( 5 |
6 |
7 |
11 |
12 |
13 |
14 | ); 15 | }; 16 | 17 | export default LoaderBounce; 18 | -------------------------------------------------------------------------------- /src/matx/components/MatxHorizontalNav/MatxHorizontalNav.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | import { Icon } from "@material-ui/core"; 4 | import { useSelector } from "react-redux"; 5 | 6 | const MatxHorizontalNav = ({ max, className }) => { 7 | let navigation = useSelector(({ navigations }) => navigations); 8 | 9 | if (!navigation || !navigation.length) { 10 | return null; 11 | } 12 | 13 | if (max && navigation.length > max) { 14 | let childItem = { 15 | name: "More", 16 | icon: "more_vert", 17 | children: navigation.slice(max, navigation.length) 18 | }; 19 | navigation = navigation.slice(0, max); 20 | navigation.push(childItem); 21 | } 22 | 23 | function renderLevels(levels) { 24 | return levels.map((item, key) => { 25 | if (item.children) { 26 | return ( 27 |
  • 28 | 29 | {item.icon && ( 30 | {item.icon} 31 | )} 32 | {item.name} 33 | 34 |
      {renderLevels(item.children)}
    35 |
  • 36 | ); 37 | } else { 38 | return ( 39 |
  • 40 | 41 | {item.icon && ( 42 | {item.icon} 43 | )} 44 | {item.name} 45 | 46 |
  • 47 | ); 48 | } 49 | }); 50 | } 51 | 52 | return ( 53 |
    54 |
      {renderLevels(navigation)}
    55 |
    56 | ); 57 | }; 58 | 59 | export default MatxHorizontalNav; 60 | -------------------------------------------------------------------------------- /src/matx/components/MatxListItem1.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import RectangleAvatar from "./RectangleAvatar"; 3 | import { IconButton, Icon } from "@material-ui/core"; 4 | import { withStyles } from "@material-ui/core/styles"; 5 | 6 | const styles = { 7 | root: { 8 | borderRadius: "8px", 9 | cursor: "pointer", 10 | transition: "all 300ms ease", 11 | "&:hover": { 12 | background: "rgba(0,0,0, .08)", 13 | paddingLeft: "8px", 14 | overflow: "hidden", 15 | "& .action-icon, & .rectangle-box": { 16 | opacity: 1 17 | } 18 | }, 19 | "& .action-icon, & .rectangle-box": { 20 | opacity: 0.76 21 | } 22 | } 23 | }; 24 | 25 | const MatxListItem1 = ({ 26 | title, 27 | subtitle, 28 | iconText, 29 | iconColor, 30 | bulletIcon, 31 | actionIcon, 32 | classes 33 | }) => { 34 | return ( 35 |
    36 | 41 | 42 |
    43 |
    {title}
    44 | {subtitle} 45 |
    46 | 47 | {actionIcon && ( 48 | 49 | {actionIcon} 50 | 51 | )} 52 |
    53 | ); 54 | }; 55 | 56 | export default withStyles(styles, { withTheme: true })(MatxListItem1); 57 | -------------------------------------------------------------------------------- /src/matx/components/MatxLoadable/Loading.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import CircularProgress from "@material-ui/core/CircularProgress"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | loading: { 7 | position: "fixed", 8 | left: 0, 9 | right: 0, 10 | top: "calc(50% - 20px)", 11 | margin: "auto", 12 | height: "40px", 13 | width: "40px", 14 | "& img": { 15 | position: "absolute", 16 | height: "25px", 17 | width: "auto", 18 | top: 0, 19 | bottom: 0, 20 | left: 0, 21 | right: 0, 22 | margin: "auto" 23 | } 24 | } 25 | })); 26 | 27 | const Loading = props => { 28 | const classes = useStyles(); 29 | 30 | return ( 31 |
    32 | 33 | 34 |
    35 | ); 36 | }; 37 | 38 | export default Loading; 39 | -------------------------------------------------------------------------------- /src/matx/components/MatxLoadable/MatxLoadable.jsx: -------------------------------------------------------------------------------- 1 | import Loadable from "react-loadable"; 2 | import Loading from "./Loading"; 3 | 4 | const MatxLoadable = opts => { 5 | return Loadable( 6 | Object.assign( 7 | { 8 | loading: Loading, 9 | delay: 100, 10 | timeout: 10000 11 | }, 12 | opts 13 | ) 14 | ); 15 | }; 16 | 17 | export default MatxLoadable; 18 | -------------------------------------------------------------------------------- /src/matx/components/MatxLoading/MatxLoading.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import CircularProgress from "@material-ui/core/CircularProgress"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | loading: { 7 | position: "fixed", 8 | left: 0, 9 | right: 0, 10 | top: "calc(50% - 20px)", 11 | margin: "auto", 12 | height: "40px", 13 | width: "40px", 14 | "& img": { 15 | position: "absolute", 16 | height: "25px", 17 | width: "auto", 18 | top: 0, 19 | bottom: 0, 20 | left: 0, 21 | right: 0, 22 | margin: "auto" 23 | } 24 | } 25 | })); 26 | 27 | const Loading = props => { 28 | const classes = useStyles(); 29 | 30 | return ( 31 |
    32 | 33 | 34 |
    35 | ); 36 | }; 37 | 38 | export default Loading; 39 | -------------------------------------------------------------------------------- /src/matx/components/MatxMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import Menu from "@material-ui/core/Menu"; 3 | 4 | const MatxMenu = props => { 5 | const [anchorEl, setAnchorEl] = React.useState(null); 6 | const children = React.Children.toArray(props.children); 7 | let { shouldCloseOnItemClick = true, horizontalPosition = "left" } = props; 8 | 9 | const handleClick = event => { 10 | setAnchorEl(event.currentTarget); 11 | }; 12 | 13 | const handleClose = () => { 14 | setAnchorEl(null); 15 | }; 16 | 17 | return ( 18 | 19 |
    25 | {props.menuButton} 26 |
    27 | 42 | {children.map((child, index) => ( 43 |
    {}} 45 | key={index} 46 | > 47 | {child} 48 |
    49 | ))} 50 |
    51 |
    52 | ); 53 | }; 54 | 55 | export default MatxMenu; 56 | -------------------------------------------------------------------------------- /src/matx/components/MatxProgressBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, LinearProgress, Typography } from "@material-ui/core"; 3 | import { withStyles } from "@material-ui/styles"; 4 | 5 | const CustomLinearProgress = withStyles(theme => ({ 6 | root: { 7 | borderRadius: 2, 8 | background: "rgba(0, 0, 0, 0.1)" 9 | } 10 | }))(LinearProgress); 11 | 12 | const MatxProgressBar = ({ 13 | value = 75, 14 | color = "primary", 15 | text = "", 16 | spacing = 2, 17 | coloredText = false, 18 | className 19 | }) => { 20 | return ( 21 | 22 | 23 | 28 | 29 | {text !== "" && ( 30 | 31 | 32 | 33 | {text} 34 | 35 | 36 | 37 | )} 38 | 39 | ); 40 | }; 41 | 42 | export default MatxProgressBar; 43 | -------------------------------------------------------------------------------- /src/matx/components/MatxSearchBox.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Icon, IconButton } from "@material-ui/core"; 3 | import { withStyles } from "@material-ui/core/styles"; 4 | 5 | const styles = theme => ({ 6 | root: { 7 | backgroundColor: theme.palette.primary.main, 8 | color: theme.palette.primary.contrastText, 9 | "&::placeholder": { 10 | color: theme.palette.primary.contrastText 11 | } 12 | } 13 | }); 14 | 15 | class MatxSearchBox extends Component { 16 | state = { 17 | open: false 18 | }; 19 | 20 | toggle = () => { 21 | this.setState({ open: !this.state.open }); 22 | }; 23 | 24 | render() { 25 | let { classes } = this.props; 26 | return ( 27 | 28 | {!this.state.open && ( 29 | 30 | search 31 | 32 | )} 33 | 34 | {this.state.open && ( 35 |
    38 | 44 | 45 | close 46 | 47 |
    48 | )} 49 |
    50 | ); 51 | } 52 | } 53 | 54 | export default withStyles(styles, { withTheme: true })(MatxSearchBox); 55 | -------------------------------------------------------------------------------- /src/matx/components/MatxSidenav/MatxSidenav.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { isMobile } from "utils"; 3 | 4 | class MatxSidenav extends Component { 5 | handleResizeRef; 6 | 7 | state = { 8 | mobile: isMobile() 9 | }; 10 | 11 | handleWindowResize = () => { 12 | return event => { 13 | if (event.target.innerWidth < 768) { 14 | this.setState({ mobile: true }); 15 | } else this.setState({ mobile: false }); 16 | }; 17 | }; 18 | 19 | componentDidMount() { 20 | this.handleResizeRef = this.handleWindowResize(); 21 | if (window) window.addEventListener("resize", this.handleResizeRef); 22 | } 23 | 24 | componentWillUnmount() { 25 | if (this.handleResizeRef) 26 | window.removeEventListener("resize", this.handleResizeRef); 27 | } 28 | 29 | render() { 30 | let { 31 | open, 32 | children, 33 | toggleSidenav, 34 | width = "220px", 35 | bgClass 36 | } = this.props; 37 | 38 | let { mobile } = this.state; 39 | 40 | return ( 41 |
    42 |
    46 | {children} 47 |
    48 | {open && mobile && ( 49 |
    50 | )} 51 |
    52 | ); 53 | } 54 | } 55 | 56 | export default MatxSidenav; 57 | -------------------------------------------------------------------------------- /src/matx/components/MatxSidenav/MatxSidenavContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const MatxSidenavContainer = ({ children }) => { 4 | return
    {children}
    ; 5 | }; 6 | 7 | export default MatxSidenavContainer; 8 | -------------------------------------------------------------------------------- /src/matx/components/MatxSidenav/MatxSidenavContent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const MatxSidenavContent = ({ children }) => { 4 | return
    {children}
    ; 5 | }; 6 | 7 | export default MatxSidenavContent; 8 | -------------------------------------------------------------------------------- /src/matx/components/MatxSnackbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IconButton, Icon, Snackbar } from "@material-ui/core"; 3 | 4 | const MatxSnackbar = ({ 5 | open, 6 | message, 7 | duration = 6000, 8 | horizontal = "center", 9 | vertical = "bottom", 10 | handleClose 11 | }) => { 12 | return ( 13 | {message}} 25 | action={[ 26 | 33 | close 34 | 35 | ]} 36 | /> 37 | ); 38 | }; 39 | 40 | export default MatxSnackbar; 41 | -------------------------------------------------------------------------------- /src/matx/components/MatxSuspense/MatxSuspense.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { MatxLoading } from "matx"; 3 | 4 | const MatxSuspense = props => { 5 | return }>{props.children}; 6 | }; 7 | 8 | export default MatxSuspense; 9 | -------------------------------------------------------------------------------- /src/matx/components/MatxToolbarMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Icon, IconButton, Hidden } from "@material-ui/core"; 3 | import { classList } from "utils"; 4 | 5 | class MatxToolbarMenu extends Component { 6 | state = { 7 | open: false 8 | }; 9 | 10 | handleToggle = () => { 11 | this.setState({ open: !this.state.open }); 12 | }; 13 | 14 | render() { 15 | let { offsetTop, children } = this.props; 16 | 17 | return ( 18 |
    24 | 25 | 26 | {this.state.open ? "close" : "more_vert"} 27 | 28 | 29 | 30 |
    34 | {children} 35 |
    36 |
    37 | ); 38 | } 39 | } 40 | 41 | export default MatxToolbarMenu; 42 | -------------------------------------------------------------------------------- /src/matx/components/MatxVerticalNav/MatxVerticalNav.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | import { Icon } from "@material-ui/core"; 4 | import TouchRipple from "@material-ui/core/ButtonBase"; 5 | import MatxVerticalNavExpansionPanel from "./MatxVerticalNavExpansionPanel"; 6 | import { withStyles } from "@material-ui/styles"; 7 | import { useSelector } from "react-redux"; 8 | import { useTranslation } from "react-i18next"; 9 | 10 | const styles = (theme) => ({ 11 | expandIcon: { 12 | transition: "transform 225ms cubic-bezier(0, 0, 0.2, 1) 0ms", 13 | transform: "rotate(90deg)", 14 | }, 15 | collapseIcon: { 16 | transition: "transform 225ms cubic-bezier(0, 0, 0.2, 1) 0ms", 17 | transform: "rotate(0deg)", 18 | }, 19 | }); 20 | 21 | const MatxVerticalNav = (props) => { 22 | const navigations = useSelector(({ navigations }) => navigations); 23 | const { t } = useTranslation(); 24 | const renderLevels = (data) => { 25 | return data.map((item, index) => { 26 | if (item.children) { 27 | return ( 28 | 29 | {renderLevels(item.children)} 30 | 31 | ); 32 | } else if (item.type === "extLink") { 33 | return ( 34 | 41 | 42 | {(() => { 43 | if (item.icon) { 44 | return ( 45 | {item.icon} 46 | ); 47 | } else { 48 | return ( 49 | {item.iconText} 50 | ); 51 | } 52 | })()} 53 | {t(item.name)} 54 |
    55 | {item.badge && ( 56 |
    57 | {item.badge.value} 58 |
    59 | )} 60 |
    61 |
    62 | ); 63 | } else { 64 | return ( 65 | 66 | 67 | {(() => { 68 | if (item.icon) { 69 | return ( 70 | {item.icon} 71 | ); 72 | } else { 73 | return ( 74 | {item.iconText} 75 | ); 76 | } 77 | })()} 78 | {t(item.name)} 79 |
    80 | {item.badge && ( 81 |
    82 | {item.badge.value} 83 |
    84 | )} 85 |
    86 |
    87 | ); 88 | } 89 | }); 90 | }; 91 | 92 | return
    {renderLevels(navigations)}
    ; 93 | }; 94 | 95 | export default withStyles(styles)(MatxVerticalNav); 96 | -------------------------------------------------------------------------------- /src/matx/components/MatxVerticalNav/MatxVerticalNavExpansionPanel.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Icon } from "@material-ui/core"; 3 | import { withStyles } from "@material-ui/core/styles"; 4 | import TouchRipple from "@material-ui/core/ButtonBase"; 5 | import { withRouter } from "react-router-dom"; 6 | import { classList } from "utils"; 7 | 8 | const styles = theme => { 9 | return { 10 | expandIcon: { 11 | transition: "transform 0.3s cubic-bezier(0, 0, 0.2, 1) 0ms", 12 | transform: "rotate(90deg)" 13 | // marginRight: "16px" 14 | }, 15 | collapseIcon: { 16 | transition: "transform 0.3s cubic-bezier(0, 0, 0.2, 1) 0ms", 17 | transform: "rotate(0deg)" 18 | // marginRight: "16px" 19 | }, 20 | "expansion-panel": { 21 | overflow: "hidden", 22 | transition: "max-height 0.3s cubic-bezier(0, 0, 0.2, 1)" 23 | }, 24 | highlight: { 25 | background: theme.palette.primary.main 26 | } 27 | }; 28 | }; 29 | 30 | class MatxVerticalNavExpansionPanel extends Component { 31 | state = { 32 | collapsed: true 33 | }; 34 | elementRef = React.createRef(); 35 | 36 | componentHeight = 0; 37 | 38 | handleClick = () => { 39 | this.setState({ collapsed: !this.state.collapsed }); 40 | }; 41 | 42 | calcaulateHeight(node) { 43 | if (node.name !== "child") { 44 | for (let child of node.children) { 45 | this.calcaulateHeight(child); 46 | } 47 | } 48 | this.componentHeight += node.clientHeight; 49 | return; 50 | } 51 | componentDidMount() { 52 | let { location } = this.props; 53 | this.calcaulateHeight(this.elementRef); 54 | 55 | // OPEN DROPDOWN IF CHILD IS ACTIVE 56 | for (let child of this.elementRef.children) { 57 | if (child.getAttribute("href") === location.pathname) { 58 | this.setState({ collapsed: false }); 59 | } 60 | } 61 | } 62 | render() { 63 | let { collapsed } = this.state; 64 | let { classes, children } = this.props; 65 | let { name, icon, iconText, badge } = this.props.item; 66 | return ( 67 |
    68 | 75 |
    76 | {(icon && {icon})} 77 | {(iconText && {iconText})} 78 | {name} 79 |
    80 | {badge && ( 81 |
    {badge.value}
    82 | )} 83 |
    90 | chevron_right 91 |
    92 |
    93 | 94 |
    (this.elementRef = el)} 96 | className={classes["expansion-panel"] + " submenu"} 97 | style={ 98 | collapsed 99 | ? { maxHeight: "0px" } 100 | : { maxHeight: this.componentHeight + "px" } 101 | } 102 | > 103 | {children} 104 |
    105 |
    106 | ); 107 | } 108 | } 109 | 110 | export default withRouter(withStyles(styles)(MatxVerticalNavExpansionPanel)); 111 | -------------------------------------------------------------------------------- /src/matx/components/RectangleAvatar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon } from "@material-ui/core"; 3 | 4 | const RectangleAvatar = ({ color = "primary", icon, textIcon, style }) => { 5 | return ( 6 |
    10 | {textIcon ? ( 11 |
    {textIcon}
    12 | ) : ( 13 | {icon} 14 | )} 15 |
    16 | ); 17 | }; 18 | 19 | export default RectangleAvatar; 20 | -------------------------------------------------------------------------------- /src/matx/components/RichTextEditor.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import ReactQuill from "react-quill"; 4 | 5 | /* 6 | * Simple editor component that takes placeholder text as a prop 7 | */ 8 | 9 | const RichTextEditor = ({ content, placeholder, handleContentChange }) => { 10 | return ( 11 | 19 | ); 20 | }; 21 | 22 | /* 23 | * Quill modules to attach to editor 24 | * See https://quilljs.com/docs/modules/ for complete options 25 | */ 26 | RichTextEditor.modules = { 27 | toolbar: [ 28 | [{ font: [] }], 29 | [{ size: ["small", false, "large", "huge"] }], // custom dropdown 30 | [{ header: [1, 2, 3, 4, 5, 6, false] }], 31 | 32 | ["bold", "italic", "underline", "strike"], // toggled buttons 33 | ["blockquote", "code-block", "link"], 34 | 35 | [{ script: "sub" }, { script: "super" }], // superscript/subscript 36 | [{ color: [] }, { background: [] }], // dropdown with defaults from theme 37 | [{ align: [] }], 38 | 39 | ["image", "video"], 40 | 41 | [{ header: 1 }, { header: 2 }], // custom button values 42 | [{ list: "ordered" }, { list: "bullet" }], 43 | [{ indent: "-1" }, { indent: "+1" }], // outdent/indent 44 | [{ direction: "rtl" }], // text direction 45 | 46 | ["clean"] 47 | ], 48 | clipboard: { 49 | // toggle to add extra line breaks when pasting HTML: 50 | matchVisual: true 51 | } 52 | }; 53 | 54 | /* 55 | * Quill editor formats 56 | * See https://quilljs.com/docs/formats/ 57 | */ 58 | RichTextEditor.formats = [ 59 | "align", 60 | "background", 61 | "bold", 62 | "blockquote", 63 | "bullet", 64 | "color", 65 | "code", 66 | "code-block", 67 | "clean", 68 | "direction", 69 | "font", 70 | "header", 71 | "italic", 72 | "indent", 73 | "image", 74 | "list", 75 | "link", 76 | "size", 77 | "strike", 78 | "script", 79 | "underline", 80 | "video" 81 | ]; 82 | 83 | /* 84 | * PropType validation 85 | */ 86 | RichTextEditor.propTypes = { 87 | placeholder: PropTypes.string 88 | }; 89 | 90 | export default RichTextEditor; 91 | -------------------------------------------------------------------------------- /src/matx/components/cards/CardWidget1.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Card, Icon, Button, Divider } from "@material-ui/core"; 3 | 4 | const CardWidget1 = ({ backgroundClass }) => { 5 | return ( 6 | 12 |
    13 |
    14 | person 15 |
    16 |
    17 | 18 |
    19 |
    20 | 21 |
    22 |

    Last week

    23 |

    New Users

    24 |

    200

    25 |
    26 |
    27 | ); 28 | }; 29 | 30 | export default CardWidget1; 31 | -------------------------------------------------------------------------------- /src/matx/components/cards/SimpleCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Card } from "@material-ui/core"; 3 | import { classList } from "utils"; 4 | 5 | const SimpleCard = ({ children, title, subtitle, icon }) => { 6 | return ( 7 | 8 |
    14 | {title} 15 |
    16 | {subtitle &&
    {subtitle}
    } 17 | {children} 18 |
    19 | ); 20 | }; 21 | 22 | export default SimpleCard; 23 | -------------------------------------------------------------------------------- /src/matx/index.js: -------------------------------------------------------------------------------- 1 | export { default as Breadcrumb } from "./components/Breadcrumb"; 2 | export { default as MatxMenu } from "./components/MatxMenu"; 3 | export { default as MatxToolbarMenu } from "./components/MatxToolbarMenu"; 4 | export { default as MatxLoading } from "./components/MatxLoading/MatxLoading"; 5 | export { default as MatxSuspense } from "./components/MatxSuspense/MatxSuspense"; 6 | export { default as MatxSearchBox } from "./components/MatxSearchBox"; 7 | export { default as MatxVerticalNav } from "./components/MatxVerticalNav/MatxVerticalNav"; 8 | export { default as MatxVerticalNavExpansionPanel } from "./components/MatxVerticalNav/MatxVerticalNavExpansionPanel"; 9 | export { default as MatxHorizontalNav } from "./components/MatxHorizontalNav/MatxHorizontalNav"; 10 | export { default as MatxSidenavContainer } from "./components/MatxSidenav/MatxSidenavContainer"; 11 | export { default as MatxSidenav } from "./components/MatxSidenav/MatxSidenav"; 12 | export { default as MatxSidenavContent } from "./components/MatxSidenav/MatxSidenavContent"; 13 | 14 | export { default as RectangleAvatar } from "./components/RectangleAvatar"; 15 | export { default as MatxListItem1 } from "./components/MatxListItem1"; 16 | export { default as MatxSnackbar } from "./components/MatxSnackbar"; 17 | 18 | export { default as ConfirmationDialog } from "./components/ConfirmationDialog"; 19 | export { default as MatxProgressBar } from "./components/MatxProgressBar"; 20 | export { default as SimpleCard } from "./components/cards/SimpleCard"; 21 | -------------------------------------------------------------------------------- /src/matx/theme/EchartTheme.jsx: -------------------------------------------------------------------------------- 1 | import echarts from "echarts"; 2 | 3 | export const EchartTheme = MuiTheme => ({ 4 | backgroundColor: MuiTheme.palette.background.paper, 5 | // Global palette: 6 | color: [ 7 | "#7467EF", 8 | "#ABA4F4", 9 | "#D3D0F4", 10 | "rgba(0, 255, 33, 1)", 11 | "#91c7ae", 12 | "#749f83", 13 | "#ca8622", 14 | "#bda29a", 15 | "#6e7074", 16 | "#546570", 17 | "#c4ccd3" 18 | ], 19 | series: [ 20 | { 21 | type: "bar" 22 | // A palette only work for the series: 23 | // itemStyle: { 24 | // normal: { 25 | // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 26 | // { offset: 0, color: "#83bff6" }, 27 | // { offset: 0.5, color: "#188df0" }, 28 | // { offset: 1, color: "#188df0" } 29 | // ]) 30 | // }, 31 | // emphasis: { 32 | // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 33 | // { offset: 0, color: "#2378f7" }, 34 | // { offset: 0.7, color: "#2378f7" }, 35 | // { offset: 1, color: "#83bff6" } 36 | // ]) 37 | // } 38 | // } 39 | }, 40 | { 41 | type: "pie", 42 | // A palette only work for the series: 43 | color: [ 44 | "#37A2DA", 45 | "#32C5E9", 46 | "#67E0E3", 47 | "#9FE6B8", 48 | "#FFDB5C", 49 | "#ff9f7f", 50 | "#fb7293", 51 | "#E062AE", 52 | "#E690D1", 53 | "#e7bcf3", 54 | "#9d96f5", 55 | "#8378EA", 56 | "#96BFFF" 57 | ] 58 | }, 59 | { 60 | type: "line", 61 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 62 | { offset: 0, color: "#83bff6" }, 63 | { offset: 0.5, color: "#188df0" }, 64 | { offset: 1, color: "#188df0" } 65 | ]) 66 | } 67 | ] 68 | }); 69 | -------------------------------------------------------------------------------- /src/styles/_app.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | @import "./mixins"; 3 | @import "./reboot"; 4 | @import "./utilities/utilities"; 5 | @import "~perfect-scrollbar/css/perfect-scrollbar.css"; 6 | @import "~react-vis/dist/style"; 7 | @import "./components/index"; 8 | @import "./layouts/index"; 9 | @import "./views/index"; 10 | -------------------------------------------------------------------------------- /src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Spacing 2 | 3 | @mixin generate-margin-padding-in-rem($from, $to) { 4 | @for $i from $from through $to { 5 | .m-#{$i} { 6 | margin: #{$i * 0.25}rem; 7 | } 8 | .mt-#{$i} { 9 | margin-top: #{$i * 0.25}rem !important; 10 | } 11 | .mr-#{$i} { 12 | margin-right: #{$i * 0.25}rem !important; 13 | } 14 | .mb-#{$i} { 15 | margin-bottom: #{$i * 0.25}rem !important; 16 | } 17 | .ml-#{$i} { 18 | margin-left: #{$i * 0.25}rem !important; 19 | } 20 | .mx-#{$i} { 21 | margin-left: #{$i * 0.25}rem !important; 22 | margin-right: #{$i * 0.25}rem !important; 23 | } 24 | .my-#{$i} { 25 | margin-top: #{$i * 0.25}rem !important; 26 | margin-bottom: #{$i * 0.25}rem !important; 27 | } 28 | 29 | .p-#{$i} { 30 | padding: #{$i * 0.25}rem !important; 31 | } 32 | .pt-#{$i} { 33 | padding-top: #{$i * 0.25}rem !important; 34 | } 35 | .pr-#{$i} { 36 | padding-right: #{$i * 0.25}rem !important; 37 | } 38 | .pb-#{$i} { 39 | padding-bottom: #{$i * 0.25}rem !important; 40 | } 41 | .pl-#{$i} { 42 | padding-left: #{$i * 0.25}rem !important; 43 | } 44 | .px-#{$i} { 45 | padding-left: #{$i * 0.25}rem !important; 46 | padding-right: #{$i * 0.25}rem !important; 47 | } 48 | .py-#{$i} { 49 | padding-top: #{$i * 0.25}rem !important; 50 | padding-bottom: #{$i * 0.25}rem !important; 51 | } 52 | } 53 | } 54 | 55 | @mixin generate-margin-padding-in-px($from, $to) { 56 | @for $i from $from through $to { 57 | .m-#{$i}px { 58 | margin: #{$i}px; 59 | } 60 | .mt-#{$i}px { 61 | margin-top: #{$i}px !important; 62 | } 63 | .mr-#{$i}px { 64 | margin-right: #{$i}px !important; 65 | } 66 | .mb-#{$i}px { 67 | margin-bottom: #{$i}px !important; 68 | } 69 | .ml-#{$i}px { 70 | margin-left: #{$i}px !important; 71 | } 72 | .mx-#{$i}px { 73 | margin-left: #{$i}px !important; 74 | margin-right: #{$i}px !important; 75 | } 76 | .my-#{$i}px { 77 | margin-top: #{$i}px !important; 78 | margin-bottom: #{$i}px !important; 79 | } 80 | 81 | .p-#{$i}px { 82 | padding: #{$i}px !important; 83 | } 84 | .pt-#{$i}px { 85 | padding-top: #{$i}px !important; 86 | } 87 | .pr-#{$i}px { 88 | padding-right: #{$i}px !important; 89 | } 90 | .pb-#{$i}px { 91 | padding-bottom: #{$i}px !important; 92 | } 93 | .pl-#{$i}px { 94 | padding-left: #{$i}px !important; 95 | } 96 | .px-#{$i}px { 97 | padding-left: #{$i}px !important; 98 | padding-right: #{$i}px !important; 99 | } 100 | .py-#{$i}px { 101 | padding-top: #{$i}px !important; 102 | padding-bottom: #{$i}px !important; 103 | } 104 | } 105 | } 106 | 107 | @mixin generate-height-width($from, $to) { 108 | @for $i from $from through $to { 109 | @if $i % 4 == 0 { 110 | .w-#{$i} { 111 | width: #{$i}px !important; 112 | } 113 | .min-w-#{$i} { 114 | min-width: #{$i}px !important; 115 | } 116 | .max-w-#{$i} { 117 | max-width: #{$i}px !important; 118 | } 119 | .h-#{$i} { 120 | height: #{$i}px !important; 121 | } 122 | .min-h-#{$i} { 123 | min-height: #{$i}px !important; 124 | } 125 | .max-h-#{$i} { 126 | max-height: #{$i}px !important; 127 | } 128 | } 129 | } 130 | } 131 | 132 | // media 133 | @mixin media($width) { 134 | @media screen and (max-width: $width) { 135 | @content; 136 | } 137 | } 138 | 139 | // Animation 140 | @mixin keyframeMaker($name) { 141 | @keyframes #{$name} { 142 | @content; 143 | } 144 | @-webkit-keyframes #{$name} { 145 | @content; 146 | } 147 | @-o-keyframes #{$name} { 148 | @content; 149 | } 150 | @-moz-keyframes #{$name} { 151 | @content; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/styles/_reboot.scss: -------------------------------------------------------------------------------- 1 | #root, 2 | body, 3 | html { 4 | width: 100%; 5 | height: 100%; 6 | overflow: hidden; 7 | position: relative; 8 | } 9 | 10 | div, 11 | a { 12 | box-sizing: border-box; 13 | } 14 | 15 | img { 16 | max-width: 100%; 17 | } 18 | h1, 19 | h2, 20 | h3, 21 | h4, 22 | h5, 23 | h6, 24 | .card-title { 25 | color: $text-body !important; 26 | // font-weight: 500; 27 | } 28 | 29 | .layout1, 30 | .layout2, 31 | .MuiPaper-root, 32 | .MuiTableCell-body, 33 | .matx-customizer { 34 | color: $text-body !important; 35 | } 36 | 37 | code { 38 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 39 | monospace; 40 | color: rgba(255, 0, 221, 0.863); 41 | font-size: 16px; 42 | } -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $white: #ffffff; 3 | $black: rgba(0, 0, 0, 0.87); 4 | $muted: var(--text-muted); 5 | 6 | $primary: var(--primary); 7 | $secondary: var(--secondary); 8 | $error: var(--error); 9 | $brand: $primary; 10 | 11 | $bg-default: var(--bg-default); 12 | $bg-paper: var(--bg-paper); 13 | 14 | $text-body: var(--text-body); 15 | $text-muted: var(--text-muted); 16 | $text-disabled: var(--text-disabled); 17 | $text-hint: var(--text-hint); 18 | $light-gray: rgba(0, 0, 0, 0.08); 19 | 20 | // Layout 21 | $topbar-mobile-width: 220px; 22 | $topbar-height: 64px; 23 | $sidenav-width: 260px; 24 | $sidenav-button-width: 220px; 25 | $sidenav-compact-width: 80px; 26 | $contained-layout-width: 1200px; 27 | 28 | // Typography 29 | $font: var(--font); 30 | $font-h1: var(--font-h1); 31 | $font-h2: var(--font-h2); 32 | $font-h3: var(--font-h3); 33 | $font-h4: var(--font-h4); 34 | $font-h5: var(--font-h5); 35 | $font-h6: var(--font-h6); 36 | $font-caption: var(--font-caption); 37 | $font-overline: var(--font-overline); 38 | $font-button: var(--font-button); 39 | $font-body-1: var(--font-body-1); 40 | $font-body-2: var(--font-body-2); 41 | $font-subtitle-1: var(--font-subtitle-1); 42 | $font-subtitle-2: var(--font-subtitle-2); 43 | $font-heading: var(--font-heading); 44 | $font-title: var(--font-title); 45 | $font-display-1: var(--font-display-1); 46 | $font-display-2: var(--font-display-2); 47 | $font-display-3: var(--font-display-3); 48 | $font-display-4: var(--font-display-4); 49 | 50 | // box shadow 51 | $elevation-z0: var(--elevation-z0); 52 | $elevation-z1: var(--elevation-z1); 53 | $elevation-z2: var(--elevation-z2); 54 | $elevation-z3: var(--elevation-z3); 55 | $elevation-z4: var(--elevation-z4); 56 | $elevation-z5: var(--elevation-z5); 57 | $elevation-z6: var(--elevation-z6); 58 | $elevation-z7: var(--elevation-z7); 59 | $elevation-z8: var(--elevation-z8); 60 | $elevation-z9: var(--elevation-z9); 61 | $elevation-z10: var(--elevation-z10); 62 | $elevation-z11: var(--elevation-z11); 63 | $elevation-z12: var(--elevation-z12); 64 | $elevation-z13: var(--elevation-z13); 65 | $elevation-z14: var(--elevation-z14); 66 | $elevation-z15: var(--elevation-z15); 67 | $elevation-z16: var(--elevation-z16); 68 | $elevation-z17: var(--elevation-z17); 69 | $elevation-z18: var(--elevation-z18); 70 | $elevation-z19: var(--elevation-z19); 71 | $elevation-z20: var(--elevation-z20); 72 | $elevation-z21: var(--elevation-z21); 73 | $elevation-z22: var(--elevation-z22); 74 | $elevation-z23: var(--elevation-z23); 75 | $elevation-z24: var(--elevation-z24); 76 | -------------------------------------------------------------------------------- /src/styles/components/_avatar.scss: -------------------------------------------------------------------------------- 1 | .rectangle-box { 2 | height: 40px; 3 | width: 40px; 4 | min-width: 40px; 5 | background-color: rgba(255, 255, 255, 0.1); 6 | border-radius: 8px; 7 | overflow: hidden; 8 | .MuiIcon-root { 9 | font-size: 18px; 10 | } 11 | } -------------------------------------------------------------------------------- /src/styles/components/_customizer.scss: -------------------------------------------------------------------------------- 1 | .matx-customizer { 2 | display: flex; 3 | flex-direction: column; 4 | width: 320px; 5 | position: fixed; 6 | right: 0; 7 | box-shadow: $elevation-z12; 8 | z-index: 50; 9 | top: 0; 10 | height: 100vh; 11 | .customizer-close { 12 | position: absolute; 13 | right: 8px; 14 | top: 8px; 15 | } 16 | .layout-boxes { 17 | display: flex; 18 | flex-wrap: wrap; 19 | flex-direction: column; 20 | &.sidebar-bg { 21 | flex-direction: row; 22 | } 23 | // margin: 0 -8px; 24 | .layout-box { 25 | width: 100%; 26 | margin: 12px 0; 27 | max-height: 150px; 28 | cursor: pointer; 29 | > div { 30 | overflow: hidden; 31 | display: flex; 32 | position: relative; 33 | // height: 76px; 34 | width: 100%; 35 | &:hover { 36 | &::before, 37 | .layout-name { 38 | display: block; 39 | } 40 | } 41 | &::before, 42 | .layout-name { 43 | text-align: center; 44 | position: absolute; 45 | top: 0; 46 | left: 0; 47 | right: 0; 48 | display: none; 49 | } 50 | &::before { 51 | content: " "; 52 | width: 100%; 53 | height: 100%; 54 | background: rgba(0,0,0,0.3); 55 | } 56 | .layout-name { 57 | color: #ffffff; 58 | top: calc(50% - 18px) 59 | } 60 | img { 61 | // position: absolute; 62 | top: 0; 63 | left: 0; 64 | } 65 | } 66 | } 67 | } 68 | .colors { 69 | display: flex; 70 | flex-wrap: wrap; 71 | .color { 72 | position: relative; 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | height: 40px; 77 | width: 40px; 78 | margin-top: 4px; 79 | margin-right: 12px; 80 | margin-bottom: 12px; 81 | cursor: pointer; 82 | border-radius: 4px; 83 | overflow: hidden; 84 | box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 2px 1px -1px rgba(0,0,0,0.12); 85 | .light, .dark { 86 | position: absolute; 87 | border: 12px solid transparent; 88 | transform: rotate(45deg); 89 | bottom: -12px; 90 | left: -12px; 91 | border-radius: 50%; 92 | } 93 | .light { 94 | border-top-color: rgba(215, 215, 215, 0.6); 95 | } 96 | .dark { 97 | // border-top-color: rgb(34, 41, 69); 98 | border-top-color: rgba(0, 0, 0, .5); 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/styles/components/_index.scss: -------------------------------------------------------------------------------- 1 | @import "./customizer.scss"; 2 | @import "./loader.scss"; 3 | @import "./matx-sidenav.scss"; 4 | @import "./matx-search-box.scss"; 5 | @import "./matx-tootbar-menu.scss"; 6 | @import "./notification.scss"; 7 | @import "./avatar.scss"; 8 | @import "./list.scss"; 9 | @import "./shopping-cart"; 10 | -------------------------------------------------------------------------------- /src/styles/components/_list.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/069dd3c1d72391db25847392eba49a08ea574f6d/src/styles/components/_list.scss -------------------------------------------------------------------------------- /src/styles/components/_loader.scss: -------------------------------------------------------------------------------- 1 | .loader-bounce { 2 | height: 100vh !important; 3 | width: 100%; 4 | // position: absolute; 5 | display: flex; 6 | align-items: center; 7 | } 8 | .spinner { 9 | width: 40px; 10 | height: 40px; 11 | position: relative; 12 | margin: auto; 13 | } 14 | .double-bounce1, 15 | .double-bounce2 { 16 | width: 100%; 17 | height: 100%; 18 | border-radius: 50%; 19 | opacity: 0.6; 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | -webkit-animation: sk-bounce 2s infinite ease-in-out; 24 | animation: sk-bounce 2s infinite ease-in-out; 25 | } 26 | .double-bounce2 { 27 | -webkit-animation-delay: -1s; 28 | animation-delay: -1s; 29 | } 30 | @-webkit-keyframes sk-bounce { 31 | 0%, 32 | 100% { 33 | -webkit-transform: scale(0); 34 | } 35 | 50% { 36 | -webkit-transform: scale(1); 37 | } 38 | } 39 | @keyframes sk-bounce { 40 | 0%, 41 | 100% { 42 | transform: scale(0); 43 | -webkit-transform: scale(0); 44 | } 45 | 50% { 46 | transform: scale(1); 47 | -webkit-transform: scale(1); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/styles/components/_matx-search-box.scss: -------------------------------------------------------------------------------- 1 | .matx-search-box { 2 | position: absolute; 3 | width: 100%; 4 | left: 0; 5 | z-index: 9; 6 | .search-box { 7 | outline: none; 8 | border: none; 9 | font-size: 1rem; 10 | height: calc(100% - 5px); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/components/_matx-sidenav.scss: -------------------------------------------------------------------------------- 1 | .matx-sidenav-container { 2 | position: relative; 3 | display: flex; 4 | flex-direction: row; 5 | height: 100%; 6 | 7 | .matx-sidenav { 8 | position: relative; 9 | transition: width 250ms ease; 10 | overflow: hidden; 11 | z-index: 91; 12 | 13 | @include media(767px) { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | bottom: 0; 18 | } 19 | } 20 | 21 | .matx-sidenav-content { 22 | position: relative; 23 | flex: 1 1 0; 24 | height: 100%; 25 | } 26 | 27 | .matx-sidenav-overlay { 28 | position: absolute; 29 | width: 100%; 30 | height: 100%; 31 | background: rgba(0, 0, 0, 0.74); 32 | z-index: 90; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/styles/components/_matx-tootbar-menu.scss: -------------------------------------------------------------------------------- 1 | .toolbar-menu-wrap { 2 | position: relative; 3 | .menu-area { 4 | @include media(959px) { 5 | position: fixed; 6 | background: #1a2038; 7 | height: 60px; 8 | width: 100%; 9 | left: 0; 10 | z-index: -10; 11 | opacity: 0; 12 | display: none; 13 | transition: all 0.15s ease; 14 | justify-content: flex-end; 15 | } 16 | } 17 | &.open { 18 | .menu-area { 19 | z-index: 9; 20 | opacity: 1; 21 | display: flex; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/components/_notification.scss: -------------------------------------------------------------------------------- 1 | .notification { 2 | width: $sidenav-width; 3 | .notification__topbar { 4 | height: $topbar-height; 5 | box-shadow: $elevation-z6; 6 | } 7 | 8 | .notification__card { 9 | &:hover { 10 | .delete-button { 11 | cursor: pointer; 12 | display: unset; 13 | right: 0; 14 | margin-top: 6px; 15 | top: 0; 16 | z-index: 2; 17 | } 18 | .card__topbar__time { 19 | display: none; 20 | } 21 | } 22 | .card__topbar { 23 | } 24 | 25 | .delete-button { 26 | display: none; 27 | position: absolute; 28 | right: 0; 29 | margin-top: 9px; 30 | } 31 | .card__topbar__button { 32 | height: 24px; 33 | width: 24px; 34 | border-radius: 15px; 35 | overflow: hidden; 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | opacity: 0.9; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/styles/components/_shopping-cart.scss: -------------------------------------------------------------------------------- 1 | .mini-cart { 2 | width: $sidenav-width; 3 | .cart__topbar { 4 | height: $topbar-height; 5 | box-shadow: $elevation-z6; 6 | } 7 | .mini-cart__item { 8 | transition: background 300ms ease; 9 | &:hover { 10 | background: $light-gray; 11 | } 12 | img { 13 | width: 80px; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/styles/layouts/_index.scss: -------------------------------------------------------------------------------- 1 | @import "./shared/index"; 2 | 3 | @import "./layout1/index"; 4 | @import "./layout2/index"; 5 | -------------------------------------------------------------------------------- /src/styles/layouts/layout1/_index.scss: -------------------------------------------------------------------------------- 1 | @import 'layout1'; -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_index.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'layout2'; 3 | @import 'topbar'; 4 | @import 'navbar'; -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_layout2.scss: -------------------------------------------------------------------------------- 1 | .layout2 { 2 | flex: 1 1 auto; 3 | display: flex; 4 | overflow: hidden; 5 | position: relative; 6 | flex-direction: column; 7 | height: 100%; 8 | transition: all .15s ease; 9 | .scrollable-content { 10 | display: flex; 11 | flex-direction: column; 12 | flex: 1 1; 13 | width: 100%; 14 | overflow-y: auto; 15 | } 16 | &.sidenav-close { 17 | .sidenav { 18 | // width: 0px; 19 | left: -#{$sidenav-width} 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_navbar.scss: -------------------------------------------------------------------------------- 1 | $item-x-padding: 20px; 2 | 3 | .layout2 { 4 | .navbar { 5 | position: relative; 6 | height: $navbar-height; 7 | box-shadow: $elevation-z8; 8 | z-index: 98; 9 | } 10 | } 11 | 12 | .horizontal-nav { 13 | ul { 14 | padding: 0; 15 | margin: 0; 16 | list-style: none; 17 | position: relative; 18 | } 19 | ul.menu { 20 | float: left; 21 | padding-right: 45px; 22 | margin-left: -#{$item-x-padding}; 23 | z-index: 99; 24 | > li { 25 | float: left; 26 | > div { 27 | > a, 28 | > div { 29 | border-bottom: 2px solid; 30 | height: 48px; 31 | box-sizing: border-box; 32 | border-color: transparent; 33 | margin: 0 6px; 34 | } 35 | } 36 | } 37 | } 38 | ul li { 39 | position: relative; 40 | margin: 0px; 41 | display: inline-block; 42 | ul a { 43 | padding: 8px $item-x-padding; 44 | height: 48px; 45 | } 46 | } 47 | 48 | a, 49 | label { 50 | display: flex; 51 | flex-direction: row; 52 | align-items: center; 53 | padding: 13px $item-x-padding; 54 | height: $navbar-height; 55 | font-size: 0.875rem; 56 | text-decoration: none; 57 | box-sizing: border-box; 58 | // color: $white; 59 | .material-icons { 60 | font-size: 14px; 61 | margin: 0 4px; 62 | } 63 | } 64 | 65 | 66 | ul ul { 67 | opacity: 0; 68 | visibility: hidden; 69 | position: absolute; 70 | 71 | /* has to be the same number as the "line-height" of "nav a" */ 72 | left: $item-x-padding; 73 | box-shadow: $elevation-z8; 74 | top: 60px; 75 | transform: translateY(-10px); 76 | transition: all 0.3s ease-in-out; 77 | z-index: -1; 78 | } 79 | 80 | ul li:hover > div > div > ul, 81 | ul li:hover > div > ul, 82 | li:hover > ul { 83 | opacity: 1; 84 | visibility: visible; 85 | transform: translateY(0); 86 | } 87 | 88 | ul ul li { 89 | width: 170px; 90 | float: none; 91 | display: list-item; 92 | position: relative; 93 | } 94 | ul ul ul { 95 | top: 0; 96 | left: 170px; 97 | } 98 | ul ul ul li { 99 | position: relative; 100 | top: 0; 101 | } 102 | 103 | li > a:after { 104 | content: "arrow_drop_down"; 105 | font-family: 'Material Icons'; 106 | font-weight: normal; 107 | font-style: normal; 108 | font-size: 14px; 109 | line-height: 1; 110 | margin-left: auto; 111 | letter-spacing: normal; 112 | text-transform: none; 113 | display: inline-block; 114 | white-space: nowrap; 115 | word-wrap: normal; 116 | direction: ltr; 117 | -webkit-font-feature-settings: 'liga'; 118 | -webkit-font-smoothing: antialiased; 119 | } 120 | li > a:only-child:after { 121 | content: ""; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_topbar.scss: -------------------------------------------------------------------------------- 1 | .layout2 { 2 | .topbar { 3 | position: relative; 4 | width: 100%; 5 | display: table; 6 | height: $topbar-height; 7 | border-bottom: 1px solid transparent; 8 | padding-top: 1rem; 9 | padding-bottom: 1rem; 10 | z-index: 98; 11 | .brand { 12 | height: 100%; 13 | img { 14 | height: 32px; 15 | } 16 | .brand__text { 17 | font-weight: 500; 18 | font-size: 1.5rem; 19 | margin: 0 1rem; 20 | } 21 | } 22 | 23 | .MuiIconButton-root { 24 | color: $white; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_variables.scss: -------------------------------------------------------------------------------- 1 | $topbar-height: 80px; 2 | $navbar-height: 60px; -------------------------------------------------------------------------------- /src/styles/layouts/shared/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | min-height: $topbar-height; 3 | 4 | @include media(480px) { 5 | display: table; 6 | width: 100%; 7 | min-height: auto; 8 | padding: 1rem 0; 9 | .container { 10 | flex-direction: column !important; 11 | a { 12 | margin: 0 0 16px !important; 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/styles/layouts/shared/_index.scss: -------------------------------------------------------------------------------- 1 | @import "layout"; 2 | @import "sidenav"; 3 | @import "footer"; -------------------------------------------------------------------------------- /src/styles/layouts/shared/_layout.scss: -------------------------------------------------------------------------------- 1 | .layout-full { 2 | .container { 3 | padding-left: 30px; 4 | padding-right: 30px; 5 | } 6 | } 7 | 8 | .layout-contained, .layout-boxed { 9 | .container { 10 | padding-left: 30px; 11 | padding-right: 30px; 12 | } 13 | } 14 | 15 | 16 | .layout-contained { 17 | .container { 18 | max-width: $contained-layout-width; 19 | margin: auto; 20 | width: 100%; 21 | @include media(767px) { 22 | max-width: 100%; 23 | } 24 | } 25 | } 26 | 27 | .layout-boxed { 28 | max-width: $contained-layout-width; 29 | margin: auto; 30 | box-shadow: $elevation-z12; 31 | background: $white; 32 | @include media(767px) { 33 | max-width: 100%; 34 | box-shadow: none; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/utilities/_animations.scss: -------------------------------------------------------------------------------- 1 | .fade-in { 2 | @include keyframeMaker(fade-in) { 3 | from { 4 | opacity: 0; 5 | } 6 | to { 7 | opacity: 1; 8 | } 9 | } 10 | animation: fade-in 1s #{bezier()}; 11 | } 12 | 13 | @keyframes spin { 14 | 0% {transform: rotate(0)} 15 | 100% {transform: rotate(360deg)} 16 | } 17 | 18 | .spin { 19 | animation: spin 3s infinite linear; 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/utilities/_border.scss: -------------------------------------------------------------------------------- 1 | .border-radius-0 { 2 | border-radius: 0px !important; 3 | overflow: hidden; 4 | } 5 | .border-radius-4 { 6 | border-radius: 4px !important; 7 | overflow: hidden; 8 | } 9 | .border-radius-8 { 10 | border-radius: 8px !important; 11 | overflow: hidden; 12 | } 13 | .border-radius-circle { 14 | border-radius: 50% !important; 15 | } 16 | .border-none { 17 | border: none !important; 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/utilities/_carousel.scss: -------------------------------------------------------------------------------- 1 | .swiper-slide { 2 | height: auto; 3 | } 4 | 5 | .swiper-pagination-bullet { 6 | opacity: 1; 7 | background: rgba(0, 0, 0, 0.55); 8 | transition: transform 400ms cubic-bezier(0.17, 0.67, 0.83, 0.67); 9 | } 10 | 11 | .bullet-active { 12 | transform: scale(1.8); 13 | } 14 | 15 | .carousel__button-next, 16 | .carousel__button-prev { 17 | position: absolute !important; 18 | top: 50%; 19 | transform: translateY(calc(-50% - 50px)); 20 | z-index: 1; 21 | } 22 | .carousel__button-prev { 23 | left: 0px; 24 | } 25 | .carousel__button-next { 26 | right: 0px; 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/utilities/_color.scss: -------------------------------------------------------------------------------- 1 | .bg-primary { 2 | background: $primary !important; 3 | } 4 | .bg-secondary { 5 | background: $secondary !important; 6 | } 7 | .bg-green { 8 | background-color: rgba($color: green, $alpha: 0.75) !important; 9 | } 10 | .bg-error { 11 | background: $error !important; 12 | } 13 | .bg-white { 14 | background: #fff !important; 15 | color: inherit; 16 | } 17 | .bg-default { 18 | background: $bg-default !important; 19 | } 20 | .bg-paper { 21 | background: $bg-paper; 22 | } 23 | .bg-light-gray { 24 | background: $light-gray !important; 25 | } 26 | .bg-dark { 27 | background: #000000; 28 | color: #fff; 29 | } 30 | .bg-light-dark { 31 | background: #212121; 32 | color: white; 33 | } 34 | .bg-error { 35 | background: $error !important; 36 | color: white !important; 37 | } 38 | .bg-green { 39 | background: #08ad6c !important; 40 | } 41 | .bg-light-primary { 42 | &::after { 43 | background: $primary; 44 | } 45 | } 46 | .bg-light-secondary { 47 | position: relative; 48 | z-index: 0; 49 | &::after { 50 | background: $secondary; 51 | } 52 | } 53 | .bg-light-error { 54 | position: relative; 55 | z-index: 0; 56 | &::after { 57 | background: $error; 58 | } 59 | } 60 | .bg-light-green { 61 | background: rgba($color: #08ad6c, $alpha: 0.5) !important; 62 | } 63 | 64 | [class^="bg-light-"], 65 | [class*=" bg-light-"] { 66 | position: relative; 67 | z-index: 0; 68 | &::after { 69 | content: ""; 70 | position: absolute; 71 | top: 0; 72 | right: 0; 73 | bottom: 0; 74 | left: 0; 75 | opacity: 0.15; 76 | z-index: -1; 77 | border-radius: 8px; 78 | } 79 | } 80 | 81 | .bg-transperant { 82 | background: transparent !important; 83 | } 84 | 85 | .text-white { 86 | color: #fff !important; 87 | } 88 | .text-white-secondary { 89 | color: rgba(#fff, 0.87) !important; 90 | } 91 | .text-muted-white { 92 | color: rgba(#fff, 0.54) !important; 93 | } 94 | .text-light-white { 95 | color: rgba(255, 255, 255, 0.54) !important; 96 | } 97 | .text-muted { 98 | color: $text-muted !important; 99 | } 100 | .text-hint { 101 | color: $text-hint !important; 102 | } 103 | .text-gray { 104 | color: rgba(0, 0, 0, 0.74) !important; 105 | } 106 | .text-brand { 107 | color: $brand !important; 108 | } 109 | .text-primary { 110 | color: $primary !important; 111 | } 112 | .text-secondary { 113 | color: $secondary !important; 114 | } 115 | .text-green { 116 | color: #08ad6c !important; 117 | } 118 | .text-error { 119 | color: $error !important; 120 | } 121 | 122 | .gray-on-hover { 123 | transition: background 250ms ease; 124 | &:hover { 125 | background: rgba(0, 0, 0, 0.054); 126 | } 127 | } 128 | 129 | // Border color 130 | .border-color-white { 131 | border-color: #ffffff !important; 132 | } 133 | .border-color-default { 134 | border-color: $bg-default !important; 135 | } 136 | .border-color-paper { 137 | border-color: $bg-paper !important; 138 | } 139 | -------------------------------------------------------------------------------- /src/styles/utilities/_common.scss: -------------------------------------------------------------------------------- 1 | .circular-image-small { 2 | height: 48px; 3 | width: 48px; 4 | border-radius: 50%; 5 | overflow: hidden; 6 | } 7 | .card { 8 | transition: all 0.3s ease; 9 | &:hover { 10 | box-shadow: $elevation-z12; 11 | } 12 | } 13 | .card-title { 14 | font-size: 1rem; 15 | text-transform: capitalize; 16 | font-weight: 500; 17 | } 18 | .card-subtitle { 19 | font-size: 0.875rem; 20 | color: $muted; 21 | .theme-dark & { 22 | color: rgba(#fff, 0.54); 23 | } 24 | } 25 | 26 | .hide-on-mobile { 27 | display: inherit; 28 | @media screen and (max-width: 767px) { 29 | display: none !important; 30 | } 31 | } 32 | 33 | .hide-on-pc { 34 | @media screen and (min-width: 1200px) { 35 | display: none !important; 36 | } 37 | } 38 | 39 | .show-on-pc { 40 | @media screen and (max-width: 1200px) { 41 | display: none !important; 42 | } 43 | } 44 | 45 | .VictoryContainer { 46 | svg { 47 | height: 100% !important; 48 | } 49 | } 50 | 51 | .box-shadow-none { 52 | box-shadow: none !important; 53 | } 54 | 55 | .circle-44 { 56 | height: 44px !important; 57 | width: 44px !important; 58 | } 59 | 60 | .circle-32 { 61 | height: 32px !important; 62 | min-height: 32px !important; 63 | width: 32px !important; 64 | 65 | .MuiFab-root { 66 | min-height: 32px !important; 67 | } 68 | .MuiIcon-root { 69 | font-size: 13px !important; 70 | } 71 | } 72 | 73 | .show-on-mobile { 74 | display: none !important; 75 | @include media(767px) { 76 | display: inherit !important; 77 | } 78 | } 79 | .invisible-on-pc { 80 | visibility: hidden; 81 | @include media(767px) { 82 | visibility: visible; 83 | } 84 | } 85 | 86 | .highlight-js { 87 | pre { 88 | white-space: pre-line; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/styles/utilities/_functions.scss: -------------------------------------------------------------------------------- 1 | @function bezier() { 2 | @return cubic-bezier(0.17, 0.67, 0.83, 0.67); 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/utilities/_misc.scss: -------------------------------------------------------------------------------- 1 | .cursor-pointer { 2 | cursor: pointer; 3 | } 4 | .cursor-move { 5 | cursor: move; 6 | } -------------------------------------------------------------------------------- /src/styles/utilities/_positionings.scss: -------------------------------------------------------------------------------- 1 | // display 2 | .hidden { 3 | display: none; 4 | } 5 | .block { 6 | display: block; 7 | } 8 | .inline-block { 9 | display: inline-block !important; 10 | } 11 | 12 | .flex { 13 | display: flex; 14 | } 15 | .flex-column { 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | .flex-wrap { 20 | flex-wrap: wrap; 21 | } 22 | .justify-start { 23 | justify-content: flex-start !important; 24 | } 25 | .justify-center { 26 | justify-content: center; 27 | } 28 | .justify-end { 29 | justify-content: flex-end; 30 | } 31 | .justify-between { 32 | justify-content: space-between !important; 33 | } 34 | .justify-around { 35 | justify-content: space-around; 36 | } 37 | .items-center { 38 | align-items: center; 39 | } 40 | .items-start { 41 | align-items: flex-start; 42 | } 43 | .items-end { 44 | align-items: flex-end; 45 | } 46 | .items-stretch { 47 | align-items: stretch; 48 | } 49 | .flex-grow { 50 | flex-grow: 1; 51 | } 52 | .overflow-auto { 53 | overflow: auto !important; 54 | } 55 | .overflow-hidden { 56 | overflow: hidden; 57 | } 58 | .scroll-y { 59 | overflow-x: hidden; 60 | overflow-y: scroll; 61 | } 62 | 63 | // postions 64 | .position-relative { 65 | position: relative; 66 | } 67 | .position-bottom { 68 | position: absolute; 69 | bottom: 0; 70 | } 71 | .text-center { 72 | text-align: center; 73 | } 74 | .align-middle { 75 | vertical-align: middle; 76 | } 77 | .text-right { 78 | text-align: right; 79 | } 80 | .text-left { 81 | text-align: left; 82 | } 83 | .x-center { 84 | left: 50%; 85 | transform: translateX(-50%); 86 | } 87 | .y-center { 88 | top: 50%; 89 | transform: translateY(-50%); 90 | } 91 | -------------------------------------------------------------------------------- /src/styles/utilities/_shadow.scss: -------------------------------------------------------------------------------- 1 | @for $i from 0 through 24 { 2 | .elevation-z#{$i} { 3 | box-shadow: var(--elevation-z#{$i}); 4 | } 5 | } -------------------------------------------------------------------------------- /src/styles/utilities/_spacing.scss: -------------------------------------------------------------------------------- 1 | @include generate-margin-padding-in-rem(0, 25); 2 | @include generate-margin-padding-in-rem(0, -25); 3 | @include generate-margin-padding-in-px(1, 16); 4 | 5 | .px-80 { 6 | padding-right: 80px; 7 | padding-left: 80px; 8 | @media screen and (max-width: 460px) { 9 | padding-right: 16px; 10 | padding-left: 16px; 11 | } 12 | } 13 | 14 | .px-sm-30 { 15 | padding: 0px 30px; 16 | @include media(767px) { 17 | padding: 0px 16px; 18 | } 19 | } 20 | .p-sm-24 { 21 | padding: 24px !important; 22 | @include media(767px) { 23 | padding: 16px !important; 24 | } 25 | } 26 | .px-sm-24 { 27 | padding: 0px 24px !important; 28 | @include media(767px) { 29 | padding: 0px 16px !important; 30 | } 31 | } 32 | .pt-sm-24 { 33 | padding-top: 24px !important; 34 | @include media(767px) { 35 | padding-top: 16px !important; 36 | } 37 | } 38 | .pl-sm-24 { 39 | padding-left: 24px !important; 40 | @include media(767px) { 41 | padding: 12px !important; 42 | } 43 | } 44 | 45 | .m-auto { 46 | margin: auto !important; 47 | } 48 | .mx-auto { 49 | margin-left: auto !important; 50 | margin-right: auto !important; 51 | } 52 | .my-auto { 53 | margin-top: auto !important; 54 | margin-bottom: auto !important; 55 | } 56 | 57 | .m-sm-30 { 58 | margin: 30px; 59 | @include media(767px) { 60 | margin: 16px; 61 | } 62 | } 63 | .mb-sm-30 { 64 | margin-bottom: 30px; 65 | @include media(767px) { 66 | margin-bottom: 16px; 67 | } 68 | } 69 | 70 | @include generate-height-width(0, 400); 71 | 72 | .w-full { 73 | width: 100%; 74 | } 75 | .w-full-screen { 76 | width: 100vw; 77 | } 78 | .min-w-750 { 79 | min-width: 750px; 80 | } 81 | .max-w-770 { 82 | max-width: 770px; 83 | } 84 | 85 | .h-full { 86 | height: 100% !important; 87 | } 88 | .h-full-screen { 89 | height: 100vh; 90 | } 91 | .h-150px { 92 | height: 150px !important; 93 | } 94 | 95 | .size-36 { 96 | height: 36px !important; 97 | width: 36px !important; 98 | } 99 | .size-24 { 100 | height: 24px !important; 101 | width: 24px !important; 102 | } 103 | -------------------------------------------------------------------------------- /src/styles/utilities/_typography.scss: -------------------------------------------------------------------------------- 1 | .h1, 2 | .h2, 3 | .h3, 4 | .h4, 5 | .h5, 6 | .h6, 7 | h1, 8 | h2, 9 | h3, 10 | h4, 11 | h5, 12 | h6 { 13 | margin: 0 0 0.5rem; 14 | line-height: 1.1; 15 | color: inherit; 16 | } 17 | .h1, 18 | h1 { 19 | font-size: 2rem; 20 | } 21 | .h2, 22 | h2 { 23 | font-size: 1.75rem; 24 | } 25 | .h3, 26 | h3 { 27 | font-size: 1.5rem; 28 | } 29 | .h4, 30 | h4 { 31 | font-size: 1.25rem; 32 | } 33 | .h5, 34 | h5 { 35 | font-size: 1rem; 36 | } 37 | .h6, 38 | h6 { 39 | font-size: 0.875rem; 40 | } 41 | 42 | a { 43 | text-decoration: none; 44 | color: inherit; 45 | } 46 | 47 | .caption { 48 | font: $font-caption; 49 | } 50 | .subtitle-1 { 51 | font: $font-subtitle-1; 52 | } 53 | .subtitle-2 { 54 | font: $font-subtitle-2; 55 | } 56 | .heading { 57 | font: $font-heading; 58 | } 59 | .title { 60 | font: $font-title; 61 | } 62 | .display-1 { 63 | font: $font-display-1; 64 | } 65 | .display-2 { 66 | font: $font-display-2; 67 | } 68 | .display-3 { 69 | font: $font-display-3; 70 | } 71 | .display-4 { 72 | font: $font-display-4; 73 | } 74 | 75 | .capitalize { 76 | text-transform: capitalize !important; 77 | } 78 | .uppercase { 79 | text-transform: uppercase !important; 80 | } 81 | .lowercase { 82 | text-transform: lowercase !important; 83 | } 84 | 85 | // font weight 86 | .font-normal { 87 | font-weight: normal !important; 88 | } 89 | .font-light { 90 | font-weight: 300 !important; 91 | } 92 | .font-medium { 93 | font-weight: 500 !important; 94 | } 95 | .font-semibold { 96 | font-weight: 600 !important; 97 | } 98 | .font-bold { 99 | font-weight: 700 !important; 100 | } 101 | 102 | // font size 103 | 104 | .text-13 { 105 | font-size: 13px; 106 | } 107 | .text-14 { 108 | font-size: 14px; 109 | } 110 | .text-16 { 111 | font-size: 16px; 112 | } 113 | .text-18 { 114 | font-size: 18px; 115 | } 116 | .text-20 { 117 | font-size: 20px; 118 | } 119 | .text-22 { 120 | font-size: 22px; 121 | } 122 | .text-24 { 123 | font-size: 24px; 124 | } 125 | .text-30 { 126 | font-size: 30px !important; 127 | } 128 | .text-32 { 129 | font-size: 32px; 130 | } 131 | .text-small { 132 | font-size: 0.8125rem !important; // 13px 133 | } 134 | 135 | .whitespace-pre-wrap { 136 | white-space: pre-wrap; 137 | word-break: break-word; 138 | } 139 | 140 | .whitespace-pre { 141 | white-space: pre; 142 | } 143 | .whitespace-no-wrap { 144 | white-space: nowrap; 145 | } 146 | -------------------------------------------------------------------------------- /src/styles/utilities/_utilities.scss: -------------------------------------------------------------------------------- 1 | @import "../mixins"; 2 | @import "./spacing"; 3 | @import "./color.scss"; 4 | @import "./typography"; 5 | @import "./functions"; 6 | @import "./animations"; 7 | @import "./positionings"; 8 | @import "./border"; 9 | @import "./shadow"; 10 | @import "./misc"; 11 | @import "./common"; 12 | -------------------------------------------------------------------------------- /src/styles/views/_CRUDTable.scss: -------------------------------------------------------------------------------- 1 | .crud-table { 2 | thead { 3 | tr { 4 | th:first-child { 5 | padding-left: 16px !important; 6 | } 7 | } 8 | } 9 | tbody { 10 | tr { 11 | transition: background 300ms ease; 12 | &:hover { 13 | background: $light-gray; 14 | } 15 | td { 16 | border-bottom: none; 17 | text-transform: capitalize; 18 | } 19 | td:first-child { 20 | padding-left: 16px !important; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/views/_calendar.scss: -------------------------------------------------------------------------------- 1 | .rbc-event { 2 | background-color: $primary !important; 3 | &.rbc-selected { 4 | background-color: $secondary !important; 5 | } 6 | } 7 | 8 | .rbc-calendar { 9 | height: auto; 10 | flex-grow: 1; 11 | } 12 | .rbc-header { 13 | padding: 12px 16px !important; 14 | a { 15 | padding-bottom: 8px !important; 16 | } 17 | span { 18 | font-size: 15px !important; 19 | font-weight: 500; 20 | } 21 | } 22 | .calendar-header { 23 | border-top-right-radius: 6px; 24 | border-top-left-radius: 6px; 25 | } 26 | -------------------------------------------------------------------------------- /src/styles/views/_carousel.scss: -------------------------------------------------------------------------------- 1 | .swiper-slide { 2 | height: auto; 3 | } 4 | 5 | .swiper-pagination-bullet { 6 | opacity: 1; 7 | background: rgba(0, 0, 0, 0.55); 8 | transition: transform 400ms cubic-bezier(0.17, 0.67, 0.83, 0.67); 9 | } 10 | 11 | .bullet-active { 12 | transform: scale(1.8); 13 | } 14 | 15 | .carousel__button-next, 16 | .carousel__button-prev { 17 | position: absolute !important; 18 | top: 50%; 19 | transform: translateY(calc(-50% - 50px)); 20 | z-index: 1; 21 | } 22 | .carousel__button-prev { 23 | left: 0px; 24 | } 25 | .carousel__button-next { 26 | right: 0px; 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/views/_chat-box.scss: -------------------------------------------------------------------------------- 1 | .chat-sidenav { 2 | border-right: 1px solid $light-gray; 3 | height: 450px; 4 | .chat-contact-list { 5 | height: 100%; 6 | } 7 | } 8 | .chat-container { 9 | background: rgba(0, 0, 0, 0.05); 10 | height: 450px; 11 | .chat-message-list { 12 | .list__message { 13 | border-radius: 4px; 14 | overflow: hidden; 15 | } 16 | } 17 | .empty-message-circle { 18 | height: 220px; 19 | width: 220px; 20 | border-radius: 50%; 21 | box-shadow: $elevation-z6; 22 | 23 | .MuiIcon-root { 24 | font-size: 4rem !important; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/views/_dashboard.scss: -------------------------------------------------------------------------------- 1 | .product-table { 2 | white-space: pre; 3 | min-width: 400px; 4 | overflow: auto; 5 | small { 6 | height: 15px; 7 | width: 50px; 8 | border-radius: 500px; 9 | box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.12), 0 2px 2px 0 rgba(0, 0, 0, 0.24); 10 | } 11 | 12 | tbody { 13 | tr { 14 | transition: background 300ms ease; 15 | &:hover { 16 | background: $light-gray; 17 | } 18 | td { 19 | border-bottom: none; 20 | text-transform: capitalize; 21 | } 22 | td:first-child { 23 | padding-left: 16px !important; 24 | } 25 | } 26 | } 27 | } 28 | 29 | .play-card { 30 | display: flex; 31 | flex-wrap: wrap; 32 | flex-direction: row; 33 | justify-content: space-between; 34 | align-items: center; 35 | 36 | small { 37 | line-height: 1; 38 | } 39 | } 40 | 41 | .upgrade-card { 42 | box-shadow: none; 43 | text-align: center; 44 | position: relative; 45 | h6 { 46 | position: relative; 47 | left: 50%; 48 | transform: translateX(-50%); 49 | width: 150px; 50 | } 51 | } 52 | 53 | .sales { 54 | .bills { 55 | .bills__icon { 56 | border-radius: 8px; 57 | height: 52px; 58 | width: 52px; 59 | overflow: hidden; 60 | background-color: rgba(24, 42, 136, 0.08); 61 | h4, 62 | h5 { 63 | color: rgba(0, 0, 0, 0.87); 64 | } 65 | img { 66 | height: 23px; 67 | width: 36.76px; 68 | } 69 | } 70 | } 71 | 72 | } 73 | 74 | .analytics { 75 | .face-group { 76 | .avatar { 77 | border: 2px solid white; 78 | &:not(:first-child) { 79 | margin-left: -14px; 80 | } 81 | } 82 | .number-avatar { 83 | background: $error; 84 | } 85 | } 86 | 87 | .small-circle { 88 | display: flex; 89 | justify-content: center; 90 | align-items: center; 91 | height: 16px; 92 | width: 16px; 93 | border-radius: 50%; 94 | 95 | .small-icon { 96 | font-size: 8px; 97 | } 98 | } 99 | 100 | .project-card { 101 | .card__roject-name { 102 | margin-left: 24px; 103 | @include media(767px) { 104 | margin-left: 4px; 105 | } 106 | } 107 | } 108 | } 109 | 110 | // online education 111 | .learning-management { 112 | position: relative; 113 | 114 | .welcome-card { 115 | position: relative; 116 | padding: 36px 50px !important; 117 | overflow: visible; 118 | 119 | img { 120 | margin-top: -82px; 121 | max-width: 230px; 122 | } 123 | 124 | @include media(767px) { 125 | img { 126 | display: none; 127 | } 128 | } 129 | } 130 | 131 | 132 | 133 | .product-table { 134 | white-space: pre; 135 | min-width: 400px; 136 | overflow: auto; 137 | small { 138 | height: 15px; 139 | width: 50px; 140 | border-radius: 500px; 141 | box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.12), 0 2px 2px 0 rgba(0, 0, 0, 0.24); 142 | } 143 | 144 | tbody { 145 | tr { 146 | transition: background 300ms ease; 147 | &:hover { 148 | background: $light-gray; 149 | } 150 | td { 151 | border-bottom: none; 152 | text-transform: capitalize; 153 | } 154 | td:first-child { 155 | padding-left: 16px !important; 156 | } 157 | } 158 | } 159 | } 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/styles/views/_ecommerce.scss: -------------------------------------------------------------------------------- 1 | .cart { 2 | min-width: 900px; 3 | overflow-x: scroll; 4 | } 5 | 6 | .ecommerce__product-card { 7 | position: relative; 8 | 9 | .product__image-box { 10 | .product__price { 11 | position: absolute; 12 | font-weight: 500; 13 | background: $primary; 14 | color: white; 15 | padding: 4px 12px; 16 | right: 0; 17 | top: 24px; 18 | border-top-left-radius: 26px; 19 | border-bottom-left-radius: 26px; 20 | overflow: hidden; 21 | z-index: 4; 22 | } 23 | .image-box__overlay { 24 | position: absolute; 25 | top: 0; 26 | bottom: 0; 27 | left: 0; 28 | right: 0; 29 | display: none; 30 | background: rgba(0, 0, 0, 0.74); 31 | z-index: 2; 32 | } 33 | } 34 | 35 | &:hover { 36 | .image-box__overlay { 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | } 41 | } 42 | } 43 | 44 | .checkout { 45 | .checkout__product-list { 46 | hr:last-of-type { 47 | display: none !important; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/views/_form.scss: -------------------------------------------------------------------------------- 1 | .upload-drop-box { 2 | height: 120px; 3 | width: 100%; 4 | border: 2px solid $light-gray; 5 | border-radius: 4px; 6 | } 7 | .drag-shadow { 8 | background: $primary; 9 | box-shadow: 3px 3px 10px rgba($color: #000000, $alpha: 0.2); 10 | } 11 | -------------------------------------------------------------------------------- /src/styles/views/_inbox.scss: -------------------------------------------------------------------------------- 1 | .inbox { 2 | .inbox__topbar { 3 | border-top-right-radius: 4px; 4 | border-top-left-radius: 4px; 5 | button { 6 | color: white !important; 7 | } 8 | } 9 | } 10 | 11 | .ql-container { 12 | min-height: 250px; 13 | // border-bottom-left-radius: 0.5em; 14 | // border-bottom-right-radius: 0.5em; 15 | // background: #fefcfc; 16 | p, 17 | code { 18 | font-size: 16px; 19 | } 20 | } 21 | 22 | .ql-toolbar { 23 | background: white; 24 | // background: #eaecec; 25 | // border-top-left-radius: 0.5em; 26 | // border-top-right-radius: 0.5em; 27 | border-bottom: none; 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/views/_index.scss: -------------------------------------------------------------------------------- 1 | @import "./topbar"; 2 | @import "./sales"; 3 | @import "./sessions"; 4 | @import "./page-layouts"; 5 | @import "./invoice"; 6 | @import "./calendar"; 7 | @import "./CRUDTable"; 8 | @import "./inbox"; 9 | @import "./chat-box"; 10 | @import "./todo"; 11 | @import "./dashboard"; 12 | @import "./list"; 13 | @import "./landing"; 14 | @import "./carousel"; 15 | @import "./pricing"; 16 | @import "./form"; 17 | @import "./scrum-board"; 18 | @import "./ecommerce"; 19 | -------------------------------------------------------------------------------- /src/styles/views/_invoice.scss: -------------------------------------------------------------------------------- 1 | .invoice-viewer { 2 | h5 { 3 | font-size: 15px; 4 | } 5 | .viewer__order-info { 6 | } 7 | .viewer__billing-info { 8 | } 9 | } 10 | 11 | // Media print 12 | @media print { 13 | body, 14 | *, 15 | html { 16 | visibility: hidden; 17 | } 18 | .ps { 19 | overflow: scroll !important; 20 | overflow-anchor: none; 21 | -ms-overflow-style: none; 22 | touch-action: auto; 23 | -ms-touch-action: auto; 24 | } 25 | #print-area { 26 | position: fixed; 27 | top: 0; 28 | left: 0; 29 | right: 0; 30 | height: 100%; 31 | * { 32 | visibility: visible; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/views/_list.scss: -------------------------------------------------------------------------------- 1 | .list { 2 | .list-view { 3 | .list__card { 4 | .project-image { 5 | height: 75px; 6 | width: 100px; 7 | } 8 | 9 | .card__button-group { 10 | display: none; 11 | position: absolute; 12 | top: 0; 13 | bottom: 0; 14 | z-index: 1; 15 | right: 0; 16 | } 17 | 18 | &:hover { 19 | .card__button-group { 20 | display: flex; 21 | } 22 | } 23 | } 24 | } 25 | 26 | .grid-view { 27 | .grid__card { 28 | position: relative; 29 | 30 | &:hover { 31 | .grid__card-top { 32 | &::after { 33 | content: " "; 34 | position: absolute; 35 | top: 0; 36 | left: 0; 37 | right: 0; 38 | bottom: 0; 39 | background: rgba(0, 0, 0, 0.54); 40 | z-index: 1; 41 | 42 | @include keyframeMaker(fade-in) { 43 | from { 44 | opacity: 0; 45 | } 46 | to { 47 | opacity: 1; 48 | } 49 | } 50 | animation: fade-in 250ms #{bezier()}; 51 | } 52 | 53 | .grid__card-overlay { 54 | display: block; 55 | } 56 | } 57 | 58 | .grid__card-bottom { 59 | .email { 60 | display: block; 61 | } 62 | .date { 63 | display: none; 64 | } 65 | } 66 | } 67 | 68 | .grid__card-top { 69 | position: relative; 70 | 71 | .grid__card-overlay { 72 | position: absolute; 73 | top: 0; 74 | left: 0; 75 | right: 0; 76 | bottom: 0; 77 | display: none; 78 | z-index: 2; 79 | 80 | & > div:nth-child(2) { 81 | position: absolute; 82 | top: 0; 83 | bottom: 0; 84 | right: 0; 85 | left: 0; 86 | z-index: -1; 87 | } 88 | } 89 | 90 | img { 91 | // max-height: 200px; 92 | } 93 | } 94 | .grid__card-bottom { 95 | .email { 96 | display: none; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/styles/views/_page-layouts.scss: -------------------------------------------------------------------------------- 1 | .left-sidenav-card { 2 | position: relative; 3 | 4 | .header-bg { 5 | height: 200px; 6 | background: $primary; 7 | background-image: url("/assets/images/home-bg-black.png"); 8 | background-size: contain; 9 | } 10 | 11 | .left-sidenav-card__content { 12 | margin-top: -200px; 13 | margin-right: 24px; 14 | @include media(767px) { 15 | margin-right: 0px; 16 | } 17 | } 18 | 19 | .left-sidenav-card__sidenav { 20 | .sidenav__header { 21 | color: white !important; 22 | @include media(767px) { 23 | color: inherit !important; 24 | } 25 | } 26 | @include media(767px) { 27 | background: $bg-default; 28 | } 29 | } 30 | 31 | .content-card { 32 | .card-header { 33 | height: 64px; 34 | } 35 | } 36 | } 37 | 38 | .user-profile { 39 | position: relative; 40 | 41 | .header-bg { 42 | height: 345px; 43 | @include media(959px) { 44 | height: 400px; 45 | } 46 | @include media(767px) { 47 | height: 400px; 48 | } 49 | } 50 | 51 | .user-profile__content { 52 | margin-top: -345px; 53 | padding-top: 74px; 54 | padding-right: 30px; 55 | padding-left: 4px; 56 | .menu-button { 57 | display: none; 58 | } 59 | @include media(959px) { 60 | margin-top: -390px; 61 | padding-top: 24px; 62 | padding-right: 16px; 63 | padding-left: 16px; 64 | } 65 | @include media(767px) { 66 | margin-top: -410px; 67 | padding-top: 16px; 68 | padding-right: 16px; 69 | padding-left: 16px; 70 | .menu-button { 71 | display: flex; 72 | } 73 | } 74 | .content__top-card-holder { 75 | .content__top-card { 76 | height: 95px; 77 | background-color: rgba(0, 0, 0, 0.12); 78 | } 79 | .content__chart { 80 | width: 54px; 81 | height: 35px; 82 | } 83 | } 84 | 85 | .user-profile__card { 86 | overflow: unset; 87 | .card__edge-button { 88 | position: relative; 89 | margin-top: -56px; 90 | } 91 | 92 | .edge-vertical-line::after { 93 | content: " "; 94 | position: absolute; 95 | height: 35px; 96 | width: 5px; 97 | top: -30px; 98 | background: $primary; 99 | } 100 | 101 | .card__button-holder { 102 | width: 100px; 103 | min-width: 100px; 104 | } 105 | 106 | .card__gray-box { 107 | img { 108 | height: 128px; 109 | width: calc(100% - 16px); 110 | border-radius: 8px; 111 | } 112 | } 113 | } 114 | 115 | .bills { 116 | .bills__icon { 117 | border-radius: 8px; 118 | height: 52px; 119 | width: 52px; 120 | overflow: hidden; 121 | background-color: rgba(24, 42, 136, 0.08); 122 | h4, 123 | h5 { 124 | color: rgba(0, 0, 0, 0.87); 125 | } 126 | img { 127 | height: 23px; 128 | width: 36.76px; 129 | } 130 | } 131 | } 132 | } 133 | 134 | .user-profile__sidenav { 135 | margin-top: -345px; 136 | padding-top: 74px; 137 | .avatar { 138 | height: 82px; 139 | width: 82px; 140 | } 141 | // .text-white { 142 | // color: rgba(255, 255, 255, 0.87) !important; 143 | // } 144 | .sidenav__square-card { 145 | height: 104px; 146 | width: 104px; 147 | } 148 | @include media(767px) { 149 | margin-top: -410px; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/styles/views/_pricing.scss: -------------------------------------------------------------------------------- 1 | .pricing { 2 | .pricing__card { 3 | border-radius: 20px; 4 | overflow: hidden; 5 | h1, 6 | h5 { 7 | margin: 0; 8 | color: $primary !important; 9 | text-transform: uppercase; 10 | } 11 | 12 | h5 { 13 | font-weight: 400; 14 | letter-spacing: 3px; 15 | } 16 | 17 | h1 { 18 | line-height: 1; 19 | font-size: 3rem; 20 | padding-top: 8px; 21 | padding-bottom: 4px; 22 | font-weight: 500; 23 | } 24 | 25 | p { 26 | color: $text-muted; 27 | font-size: 1rem; 28 | } 29 | 30 | img { 31 | height: 150px; 32 | width: 150px; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/views/_sales.scss: -------------------------------------------------------------------------------- 1 | .bg-circle-primary { 2 | background: url("/assets/images/circles.png"), 3 | linear-gradient(90deg, #{$primary} -19.83%, #{$primary} 189.85%); 4 | background-size: cover; 5 | background-repeat: no-repeat; 6 | } 7 | .bg-circle-secondary { 8 | background: url("/assets/images/circles.png"), 9 | linear-gradient(90deg, #{$secondary} -19.83%, #{$secondary} 189.85%); 10 | background-size: cover; 11 | background-repeat: no-repeat; 12 | } 13 | .bg-circle-error { 14 | background: url("/assets/images/circles.png"), 15 | linear-gradient(90deg, #{$error} -19.83%, #{$error} 189.85%); 16 | background-size: cover; 17 | background-repeat: no-repeat; 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/views/_scrum-board.scss: -------------------------------------------------------------------------------- 1 | .scrum-board { 2 | .face-group, 3 | .face-group-9 { 4 | .avatar { 5 | border: 2px solid white; 6 | height: 24px; 7 | width: 24px; 8 | &:not(:first-child) { 9 | margin-left: -8px; 10 | } 11 | } 12 | .number-avatar { 13 | font-size: 12px; 14 | background: $error; 15 | } 16 | } 17 | 18 | .face-group-9 { 19 | .avatar { 20 | height: 36px; 21 | width: 36px; 22 | &:not(:first-child) { 23 | margin-left: -12px; 24 | } 25 | } 26 | .number-avatar { 27 | font-size: 14px; 28 | } 29 | } 30 | 31 | .button-group { 32 | button { 33 | min-width: 32px !important; 34 | } 35 | } 36 | 37 | .list-column { 38 | margin: 0px 12px; 39 | .list-column__card { 40 | margin-bottom: 16px; 41 | } 42 | // .list-column__card:first-child { 43 | // margin-top: 0px; 44 | // } 45 | .list-column__card:last-child { 46 | margin-bottom: 0px; 47 | } 48 | } 49 | .list-column:first-child { 50 | margin: 0px 12px 0px 0px; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/styles/views/_sessions.scss: -------------------------------------------------------------------------------- 1 | .signup { 2 | background: #1A2038; 3 | 4 | 5 | .signup-card { 6 | max-width: 800px; 7 | border-radius: 12px !important; 8 | img { 9 | width: 200px; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/views/_todo.scss: -------------------------------------------------------------------------------- 1 | .todo { 2 | $top: 94px; 3 | 4 | .todo__search-box-holder { 5 | background: $primary; 6 | height: 220px; 7 | & > div { 8 | height: calc(220px - #{$top} + 30px); 9 | @include media(767px) { 10 | height: calc(220px - #{$top} - 16px + 30px); 11 | } 12 | 13 | .todo__search-box { 14 | width: calc(100% - 60px); 15 | height: 48px; 16 | border-radius: 24px; 17 | overflow: hidden; 18 | 19 | input[type="text"] { 20 | font-size: 18px; 21 | outline: none; 22 | border: none; 23 | } 24 | } 25 | } 26 | } 27 | .todo__content { 28 | margin-top: -$top; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/views/_topbar.scss: -------------------------------------------------------------------------------- 1 | .circular-image-small { 2 | height: 36px; 3 | width: 36px; 4 | border-radius: 50%; 5 | } 6 | 7 | --------------------------------------------------------------------------------