harukatokutake 6 лет назад
Родитель
Сommit
888dcd7a57

+ 82 - 44
src/client/js/components/Admin/Users/PasswordResetModal.jsx

@@ -4,59 +4,100 @@ import { withTranslation } from 'react-i18next';
 
 import Modal from 'react-bootstrap/es/Modal';
 
+import toastError from '../../../util/apiNotification';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 
 class PasswordResetModal extends React.Component {
 
-  render() {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      temporaryPassword: [],
+      isPasswordResetDone: false,
+    };
+
+    this.returnModalBody = this.returnModalBody.bind(this);
+    this.returnModalFooter = this.returnModalFooter.bind(this);
+    this.resetPassword = this.resetPassword.bind(this);
+  }
+
+  async resetPassword() {
+    const { appContainer, user } = this.props;
+
+    const res = await appContainer.apiPost('/admin/users.resetPassword', { user_id: user._id });
+    if (res.ok) {
+      this.setState({ temporaryPassword: res.newPassword, isPasswordResetDone: true });
+    }
+    else {
+      toastError('Failed to reset password');
+    }
+  }
+
+  returnModalBody() {
     const { t, user } = this.props;
+    return (
+      this.state.isPasswordResetDone
+        ? (
+          <div>
+            <p className="alert alert-danger">{ t('user_management.password_reset_message') }</p>
+            <p>
+              { t('user_management.target_user') }: <code>{ user.email }</code>
+            </p>
+            <p>
+              { t('user_management.new_password') }: <code>{ this.state.temporaryPassword }</code>
+            </p>
+          </div>
+        )
+        : (
+          <div>
+            <p>
+              { t('user_management.password_never_seen') }<br />
+              <span className="text-danger">{ t('user_management.send_new_password') }</span>
+            </p>
+            <p>
+              { t('user_management.target_user') }: <code>{ user.email }</code>
+            </p>
+            <button type="submit" className="btn btn-primary" onClick={this.resetPassword}>
+              { t('user_management.reset_password')}
+            </button>
+          </div>
+        )
+    );
+  }
+
+  returnModalFooter() {
+    return (
+      this.state.isPasswordResetDone
+        ? (
+          <div>
+            <button type="submit" className="btn btn-primary" onClick={this.props.onHideModal}>OK</button>
+          </div>
+        )
+        : (
+          ''
+        )
+    );
+  }
+
+
+  render() {
+    const { t } = this.props;
 
     return (
-      <Modal show={this.props.isPasswordReset} onHide={this.props.onHideModal}>
+      <Modal show={this.props.show} onHide={this.props.onHideModal}>
         <Modal.Header className="modal-header" closeButton>
           <Modal.Title>
             { t('user_management.reset_password') }
           </Modal.Title>
         </Modal.Header>
-        {this.props.isPasswordResetDone
-          ? (
-            <div>
-              <Modal.Body>
-                <div>
-                  <p className="alert alert-danger">{ t('user_management.password_reset_message') }</p>
-                  <p>
-                    { t('user_management.target_user') }: <code>{ user.email }</code>
-                  </p>
-                  <p>
-                    { t('user_management.new_password') }: <code>{ this.props.temporaryPassword }</code>
-                  </p>
-                </div>
-              </Modal.Body>
-              <Modal.Footer>
-                <div>
-                  <button type="submit" className="btn btn-primary" onClick={this.props.onHideModal}>OK</button>
-                </div>
-              </Modal.Footer>
-            </div>
-          )
-          : (
-            <Modal.Body>
-              <div>
-                <p>
-                  { t('user_management.password_never_seen') }<br />
-                  <span className="text-danger">{ t('user_management.send_new_password') }</span>
-                </p>
-                <p>
-                  { t('user_management.target_user') }: <code>{ user.email }</code>
-                </p>
-                <button type="submit" className="btn btn-primary" onClick={this.props.resetPassword}>
-                  { t('user_management.reset_password')}
-                </button>
-              </div>
-            </Modal.Body>
-          )
-        }
+        <Modal.Body>
+          {this.returnModalBody()}
+        </Modal.Body>
+        <Modal.Footer>
+          {this.returnModalFooter()}
+        </Modal.Footer>
       </Modal>
 
     );
@@ -76,11 +117,8 @@ PasswordResetModal.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   user: PropTypes.object.isRequired,
-  isPasswordReset: PropTypes.bool.isRequired,
-  temporaryPassword: PropTypes.array.isRequired,
+  show: PropTypes.bool.isRequired,
   onHideModal: PropTypes.func.isRequired,
-  isPasswordResetDone: PropTypes.bool.isRequired,
-  resetPassword: PropTypes.func.isRequired,
 };
 
 export default withTranslation()(PasswordResetModalWrapper);

