Explorar o código

Extremely basic user whitelist editor GUI

Fela Maslen %!s(int64=7) %!d(string=hai) anos
pai
achega
0855529553

+ 10 - 0
package-lock.json

@@ -2555,6 +2555,11 @@
         }
       }
     },
+    "classnames": {
+      "version": "2.2.6",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+      "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+    },
     "clean-css": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
@@ -11202,6 +11207,11 @@
       "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
       "dev": true
     },
+    "uniqid": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-5.0.3.tgz",
+      "integrity": "sha512-R2qx3X/LYWSdGRaluio4dYrPXAJACTqyUjuyXHoJLBUOIfmMcnYOyY2d6Y4clZcIz5lK6ZaI0Zzmm0cPfsIqzQ=="
+    },
     "unique-filename": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",

+ 2 - 0
package.json

@@ -73,6 +73,7 @@
     "@babel/polyfill": "^7.2.5",
     "@slack/client": "^4.8.0",
     "body-parser": "^1.18.3",
+    "classnames": "^2.2.6",
     "create-reducer-object": "^1.1.0",
     "express": "^4.16.4",
     "express-async-errors": "^3.1.1",
@@ -89,6 +90,7 @@
     "request-promise": "^4.2.2",
     "reselect": "^4.0.0",
     "soap": "^0.25.0",
+    "uniqid": "^5.0.3",
     "winston": "^3.1.0"
   }
 }

+ 3 - 0
src/components/App/index.js

@@ -2,6 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Provider } from 'react-redux';
 
+import EmployeeAdmin from 'components/EmployeeAdmin';
+
 import './style.scss';
 
 export default function App({ store }) {
@@ -9,6 +11,7 @@ export default function App({ store }) {
         <div className="gurubot2">
             <Provider store={store}>
                 <h1>{'GuruBot2 Admin Panel'}</h1>
+                <EmployeeAdmin />
             </Provider>
         </div>
     );

+ 66 - 0
src/components/CrudDocument/index.js

@@ -0,0 +1,66 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import CrudField from 'components/CrudField';
+
+export default class CrudDocument extends Component {
+    static propTypes = {
+        id: PropTypes.string.isRequired,
+        pending: PropTypes.bool,
+        fields: PropTypes.object.isRequired,
+        docFields: PropTypes.object.isRequired,
+        onUpdate: PropTypes.func.isRequired,
+        onDelete: PropTypes.func.isRequired
+    };
+
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            editing: false
+        };
+    }
+
+    onEditStart = () => {
+        this.setState({ editing: true });
+    };
+
+    onEditEnd = (key, value) => {
+        this.setState({ editing: false });
+
+        this.props.onUpdate(this.props.id, { [key]: value });
+    };
+
+    render() {
+        const {
+            pending,
+            fields,
+            docFields
+        } = this.props;
+
+        const fieldsList = Object.keys(fields).map(key => (
+            <CrudField key={key}
+                field={key}
+                pending={pending}
+                type={docFields[key].type}
+                value={fields[key]}
+                onEditStart={this.onEditStart}
+                onEditEnd={this.onEditEnd}
+            />
+        ));
+
+        const className = classNames('crud-document-wrapper', {
+            pending
+        });
+
+        return (
+            <div className={className}>
+                <div className="fields-list">
+                    {fieldsList}
+                </div>
+            </div>
+        );
+    }
+}
+

+ 84 - 0
src/components/CrudField/index.js

