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 |
--------------------------------------------------------------------------------
/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 |
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 | 
62 | 
63 | 
64 | 
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
--------------------------------------------------------------------------------