Просмотр исходного кода

Merge branch 'imprv/reactify-admin' into create-xss-post-apiV3

itizawa 6 лет назад
Родитель
Сommit
5bef7bb624

+ 15 - 2
src/client/js/app.jsx

@@ -40,7 +40,7 @@ import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
 import MarkdownSetting from './components/Admin/MarkdownSetting/MarkDownSetting';
 import MarkdownSetting from './components/Admin/MarkdownSetting/MarkDownSetting';
-import UserPage from './components/Admin/Users/Users';
+import Users from './components/Admin/Users/Users';
 import UserGroupPage from './components/Admin/UserGroup/UserGroupPage';
 import UserGroupPage from './components/Admin/UserGroup/UserGroupPage';
 import Customize from './components/Admin/Customize/Customize';
 import Customize from './components/Admin/Customize/Customize';
 import Importer from './components/Admin/Importer';
 import Importer from './components/Admin/Importer';
@@ -53,6 +53,7 @@ import CommentContainer from './services/CommentContainer';
 import EditorContainer from './services/EditorContainer';
 import EditorContainer from './services/EditorContainer';
 import TagContainer from './services/TagContainer';
 import TagContainer from './services/TagContainer';
 import UserGroupDetailContainer from './services/UserGroupDetailContainer';
 import UserGroupDetailContainer from './services/UserGroupDetailContainer';
+import AdminUsersContainer from './services/AdminUsersContainer';
 import WebsocketContainer from './services/WebsocketContainer';
 import WebsocketContainer from './services/WebsocketContainer';
 import MarkDownSettingContainer from './services/MarkDownSettingContainer';
 import MarkDownSettingContainer from './services/MarkDownSettingContainer';
 
 
@@ -107,7 +108,6 @@ let componentMappings = {
   'user-created-list': <RecentCreated />,
   'user-created-list': <RecentCreated />,
   'user-draft-list': <MyDraftList />,
   'user-draft-list': <MyDraftList />,
 
 
-  'admin-user-page': <UserPage />,
   'admin-full-text-search-management': <FullTextSearchManagement />,
   'admin-full-text-search-management': <FullTextSearchManagement />,
   'admin-customize': <Customize />,
   'admin-customize': <Customize />,
 
 
@@ -159,6 +159,19 @@ Object.keys(componentMappings).forEach((key) => {
 });
 });
 
 
 // render for admin
 // render for admin