@@ -0,0 +1,84 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import './style.scss';
+
+export default class CrudField extends Component {
+    static propTypes = {
+        field: PropTypes.string.isRequired,
+        pending: PropTypes.bool,
+        type: PropTypes.string.isRequired,
+        value: PropTypes.string.isRequired,
+        onEditStart: PropTypes.func.isRequired,
+        onEditEnd: PropTypes.func.isRequired
+    };
+
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            editing: false,
+            editValue: props.value
+        };
+    }
+
+    onChange = event => {
+        this.setState({
+            editValue: event.target.value
+        });
+    };
+
+    onEditStart = () => {
+        this.setState({
+            editing: true
+        });
+    };
+
+    onEditEnd = () => {
+        this.props.onEditEnd(this.props.field, this.state.editValue);
+
+        this.setState({
+            editing: false
+        });
+    };
+
+    componentDidUpdate(prevProps) {
+        if (prevProps.value !== this.props.value) {
+            this.setState({
+                editing: false,
+                editValue: this.props.value
+            });
+        }
+    }
+
+    render() {
+        const {
+            pending,
+            type,
+            value
+        } = this.props;
+
+        if (this.state.editing) {
+            return (
+                <input className="crud-field editing"
+                    disabled={pending}
+                    type={type}
+                    value={this.state.editValue}
+                    onChange={this.onChange}
+                    onBlur={this.onEditEnd}
+                />
+            );
+        }
+
+        const className = classNames('crud-field', { pending });
+
+        return (
+            <span className={className}
+                onClick={this.onEditStart}
+
+            >{value}</span>
+        );
+    }
+}
+

+ 0 - 0
src/components/CrudField/style.scss


+ 23 - 0
src/components/EmployeeAdmin/index.js

@@ -0,0 +1,23 @@
+import React from 'react';
+
+import CrudList from 'containers/CrudList';
+
+const EMPLOYEE_FIELDS = {
+    name: {
+        type: 'text'
+    },
+    email: {
+        type: 'text'
+    }
+};
+
+export default function EmployeeAdmin() {
+    return (
+        <CrudList
+            title="User whitelist"
+            route="employees"
+            docFields={EMPLOYEE_FIELDS}
+        />
+    );
+}
+

+ 98 - 0
src/containers/CrudList/index.js

@@ -0,0 +1,98 @@
+import { connect } from 'react-redux';
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import uniqid from 'uniqid';
+
+import { getDocs } from 'selectors/crud';
+
+import {
+    docCreated,
+    docRead,
+    docUpdated,
+    docDeleted
+} from 'actions/crud';
+
+import CrudDocument from 'components/CrudDocument';
+
+const mapStateToProps = (state, { route }) => ({
+    items: getDocs(state, route)
+});
+
+const mapDispatchToProps = dispatch => ({
+    onCreate: (route, fields) => {
+        const pendingId = uniqid();
+
+        dispatch(docCreated(route, pendingId, fields));
+    },
+    onRead: route => dispatch(docRead(route)),
+    onUpdate: (route, id, fields) => dispatch(docUpdated(route, id, fields)),
+    onDelete: (route, id) => dispatch(docDeleted(route, id))
+});
+
+export default
+@connect(mapStateToProps, mapDispatchToProps)
+class CrudList extends Component {
+    static propTypes = {
+        title: PropTypes.string.isRequired,
+        route: PropTypes.string.isRequired,
+        docFields: PropTypes.object.isRequired,
+        items: PropTypes.array.isRequired,
+        onCreate: PropTypes.func.isRequired,
+        onRead: PropTypes.func.isRequired,
+        onUpdate: PropTypes.func.isRequired,
+        onDelete: PropTypes.func.isRequired
+    };
+
+    onRefresh = () => {
+        this.props.onRead(this.props.route);
+    };
+
+    onUpdate = (id, fields) => {
+        this.props.onUpdate(this.props.route, id, fields);
+    };
+
+    onDelete = id => {
+        this.props.onDelete(this.props.route, id);
+    };
+
+    componentDidMount() {
+        this.onRefresh();
+    }
+
+    render() {
+        const {
+            title,
+            items,
+            docFields
+        } = this.props;
+
+        const docsList = items.map(({ id, pending, ...fields }) => (
+            <CrudDocument key={id}
+                id={id}
+                pending={pending}
+                fields={fields}
+                docFields={docFields}
+                onUpdate={this.onUpdate}
+                onDelete={this.onDelete}
+            />
+        ));
+
+        return (
+            <div className="crud-list-wrapper">
+                <div className="head">
+                    <h3 className="title">{title}</h3>
+                    <div className="meta">
+                        <button className="button-refresh"
+                            onClick={this.onRefresh}>
+                            {'Refresh'}
+                        </button>
+                    </div>
+                </div>
+                <div className="body">
+                    {docsList}
+                </div>
+            </div>
+        );
+    }
+}
+