├── .dockerignore ├── src ├── styles │ ├── components │ │ ├── _list.scss │ │ ├── _avatar.scss │ │ ├── _matx-search-box.scss │ │ ├── _index.scss │ │ ├── _shopping-cart.scss │ │ ├── _matx-tootbar-menu.scss │ │ ├── _matx-sidenav.scss │ │ ├── _notification.scss │ │ ├── _loader.scss │ │ └── _customizer.scss │ ├── layouts │ │ ├── layout1 │ │ │ └── _index.scss │ │ ├── layout2 │ │ │ ├── _variables.scss │ │ │ ├── _index.scss │ │ │ ├── _layout2.scss │ │ │ ├── _topbar.scss │ │ │ └── _navbar.scss │ │ ├── shared │ │ │ ├── _index.scss │ │ │ ├── _footer.scss │ │ │ └── _layout.scss │ │ └── _index.scss │ ├── utilities │ │ ├── _functions.scss │ │ ├── _misc.scss │ │ ├── _shadow.scss │ │ ├── _utilities.scss │ │ ├── _animations.scss │ │ ├── _border.scss │ │ ├── _carousel.scss │ │ ├── _positionings.scss │ │ ├── _common.scss │ │ ├── _spacing.scss │ │ ├── _typography.scss │ │ └── _color.scss │ ├── views │ │ ├── _topbar.scss │ │ ├── _sessions.scss │ │ ├── _form.scss │ │ ├── _index.scss │ │ ├── _CRUDTable.scss │ │ ├── _calendar.scss │ │ ├── _carousel.scss │ │ ├── _chat-box.scss │ │ ├── _inbox.scss │ │ ├── _invoice.scss │ │ ├── _pricing.scss │ │ ├── _sales.scss │ │ ├── _todo.scss │ │ ├── _ecommerce.scss │ │ ├── _scrum-board.scss │ │ ├── _list.scss │ │ ├── _dashboard.scss │ │ └── _page-layouts.scss │ ├── _app.scss │ ├── _reboot.scss │ ├── _variables.scss │ └── _mixins.scss ├── history.js ├── app │ ├── appContext.js │ ├── MatxLayout │ │ ├── index.js │ │ ├── MatxTheme │ │ │ ├── SecondarySidenavTheme │ │ │ │ └── SecondarySidenavTheme.jsx │ │ │ ├── SidenavTheme │ │ │ │ ├── SidenavTheme.jsx │ │ │ │ └── SidenavThemeStyles.jsx │ │ │ ├── MatxTheme.jsx │ │ │ ├── EchartTheme.jsx │ │ │ ├── SidenavTheme.jsx │ │ │ └── MatxCssVars.jsx │ │ ├── SharedCompoents │ │ │ ├── MatxCustomizer │ │ │ │ ├── customizerOptions.js │ │ │ │ ├── BadgeSelected.jsx │ │ │ │ └── Layout2Customizer.jsx │ │ │ ├── Brand.jsx │ │ │ ├── SecondarySidebar │ │ │ │ ├── SecondarySidebar.jsx │ │ │ │ ├── SecondarySidebarContent.jsx │ │ │ │ └── SecondarySidebarToggle.jsx │ │ │ ├── TopbarMenu.jsx │ │ │ ├── Footer.jsx │ │ │ └── Sidenav.jsx │ │ ├── Layout1 │ │ │ ├── Layout1Settings.js │ │ │ └── Layout1.jsx │ │ ├── settings.js │ │ ├── MatxLayoutSFC.jsx │ │ └── MatxLayout.jsx │ ├── views │ │ ├── bot │ │ │ ├── BotRoutes.js │ │ │ ├── SettingFormHeader.jsx │ │ │ ├── RightsTable.jsx │ │ │ └── AddRightModel.jsx │ │ ├── dashboard │ │ │ ├── DashboardRoutes.js │ │ │ ├── RemoveBotModal.jsx │ │ │ ├── StatsCard.jsx │ │ │ ├── Analytics.jsx │ │ │ └── TransferBotModal.jsx │ │ ├── users │ │ │ ├── UsersRoutes.js │ │ │ └── PasswordChangeModal.jsx │ │ ├── botcreate │ │ │ └── BotCreateRoutes.js │ │ └── sessions │ │ │ ├── SessionRoutes.js │ │ │ └── NotFound.jsx │ ├── ui │ │ ├── Spinner.jsx │ │ ├── BlockButton.jsx │ │ └── AdvanceTable.jsx │ ├── navigations.js │ ├── redux │ │ ├── actions │ │ │ ├── NotificationsActions.js │ │ │ ├── LayoutActions.js │ │ │ ├── NavigationActions.js │ │ │ ├── UserActions.js │ │ │ ├── UsersActions.js │ │ │ ├── NavigationAction.js │ │ │ ├── BotsActions.js │ │ │ ├── BotCreateActions.js │ │ │ ├── UserCreateActions.js │ │ │ ├── LoginActions.js │ │ │ └── UserRemoveActions.js │ │ ├── Store.js │ │ └── reducers │ │ │ ├── NavigationReducer.js │ │ │ ├── UserReducer.js │ │ │ ├── NotificationsReducer.js │ │ │ ├── NotificationReducer.js │ │ │ ├── LayoutReducer.js │ │ │ ├── BotCreateReducer.js │ │ │ ├── UsersReducer.js │ │ │ ├── UserCreateReducer.js │ │ │ ├── BotsReducer.js │ │ │ ├── LoginReducer.js │ │ │ ├── RootReducer.js │ │ │ ├── BotRightsReducer.js │ │ │ ├── BotSettingsReducer.js │ │ │ └── ScrumBoardReducer.js │ ├── auth │ │ ├── authRoles.js │ │ ├── Auth.jsx │ │ └── AuthGuard.jsx │ ├── RootRoutes.jsx │ ├── NotificationDisplay.jsx │ ├── App.jsx │ └── services │ │ └── jwtAuthService.js ├── matx │ ├── components │ │ ├── MatxSidenav │ │ │ ├── MatxSidenavContainer.jsx │ │ │ ├── MatxSidenavContent.jsx │ │ │ └── MatxSidenav.jsx │ │ ├── MatxSuspense │ │ │ └── MatxSuspense.jsx │ │ ├── MatxLoadable │ │ │ ├── MatxLoadable.jsx │ │ │ └── Loading.js │ │ ├── LoaderBounce.jsx │ │ ├── RectangleAvatar.jsx │ │ ├── cards │ │ │ ├── SimpleCard.jsx │ │ │ └── CardWidget1.jsx │ │ ├── MatxLoading │ │ │ └── MatxLoading.jsx │ │ ├── MatxSnackbar.jsx │ │ ├── ConfirmationDialog.jsx │ │ ├── MatxToolbarMenu.jsx │ │ ├── MatxProgressBar.jsx │ │ ├── Breadcrumb.jsx │ │ ├── MatxListItem1.jsx │ │ ├── MatxMenu.jsx │ │ ├── MatxSearchBox.jsx │ │ ├── MatxHorizontalNav │ │ │ └── MatxHorizontalNav.jsx │ │ ├── RichTextEditor.jsx │ │ └── MatxVerticalNav │ │ │ ├── MatxVerticalNav.jsx │ │ │ └── MatxVerticalNavExpansionPanel.jsx │ ├── index.js │ └── theme │ │ └── EchartTheme.jsx ├── axios.js ├── _index.scss ├── index.jsx └── i18n.js ├── jsconfig.json ├── public ├── favicon.ico ├── assets │ └── images │ │ ├── logo.png │ │ ├── logo-circle.png │ │ ├── sidebar │ │ └── sidebar-bg-dark.jpg │ │ ├── logo.svg │ │ └── logo-circle.svg ├── manifest.json └── index.html ├── config-overrides.js ├── Dockerfile ├── .gitignore ├── .babelrc.js ├── LICENSE ├── README.md ├── nginx.conf └── package.json /.dockerignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/components/_list.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/layouts/layout1/_index.scss: -------------------------------------------------------------------------------- 1 | @import 'layout1'; -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | } 5 | } -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_variables.scss: -------------------------------------------------------------------------------- 1 | $topbar-height: 80px; 2 | $navbar-height: 60px; -------------------------------------------------------------------------------- /src/styles/layouts/shared/_index.scss: -------------------------------------------------------------------------------- 1 | @import "layout"; 2 | @import "sidenav"; 3 | @import "footer"; -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | 3 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/HEAD/public/assets/images/logo.png -------------------------------------------------------------------------------- /src/styles/layouts/_index.scss: -------------------------------------------------------------------------------- 1 | @import "./shared/index"; 2 | 3 | @import "./layout1/index"; 4 | @import "./layout2/index"; 5 | -------------------------------------------------------------------------------- /src/styles/layouts/layout2/_index.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'layout2'; 3 | @import 'topbar'; 4 | @import 'navbar'; -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { useBabelRc, override } = require("customize-cra"); 2 | 3 | module.exports = override(useBabelRc()); 4 | -------------------------------------------------------------------------------- /src/styles/views/_topbar.scss: -------------------------------------------------------------------------------- 1 | .circular-image-small { 2 | height: 36px; 3 | width: 36px; 4 | border-radius: 50%; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /public/assets/images/logo-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/HEAD/public/assets/images/logo-circle.png -------------------------------------------------------------------------------- /src/app/appContext.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AppContext = React.createContext({}); 4 | 5 | export default AppContext; 6 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /public/assets/images/sidebar/sidebar-bg-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elipeF/TS3AudioBot-Control-Panel/HEAD/public/assets/images/sidebar/sidebar-bg-dark.jpg -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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;"] -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/_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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/_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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------