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

Merge pull request #1210 from weseek/confirmationPasswordModal

Confirmation password modal
itizawa 6 лет назад
Родитель
Сommit
667d058531

+ 2 - 1
resource/locales/en-US/translation.json

@@ -700,7 +700,8 @@
     "cannot_remove":"You cannot remove yourself from administrator",
     "cannot_invite_maximum_users": "Can not invite more than the maximum number of users.",
     "current_users": "Current users:",
-    "valid_email": "Valid email address is required"
+    "valid_email": "Valid email address is required",
+    "existing_email": "The following emails already exist"
   },
 
   "user_group_management": {

+ 2 - 1
resource/locales/ja/translation.json

@@ -684,7 +684,8 @@
     "cannot_remove": "自分自身を管理者から外すことはできません",
     "cannot_invite_maximum_users": "ユーザーが上限に達したため招待できません。",
     "current_users": "現在のユーザー数:",
-    "valid_email": "メールアドレスを入力してください。"
+    "valid_email": "メールアドレスを入力してください。",
+    "existing_email": "以下のEmailはすでに存在しています。"
   },
 
   "user_group_management": {

+ 135 - 35
src/client/js/components/Admin/Users/UserInviteModal.jsx

@@ -2,6 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+
 import Button from 'react-bootstrap/es/Button';
 import Modal from 'react-bootstrap/es/Modal';
 
@@ -18,11 +20,135 @@ class UserInviteModal extends React.Component {
     this.state = {
       emailInputValue: '',
       sendEmail: false,
+      invitedEmailList: null,
     };
 
     this.handleSubmit = this.handleSubmit.bind(this);
     this.handleInput = this.handleInput.bind(this);
     this.handleCheckBox = this.handleCheckBox.bind(this);
+    this.onToggleModal = this.onToggleModal.bind(this);
+  }
+
+  onToggleModal() {
+    this.props.onToggleModal();
+    this.setState({ invitedEmailList: null });
+  }
+
+  showToaster() {
+    toastSuccess('Copied Mail and Password');
+  }
+
+  renderModalBody() {
+    const { t } = this.props;
+
+    return (
+      <>
+        <label> { t('user_management.emails') }</label>
+        <textarea
+          className="form-control"
+          placeholder="e.g. user@growi.org"
+          style={{ height: '200px' }}
+          value={this.state.emailInputValue}
+          onChange={this.handleInput}
+        />
+        {!this.validEmail() && <p className="m-2 text-danger">{ t('user_management.valid_email') }</p>}
+      </>
+    );
+  }
+
+  renderCreatedModalBody() {
+    const { t } = this.props;
+    const { invitedEmailList } = this.state;
+
+    return (
+      <>
+        <p>{t('user_management.temporary_password')}</p>
+        <p>{t('user_management.send_new_password')}</p>
+        {invitedEmailList.createdUserList.length > 0 && this.renderCreatedEmail(invitedEmailList.createdUserList)}
+        {invitedEmailList.existingEmailList.length > 0 && this.renderExistingEmail(invitedEmailList.existingEmailList)}
+      </>
+    );
+  }
+
+  renderModalFooter() {
+    const { t } = this.props;
+
+    return (
+      <>
+        <label className="mr-3 text-left" style={{ flex: 1 }}>
+          <input
+            type="checkbox"
+            defaultChecked={this.state.sendEmail}
+            onChange={this.handleCheckBox}
+          />
+          <span className="ml-2">{ t('user_management.invite_thru_email') }</span>
+        </label>
+        <div>
+          <Button bsStyle="danger" className="fcbtn btn btn-xs btn-danger btn-outline btn-rounded" onClick={this.onToggleModal}>
+          Cancel
+          </Button>
+          <Button
+            bsStyle="primary"
+            className="fcbtn btn btn-primary btn-outline btn-rounded btn-1b"
+            onClick={this.handleSubmit}
+            disabled={!this.validEmail()}
+          >
+          Done
+          </Button>
+        </div>
+      </>
+    );
+  }
+
+  renderCreatedModalFooter() {
+    const { t } = this.props;
+
+    return (
+      <>
+        <label className="mr-3 text-left text-danger" style={{ flex: 1 }}>
+          {t('user_management.send_temporary_password')}
+        </label>
+        <Button
+          bsStyle="primary"
+          className="fcbtn btn btn-primary btn-outline btn-rounded"
+          onClick={this.onToggleModal}
+        >
+          Close
+        </Button>
+      </>
+    );
+  }
+
+  renderCreatedEmail(userList) {
+    return (
+      <ul>
+        {userList.map((user) => {
+          const copyText = `Email:${user.email} Password:${user.password} `;
+          return (
+            <CopyToClipboard text={copyText} onCopy={this.showToaster}>
+              <li key={user.email} className="btn">Email: <strong className="mr-3">{user.email}</strong> Password: <strong>{user.password}</strong></li>
+            </CopyToClipboard>
+          );
+        })}
+      </ul>
+    );
+  }
+
+  renderExistingEmail(emailList) {
+    const { t } = this.props;
+
+    return (
+      <>
+        <p className="text-warning">{ t('user_management.existing_email') }</p>
+        <ul>
+          {emailList.map((user) => {
+            return (
+              <li key={user}><strong>{user}</strong></li>
+            );
+          })}
+        </ul>
+      </>
+    );
   }
 
   validEmail() {
@@ -37,13 +163,12 @@ class UserInviteModal extends React.Component {
     const shapedEmailList = emailList.map((email) => { return email.trim() });
 
     try {
-      // TODO GW-230 use emailList client side
-      // eslint-disable-next-line no-unused-vars
-      const emailList = await appContainer.apiv3.post('/users/invite', {
+      const response = await appContainer.apiv3.post('/users/invite', {
         shapedEmailList,
         sendEmail: this.state.sendEmail,
       });
-      this.props.onToggleModal();
+      this.setState({ emailInputValue: '' });
+      this.setState({ invitedEmailList: response.data.emailList });
       toastSuccess('Inviting user success');
     }
     catch (err) {
@@ -61,47 +186,22 @@ class UserInviteModal extends React.Component {
 
   render() {
     const { t } = this.props;
+    const { invitedEmailList } = this.state;
 
     return (
-      <Modal show={this.props.show} onHide={this.props.onToggleModal}>
+      <Modal show={this.props.show} onHide={this.onToggleModal}>
         <Modal.Header className="modal-header" closeButton>
           <Modal.Title>
             { t('user_management.invite_users') }
           </Modal.Title>
         </Modal.Header>
         <Modal.Body>
-          <label> { t('user_management.emails') }</label>
-          <textarea
-            className="form-control"
-            placeholder="e.g. user@growi.org"
-            style={{ height: '200px' }}
-            value={this.state.emailInputValue}
-            onChange={this.handleInput}
-          />
-          {!this.validEmail() && <p className="m-2 text-danger">{ t('user_management.valid_email') }</p>}
+          {invitedEmailList == null ? this.renderModalBody()
+           : this.renderCreatedModalBody()}
         </Modal.Body>
         <Modal.Footer className="d-flex">
-          <label className="mr-3 text-left" style={{ flex: 1 }}>
-            <input
-              type="checkbox"
-              defaultChecked={this.state.sendEmail}
-              onChange={this.handleCheckBox}
-            />
-            <span className="ml-2">{ t('user_management.invite_thru_email') }</span>
-          </label>
-          <div>
-            <Button bsStyle="danger" className="fcbtn btn btn-xs btn-danger btn-outline btn-rounded" onClick={this.props.onToggleModal}>
-              Cancel
-            </Button>
-            <Button
-              bsStyle="primary"
-              className="fcbtn btn btn-primary btn-outline btn-rounded btn-1b"
-              onClick={this.handleSubmit}
-              disabled={!this.validEmail()}
-            >
-              Done
-            </Button>
-          </div>
+          {invitedEmailList == null ? this.renderModalFooter()
+           : this.renderCreatedModalFooter()}
         </Modal.Footer>
       </Modal>
     );

+ 1 - 28
src/server/routes/admin.js

@@ -438,34 +438,6 @@ module.exports = function(crowi, app) {
       isUserCountExceedsUpperLimit,
     });
   };
-  api.validators = {};
-  actions.user.api = api;
-
-  api.validators.inviteEmail = [
-    // isEmail prevents line breaks, so use isString
-    check('emailInputValue').isString(/.+@.+\..+/).withMessage('Error. Valid email address is required'),
-  ];
-
-  actions.user.invite = async function(req, res) {
-
-    const { validationResult } = require('express-validator');
-    const errors = validationResult(req);
-    if (!errors.isEmpty()) {
-      return res.json(ApiResponse.error('Valid email address is required'));
-    }
-
-    const array = req.body.emailInputValue.split('\n');
-    const emailList = array.filter((element) => { return element.match(/.+@.+\..+/) });
-    const shapedEmailList = emailList.map((email) => { return email.trim() });
-
-    try {
-      const emailList = await User.createUsersByInvitation(shapedEmailList, req.body.sendEmail);
-      return emailList;
-    }
-    catch (err) {
-      return res.json(ApiResponse.error(err));
-    }
-  };
 
   actions.user.makeAdmin = function(req, res) {
     const id = req.params.id;
@@ -710,6 +682,7 @@ module.exports = function(crowi, app) {
   // Importer management
   actions.importer = {};
   actions.importer.api = api;
+  api.validators = {};
   api.validators.importer = {};
 
   actions.importer.index = function(req, res) {