+ 6 - 36
src/client/js/components/Admin/Users/UserMenu.jsx

@@ -2,14 +2,12 @@ import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
-import PasswordResetModal from './PasswordResetModal';
 import StatusActivateForm from './StatusActivateForm';
 import StatusSuspendedForm from './StatusSuspendedForm';
 import RemoveUserForm from './UserRemoveForm';
 import RemoveAdminForm from './RemoveAdminForm';
 import GiveAdminForm from './GiveAdminForm';
 
-import toastError from '../../../util/apiNotification';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 
@@ -19,34 +17,14 @@ class UserMenu extends React.Component {
     super(props);
 
     this.state = {
-      isPasswordReset: false,
-      isPasswordResetDone: false,
-      temporaryPassword: [],
-    };
-
-    this.isShow = this.isShow.bind(this);
-    this.onHideModal = this.onHideModal.bind(this);
-    this.resetPassword = this.resetPassword.bind(this);
-  }
 
-  isShow() {
-    this.setState({ isPasswordReset: true });
-  }
+    };
 
-  onHideModal() {
-    this.setState({ isPasswordReset: false, isPasswordResetDone: false });
+    this.onPasswordResetClicked = this.onPasswordResetClicked.bind(this);
   }
 
-  async resetPassword() {
-    const { appContainer, user } = this.props;
-
-    const res = await appContainer.apiPost('/admin/users.resetPassword', { user_id: user._id });
-    if (res.ok) {
-      this.setState({ temporaryPassword: res.newPassword, isPasswordResetDone: true });
-    }
-    else {
-      toastError('Failed to reset password');
-    }
+  onPasswordResetClicked() {
+    this.props.onPasswordResetClicked(this.props.user);
   }
 
   render() {
@@ -54,21 +32,13 @@ class UserMenu extends React.Component {
 
     return (
       <Fragment>
-        <PasswordResetModal
-          user={this.props.user}
-          isPasswordReset={this.state.isPasswordReset}
-          isPasswordResetDone={this.state.isPasswordResetDone}
-          temporaryPassword={this.state.temporaryPassword}
-          onHideModal={this.onHideModal}
-          resetPassword={this.resetPassword}
-        />
         <div className="btn-group admin-user-menu">
           <button type="button" className="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">
             <i className="icon-settings"></i> <span className="caret"></span>
           </button>
           <ul className="dropdown-menu" role="menu">
             <li className="dropdown-header">{ t('user_management.edit_menu') }</li>
-            <li onClick={this.isShow}>
+            <li onClick={this.onPasswordResetClicked}>
               <a>
                 <i className="icon-fw icon-key"></i>{ t('user_management.reset_password') }
               </a>
@@ -103,7 +73,7 @@ UserMenu.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   user: PropTypes.object.isRequired,
-  isShow: PropTypes.func.isRequired,
+  onPasswordResetClicked: PropTypes.func.isRequired,
 };
 
 export default withTranslation()(UserMenuWrapper);

+ 44 - 36
src/client/js/components/Admin/Users/UserTable.jsx

@@ -11,44 +11,52 @@ import AppContainer from '../../../services/AppContainer';
 
 class UserTable extends React.Component {
 
+  constructor(props) {
+    super(props);
 
-  render() {
-    const { t } = this.props;
-    let userStatusLabel;
+    this.state = {
+
+    };
+
+    this.getUserStatusLabel = this.getUserStatusLabel.bind(this);
+  }
+
+  getUserStatusLabel(userStatus) {
     let additionalClassName;
     let text;
 
-    this.props.users.forEach((user) => {
-      userStatusLabel = (
-        <span className={`label ${additionalClassName}`}>
-          {text}
-        </span>
-      );
-
-      switch (user.status) {
-        case 1:
-          additionalClassName = 'label-info';
-          text = 'Approval Pending';
-          break;
-        case 2:
-          additionalClassName = 'label-success';
-          text = 'Active';
-          break;
-        case 3:
-          additionalClassName = 'label-warning';
-          text = 'Suspended';
-          break;
-        case 4:
-          additionalClassName = 'label-danger';
-          text = 'Deleted';
-          break;
-        case 5:
-          additionalClassName = 'label-info';
-          text = 'Invited';
-          break;
-      }
-    });
+    switch (userStatus) {
+      case 1:
+        additionalClassName = 'label-info';
+        text = 'Approval Pending';
+        break;
+      case 2:
+        additionalClassName = 'label-success';
+        text = 'Active';
+        break;
+      case 3:
+        additionalClassName = 'label-warning';
+        text = 'Suspended';
+        break;
+      case 4:
+        additionalClassName = 'label-danger';
+        text = 'Deleted';
+        break;
+      case 5:
+        additionalClassName = 'label-info';
+        text = 'Invited';
+        break;
+    }
 
+    return (
+      <span className={`label ${additionalClassName}`}>
+        {text}
+      </span>
+    );
+  }
+
+  render() {
+    const { t } = this.props;
 
     return (
       <Fragment>
@@ -75,7 +83,7 @@ class UserTable extends React.Component {
                     <UserPicture user={user} className="picture img-circle" />
                     {user.admin && <span className="label label-inverse label-admin ml-2">{ t('administrator') }</span>}
                   </td>
-                  <td>{userStatusLabel}</td>
+                  <td>{this.getUserStatusLabel(user.status)}</td>
                   <td>
                     <strong>{user.username}</strong>
                   </td>
@@ -86,7 +94,7 @@ class UserTable extends React.Component {
                     { user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'YYYY-MM-DD HH:mm')}</span> }
                   </td>
                   <td>
-                    <UserMenu user={user} />
+                    <UserMenu user={user} onPasswordResetClicked={this.props.onPasswordResetClicked} />
                   </td>
                 </tr>
               );
@@ -108,7 +116,7 @@ UserTable.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   users: PropTypes.array.isRequired,
-
+  onPasswordResetClicked: PropTypes.func.isRequired,
 };
 
 export default withTranslation()(UserTableWrapper);

+ 25 - 0
src/client/js/components/Admin/Users/Users.jsx

@@ -2,6 +2,7 @@ import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
+import PasswordResetModal from './PasswordResetModal';
 import PaginationWrapper from '../../PaginationWrapper';
 import InviteUserControl from './InviteUserControl';
 import UserTable from './UserTable';
@@ -15,11 +16,15 @@ class UserPage extends React.Component {
     super();
 
     this.state = {
+      userForPasswordResetModal: null,
       users: [],
       activePage: 1,
       pagingLimit: Infinity,
+      isPasswordResetModalShown: false,
     };
 
+    this.showPasswordResetModal = this.showPasswordResetModal.bind(this);
+    this.hidePasswordResetModal = this.hidePasswordResetModal.bind(this);
   }
 
   // TODO unstatedContainerを作ってそこにリファクタすべき
@@ -32,12 +37,30 @@ class UserPage extends React.Component {
     });
   }
 
+  showPasswordResetModal(user) {
+    this.setState({
+      isPasswordResetModalShown: true,
+      userForPasswordResetModal: user,
+    });
+  }
+
+  hidePasswordResetModal() {
+    this.setState({ isPasswordResetModalShown: false });
+  }
+
 
   render() {
     const { t } = this.props;
 
     return (
       <Fragment>
+        { this.state.userForPasswordResetModal && (
+          <PasswordResetModal
+            user={this.state.userForPasswordResetModal}
+            show={this.state.isPasswordResetModalShown}
+            onHideModal={this.hidePasswordResetModal}
+          />
+        ) }
         <p>
           <InviteUserControl />
           <a className="btn btn-default btn-outline ml-2" href="/admin/users/external-accounts">
@@ -47,6 +70,7 @@ class UserPage extends React.Component {
         </p>
         <UserTable
           users={this.state.users}
+          onPasswordResetClicked={this.showPasswordResetModal}
         />
         <PaginationWrapper
           activePage={this.state.activePage}
@@ -69,6 +93,7 @@ UserPage.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
+  userForPasswordResetModal: PropTypes.object.isRequired,
 };
 
 export default withTranslation()(UserPageWrapper);

+ 14 - 0
src/server/views/admin/users.html

@@ -12,6 +12,20 @@
 
 {% block content_main %}
 <div class="content-main">
+    {% set smessage = req.flash('successMessage') %}
+  {% if smessage.length %}
+  <div class="alert alert-success">
+    {{ smessage }}
+  </div>
+  {% endif %}
+
+  {% set emessage = req.flash('errorMessage') %}
+  {% if emessage.length %}
+  <div class="alert alert-danger">
+    {{ emessage }}
+  </div>
+  {% endif %}
+
   <div class="col-md-3">
     {% include './widget/menu.html' with {current: 'user'} %}
   </div>