+const adminUsersElem = document.getElementById('admin-user-page');
+if (adminUsersElem != null) {
+  const adminUsersContainer = new AdminUsersContainer(appContainer);
+  ReactDOM.render(
+    <Provider inject={[injectableContainers, adminUsersContainer]}>
+      <I18nextProvider i18n={i18n}>
+        <Users />
+      </I18nextProvider>
+    </Provider>,
+    adminUsersElem,
+  );
+}
+
 const adminUserGroupDetailElem = document.getElementById('admin-user-group-detail');
 const adminUserGroupDetailElem = document.getElementById('admin-user-group-detail');
 if (adminUserGroupDetailElem != null) {
 if (adminUserGroupDetailElem != null) {
   const userGroupDetailContainer = new UserGroupDetailContainer(appContainer);
   const userGroupDetailContainer = new UserGroupDetailContainer(appContainer);

+ 6 - 24
src/client/js/components/Admin/Users/InviteUserControl.jsx

@@ -4,39 +4,20 @@ import { withTranslation } from 'react-i18next';
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
 import UserInviteModal from './UserInviteModal';
 import UserInviteModal from './UserInviteModal';
 
 
 class InviteUserControl extends React.Component {
 class InviteUserControl extends React.Component {
 
 
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      isUserInviteModalShown: false,
-    };
-
-    this.toggleUserInviteModal = this.toggleUserInviteModal.bind(this);
-  }
-
-  /**
-   * user招待モーダルを開閉する
-   */
-  toggleUserInviteModal() {
-    this.setState({ isUserInviteModalShown: !this.state.isUserInviteModalShown });
-  }
-
   render() {
   render() {
-    const { t } = this.props;
+    const { t, adminUsersContainer } = this.props;
 
 
     return (
     return (
       <Fragment>
       <Fragment>
-        <button type="button" className="btn btn-default" onClick={this.toggleUserInviteModal}>
+        <button type="button" className="btn btn-default" onClick={adminUsersContainer.toggleUserInviteModal}>
           { t('user_management.invite_users') }
           { t('user_management.invite_users') }
         </button>
         </button>
-        <UserInviteModal
-          show={this.state.isUserInviteModalShown}
-          onToggleModal={this.toggleUserInviteModal}
-        />
+        <UserInviteModal />
       </Fragment>
       </Fragment>
     );
     );
   }
   }
@@ -44,12 +25,13 @@ class InviteUserControl extends React.Component {
 }
 }
 
 
 const InviteUserControlWrapper = (props) => {
 const InviteUserControlWrapper = (props) => {
-  return createSubscribedElement(InviteUserControl, props, [AppContainer]);
+  return createSubscribedElement(InviteUserControl, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 InviteUserControl.propTypes = {
 InviteUserControl.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 };
 };
 
 
 export default withTranslation()(InviteUserControlWrapper);
 export default withTranslation()(InviteUserControlWrapper);

+ 45 - 50
src/client/js/components/Admin/Users/PasswordResetModal.jsx

@@ -7,6 +7,7 @@ import Modal from 'react-bootstrap/es/Modal';
 import { toastError } from '../../../util/apiNotification';
 import { toastError } from '../../../util/apiNotification';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
 
 
 class PasswordResetModal extends React.Component {
 class PasswordResetModal extends React.Component {
 
 
@@ -18,13 +19,12 @@ class PasswordResetModal extends React.Component {
       isPasswordResetDone: false,
       isPasswordResetDone: false,
     };
     };
 
 
-    this.returnModalBody = this.returnModalBody.bind(this);
-    this.returnModalFooter = this.returnModalFooter.bind(this);
     this.resetPassword = this.resetPassword.bind(this);
     this.resetPassword = this.resetPassword.bind(this);
   }
   }
 
 
   async resetPassword() {
   async resetPassword() {
-    const { appContainer, user } = this.props;
+    const { appContainer, adminUsersContainer } = this.props;
+    const user = adminUsersContainer.state.userForPasswordResetModal;
 
 
     const res = await appContainer.apiPost('/admin/users.resetPassword', { user_id: user._id });
     const res = await appContainer.apiPost('/admin/users.resetPassword', { user_id: user._id });
     if (res.ok) {
     if (res.ok) {
@@ -35,71 +35,69 @@ class PasswordResetModal extends React.Component {
     }
     }
   }
   }
 
 
-  returnModalBody() {
-    const { t, user } = this.props;
+  renderModalBodyBeforeReset() {
+    const { t, adminUsersContainer } = this.props;
+    const user = adminUsersContainer.state.userForPasswordResetModal;
+
+    return (
+      <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>
+    );
+  }
+
+  returnModalBodyAfterReset() {
+    const { t, adminUsersContainer } = this.props;
+    const user = adminUsersContainer.state.userForPasswordResetModal;
+
     return (
     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>
-        )
+      <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() {
   returnModalFooter() {
     return (
     return (
-      this.state.isPasswordResetDone
-        ? (
-          <div>
-            <button type="submit" className="btn btn-primary" onClick={this.props.onHideModal}>OK</button>
-          </div>
-        )
-        : (
-          ''
-        )
+      <div>
+        <button type="submit" className="btn btn-primary" onClick={this.props.adminUsersContainer.hidePasswordResetModal}>OK</button>
+      </div>
     );
     );
   }
   }
 
 
 
 
   render() {
   render() {
-    const { t } = this.props;
+    const { t, adminUsersContainer } = this.props;
 
 
     return (
     return (
-      <Modal show={this.props.show} onHide={this.props.onHideModal}>
+      <Modal show={adminUsersContainer.state.isPasswordResetModalShown} onHide={adminUsersContainer.hidePasswordResetModal}>
         <Modal.Header className="modal-header" closeButton>
         <Modal.Header className="modal-header" closeButton>
           <Modal.Title>
           <Modal.Title>
             { t('user_management.reset_password') }
             { t('user_management.reset_password') }
           </Modal.Title>
           </Modal.Title>
         </Modal.Header>
         </Modal.Header>
         <Modal.Body>
         <Modal.Body>
-          {this.returnModalBody()}
+          {this.state.isPasswordResetDone ? this.renderModalBodyBeforeReset() : this.returnModalBodyAfterReset()}
         </Modal.Body>
         </Modal.Body>
         <Modal.Footer>
         <Modal.Footer>
-          {this.returnModalFooter()}
+          {this.state.isPasswordResetDone && this.returnModalFooter()}
         </Modal.Footer>
         </Modal.Footer>
       </Modal>
       </Modal>
-
     );
     );
   }
   }
 
 
@@ -109,16 +107,13 @@ class PasswordResetModal extends React.Component {
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
 const PasswordResetModalWrapper = (props) => {
 const PasswordResetModalWrapper = (props) => {
-  return createSubscribedElement(PasswordResetModal, props, [AppContainer]);
+  return createSubscribedElement(PasswordResetModal, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 PasswordResetModal.propTypes = {
 PasswordResetModal.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  user: PropTypes.object.isRequired,
-  show: PropTypes.bool.isRequired,
-  onHideModal: PropTypes.func.isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 };
 };
 
 
 export default withTranslation()(PasswordResetModalWrapper);
 export default withTranslation()(PasswordResetModalWrapper);

+ 6 - 7
src/client/js/components/Admin/Users/UserInviteModal.jsx

@@ -11,6 +11,7 @@ import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
 
 
 class UserInviteModal extends React.Component {
 class UserInviteModal extends React.Component {
 
 
@@ -30,7 +31,7 @@ class UserInviteModal extends React.Component {
   }
   }
 
 
   onToggleModal() {
   onToggleModal() {
-    this.props.onToggleModal();
+    this.props.adminUsersContainer.toggleUserInviteModal();
     this.setState({ invitedEmailList: null });
     this.setState({ invitedEmailList: null });
   }
   }
 
 
@@ -185,11 +186,11 @@ class UserInviteModal extends React.Component {
   }
   }
 
 
   render() {
   render() {
-    const { t } = this.props;
+    const { t, adminUsersContainer } = this.props;
     const { invitedEmailList } = this.state;
     const { invitedEmailList } = this.state;
 
 
     return (
     return (
-      <Modal show={this.props.show} onHide={this.onToggleModal}>
+      <Modal show={adminUsersContainer.state.isUserInviteModalShown} onHide={this.onToggleModal}>
         <Modal.Header className="modal-header" closeButton>
         <Modal.Header className="modal-header" closeButton>
           <Modal.Title>
           <Modal.Title>
             { t('user_management.invite_users') }
             { t('user_management.invite_users') }
@@ -213,16 +214,14 @@ class UserInviteModal extends React.Component {
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
 const UserInviteModalWrapper = (props) => {
 const UserInviteModalWrapper = (props) => {
-  return createSubscribedElement(UserInviteModal, props, [AppContainer]);
+  return createSubscribedElement(UserInviteModal, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 
 
 UserInviteModal.propTypes = {
 UserInviteModal.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  show: PropTypes.bool.isRequired,
-  onToggleModal: PropTypes.func.isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 };
 };
 
 
 export default withTranslation()(UserInviteModalWrapper);
 export default withTranslation()(UserInviteModalWrapper);

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

@@ -10,6 +10,7 @@ import GiveAdminForm from './GiveAdminForm';
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
 
 
 class UserMenu extends React.Component {
 class UserMenu extends React.Component {
 
 
@@ -24,7 +25,7 @@ class UserMenu extends React.Component {
   }
   }
 
 
   onPasswordResetClicked() {
   onPasswordResetClicked() {
-    this.props.onPasswordResetClicked(this.props.user);
+    this.props.adminUsersContainer.showPasswordResetModal(this.props.user);
   }
   }
 
 
   render() {
   render() {
@@ -48,7 +49,7 @@ class UserMenu extends React.Component {
             <li>
             <li>
               {(user.status === 1 || user.status === 3) && <StatusActivateForm user={user} />}
               {(user.status === 1 || user.status === 3) && <StatusActivateForm user={user} />}
               {user.status === 2 && <StatusSuspendedForm user={user} />}
               {user.status === 2 && <StatusSuspendedForm user={user} />}
-              {(user.status === 1 || user.status === 3 || user.status === 5) && <RemoveUserButton user={user} removeUser={this.props.removeUser} />}
+              {(user.status === 1 || user.status === 3 || user.status === 5) && <RemoveUserButton user={user} />}
             </li>
             </li>
             <li className="divider pl-0"></li>
             <li className="divider pl-0"></li>
             <li className="dropdown-header">{ t('user_management.administrator_menu') }</li>
             <li className="dropdown-header">{ t('user_management.administrator_menu') }</li>
@@ -65,16 +66,15 @@ class UserMenu extends React.Component {
 }
 }
 
 
 const UserMenuWrapper = (props) => {
 const UserMenuWrapper = (props) => {
-  return createSubscribedElement(UserMenu, props, [AppContainer]);
+  return createSubscribedElement(UserMenu, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 UserMenu.propTypes = {
 UserMenu.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 
 
   user: PropTypes.object.isRequired,
   user: PropTypes.object.isRequired,
-  removeUser: PropTypes.func.isRequired,
-  onPasswordResetClicked: PropTypes.func.isRequired,
 };
 };
 
 
 export default withTranslation()(UserMenuWrapper);
 export default withTranslation()(UserMenuWrapper);

+ 12 - 4
src/client/js/components/Admin/Users/UserRemoveButton.jsx

@@ -4,6 +4,8 @@ import { withTranslation } from 'react-i18next';
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 
 class UserRemoveButton extends React.Component {
 class UserRemoveButton extends React.Component {
 
 
@@ -13,8 +15,14 @@ class UserRemoveButton extends React.Component {
     this.onClickDeleteBtn = this.onClickDeleteBtn.bind(this);
     this.onClickDeleteBtn = this.onClickDeleteBtn.bind(this);
   }
   }
 
 
-  onClickDeleteBtn() {
-    this.props.removeUser(this.props.user);
+  async onClickDeleteBtn() {
+    try {
+      const username = await this.props.adminUsersContainer.removeUser(this.props.user._id);
+      toastSuccess(`Delete ${username} success`);
+    }
+    catch (err) {
+      toastError(err);
+    }
   }
   }
 
 
   render() {
   render() {
@@ -33,15 +41,15 @@ class UserRemoveButton extends React.Component {
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
 const UserRemoveButtonWrapper = (props) => {
 const UserRemoveButtonWrapper = (props) => {
-  return createSubscribedElement(UserRemoveButton, props, [AppContainer]);
+  return createSubscribedElement(UserRemoveButton, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 UserRemoveButton.propTypes = {
 UserRemoveButton.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 
 
   user: PropTypes.object.isRequired,
   user: PropTypes.object.isRequired,
-  removeUser: PropTypes.func.isRequired,
 };
 };
 
 
 export default withTranslation()(UserRemoveButtonWrapper);
 export default withTranslation()(UserRemoveButtonWrapper);

+ 6 - 7
src/client/js/components/Admin/Users/UserTable.jsx

@@ -8,6 +8,7 @@ import UserMenu from './UserMenu';
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
 
 
 class UserTable extends React.Component {
 class UserTable extends React.Component {
 
 
@@ -61,7 +62,7 @@ class UserTable extends React.Component {
   }
   }
 
 
   render() {
   render() {
-    const { t } = this.props;
+    const { t, adminUsersContainer } = this.props;
 
 
     return (
     return (
       <Fragment>
       <Fragment>
@@ -81,7 +82,7 @@ class UserTable extends React.Component {
             </tr>
             </tr>
           </thead>
           </thead>
           <tbody>
           <tbody>
-            {this.props.users.map((user) => {
+            {adminUsersContainer.state.users.map((user) => {
               return (
               return (
                 <tr key={user._id}>
                 <tr key={user._id}>
                   <td>
                   <td>
@@ -99,7 +100,7 @@ class UserTable extends React.Component {
                     { user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'yyyy-MM-dd HH:mm')}</span> }
                     { user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'yyyy-MM-dd HH:mm')}</span> }
                   </td>
                   </td>
                   <td>
                   <td>
-                    <UserMenu user={user} onPasswordResetClicked={this.props.onPasswordResetClicked} removeUser={this.props.removeUser} />
+                    <UserMenu user={user} />
                   </td>
                   </td>
                 </tr>
                 </tr>
               );
               );
@@ -113,16 +114,14 @@ class UserTable extends React.Component {
 }
 }
 
 
 const UserTableWrapper = (props) => {
 const UserTableWrapper = (props) => {
-  return createSubscribedElement(UserTable, props, [AppContainer]);
+  return createSubscribedElement(UserTable, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 UserTable.propTypes = {
 UserTable.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 
 
-  users: PropTypes.array.isRequired,
-  removeUser: PropTypes.func.isRequired,
-  onPasswordResetClicked: PropTypes.func.isRequired,
 };
 };
 
 
 export default withTranslation()(UserTableWrapper);
 export default withTranslation()(UserTableWrapper);

+ 7 - 64
src/client/js/components/Admin/Users/Users.jsx

@@ -2,8 +2,6 @@ import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
-import { toastSuccess, toastError } from '../../../util/apiNotification';
-
 import PasswordResetModal from './PasswordResetModal';
 import PasswordResetModal from './PasswordResetModal';
 import PaginationWrapper from '../../PaginationWrapper';
 import PaginationWrapper from '../../PaginationWrapper';
 import InviteUserControl from './InviteUserControl';
 import InviteUserControl from './InviteUserControl';
@@ -11,6 +9,7 @@ import UserTable from './UserTable';
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
 
 
 class UserPage extends React.Component {
 class UserPage extends React.Component {
 
 
@@ -18,71 +17,18 @@ class UserPage extends React.Component {
     super();
     super();
 
 
     this.state = {
     this.state = {
-      userForPasswordResetModal: null,
-      users: [],
       activePage: 1,
       activePage: 1,
       pagingLimit: Infinity,
       pagingLimit: Infinity,
-      isPasswordResetModalShown: false,
     };
     };
 
 
-    this.removeUser = this.removeUser.bind(this);
-    this.showPasswordResetModal = this.showPasswordResetModal.bind(this);
-    this.hidePasswordResetModal = this.hidePasswordResetModal.bind(this);
-  }
-
-  // TODO unstatedContainerを作ってそこにリファクタすべき
-  componentDidMount() {
-    const data = document.getElementById('admin-user-page');
-    const users = JSON.parse(data.getAttribute('users'));
-
-    this.setState({
-      users,
-    });
-  }
-
-  async removeUser(user) {
-
-    const { appContainer } = this.props;
-
-    try {
-      const response = await appContainer.apiv3.delete(`/users/${user._id}/remove`);
-      const { username } = response.data.userData;
-      toastSuccess(`Delete ${username} success`);
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  /**
-   * passwordリセットモーダルが開き、userが渡される
-   * @param {object} user
-   *
-   */
-  showPasswordResetModal(user) {
-    this.setState({
-      isPasswordResetModalShown: true,
-      userForPasswordResetModal: user,
-    });
   }
   }
 
 
-  hidePasswordResetModal() {
-    this.setState({ isPasswordResetModalShown: false });
-  }
-
-
   render() {
   render() {
-    const { t } = this.props;
+    const { t, adminUsersContainer } = this.props;
 
 
     return (
     return (
       <Fragment>
       <Fragment>
-        { this.state.userForPasswordResetModal && (
-          <PasswordResetModal
-            user={this.state.userForPasswordResetModal}
-            show={this.state.isPasswordResetModalShown}
-            onHideModal={this.hidePasswordResetModal}
-          />
-        ) }
+        {adminUsersContainer.state.userForPasswordResetModal && <PasswordResetModal />}
         <p>
         <p>
           <InviteUserControl />
           <InviteUserControl />
           <a className="btn btn-default btn-outline ml-2" href="/admin/users/external-accounts">
           <a className="btn btn-default btn-outline ml-2" href="/admin/users/external-accounts">
@@ -90,15 +36,11 @@ class UserPage extends React.Component {
             { t('user_management.external_account') }
             { t('user_management.external_account') }
           </a>
           </a>
         </p>
         </p>
-        <UserTable
-          users={this.state.users}
-          onPasswordResetClicked={this.showPasswordResetModal}
-          removeUser={this.removeUser}
-        />
+        <UserTable />
         <PaginationWrapper
         <PaginationWrapper
           activePage={this.state.activePage}
           activePage={this.state.activePage}
           changePage={this.handlePage} // / TODO GW-314 create function
           changePage={this.handlePage} // / TODO GW-314 create function
-          totalItemsCount={this.state.users.length} // TODO GW-314 props.userTotalCount
+          totalItemsCount={adminUsersContainer.state.users.length}
           pagingLimit={this.state.pagingLimit}
           pagingLimit={this.state.pagingLimit}
         />
         />
       </Fragment>
       </Fragment>
@@ -108,12 +50,13 @@ class UserPage extends React.Component {
 }
 }
 
 
 const UserPageWrapper = (props) => {
 const UserPageWrapper = (props) => {
-  return createSubscribedElement(UserPage, props, [AppContainer]);
+  return createSubscribedElement(UserPage, props, [AppContainer, AdminUsersContainer]);
 };
 };
 
 
 UserPage.propTypes = {
 UserPage.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 
 
 };
 };
 
 

+ 78 - 0
src/client/js/services/AdminUsersContainer.js

@@ -0,0 +1,78 @@
+import { Container } from 'unstated';
+
+import loggerFactory from '@alias/logger';
+
+// eslint-disable-next-line no-unused-vars
+const logger = loggerFactory('growi:services:UserGroupDetailContainer');
+
+/**
+ * Service container for admin users page (Users.jsx)
+ * @extends {Container} unstated Container
+ */
+export default class AdminUsersContainer extends Container {
+
+  constructor(appContainer) {
+    super();
+
+    this.appContainer = appContainer;
+
+    this.state = {
+      users: JSON.parse(document.getElementById('admin-user-page').getAttribute('users')) || [],
+      isPasswordResetModalShown: false,
+      isUserInviteModalShown: false,
+      userForPasswordResetModal: null,
+    };
+
+    this.showPasswordResetModal = this.showPasswordResetModal.bind(this);
+    this.hidePasswordResetModal = this.hidePasswordResetModal.bind(this);
+    this.toggleUserInviteModal = this.toggleUserInviteModal.bind(this);
+  }
+
+  /**
+   * Workaround for the mangling in production build to break constructor.name
+   */
+  static getClassName() {
+    return 'AdminUsersContainer';
+  }
+
+  /**
+   * open reset password modal, and props user
+   * @memberOf AdminUsersContainer
+   * @param {object} user
+   */
+  async showPasswordResetModal(user) {
+    await this.setState({
+      isPasswordResetModalShown: true,
+      userForPasswordResetModal: user,
+    });
+  }
+
+  /**
+   * close reset password modal
+   * @memberOf AdminUsersContainer
+   */
+  async hidePasswordResetModal() {
+    await this.setState({ isPasswordResetModalShown: false });
+  }
+
+  /**
+   * toggle user invite modal
+   * @memberOf AdminUsersContainer
+   */
+  async toggleUserInviteModal() {
+    await this.setState({ isUserInviteModalShown: !this.state.isUserInviteModalShown });
+  }
+
+  /**
+   * remove user
+   * @memberOf AdminUsersContainer
+   * @param {string} userId
+   * @return {string} username
+   */
+  async removeUser(userId) {
+    const response = await this.appContainer.apiv3.delete(`/users/${userId}/remove`);
+    const { username } = response.data.userData;
+    return username;
+  }
+
+}