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

Merge pull request #3977 from weseek/feat/GW-6629-6652-resend-invitation-mail

Feat/gw 6629 6652 resend invitation mail
Shun Miyazawa 4 лет назад
Родитель
Сommit
70aee4a9d2

+ 3 - 1
resource/locales/en_US/admin/admin.json

@@ -285,7 +285,9 @@
       "your_own": "You cannot deactivate your own account",
       "remove_admin_access": "Remove admin access",
       "cannot_remove": "You cannot remove yourself from administrator",
-      "give_admin_access": "Give admin access"
+      "give_admin_access": "Give admin access",
+      "send_invitation_email": "Send invitation email",
+      "resend_invitation_email": "Resend invitation email"
     },
     "reset_password": "Reset Password",
     "reset_password_modal": {

+ 3 - 1
resource/locales/ja_JP/admin/admin.json

@@ -283,7 +283,9 @@
       "your_own": "自分自身のアカウントを停止することはできません",
       "remove_admin_access": "管理者から外す",
       "cannot_remove": "自分自身を管理者から外すことはできません",
-      "give_admin_access": "管理者にする"
+      "give_admin_access": "管理者にする",
+      "send_invitation_email": "招待メールの送信",
+      "resend_invitation_email": "招待メールの再送信"
     },
     "reset_password": "パスワードのリセット",
     "reset_password_modal": {

+ 3 - 1
resource/locales/zh_CN/admin/admin.json

@@ -293,7 +293,9 @@
 			"your_own": "您不能停用自己的帐户",
 			"remove_admin_access": "删除管理员访问权限",
 			"cannot_remove": "您不能从管理员中删除自己",
-			"give_admin_access": "授予管理员访问权限"
+			"give_admin_access": "授予管理员访问权限",
+      "send_invitation_email": "发送邀请邮件",
+      "resend_invitation_email": "重发邀请函"
 		},
 		"reset_password": "重置密码",
 		"reset_password_modal": {

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

@@ -0,0 +1,49 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+import AppContainer from '../../../services/AppContainer';
+import AdminUsersContainer from '../../../services/AdminUsersContainer';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
+const SendInvitationEmailButton = (props) => {
+  const { appContainer, user } = props;
+  const { t } = useTranslation();
+
+  const textColor = !user.isInvitationEmailSended ? 'text-danger' : '';
+
+  const onClickSendInvitationEmailButton = async() => {
+    try {
+      const res = await appContainer.apiv3Put('users/send-invitation-email', { id: user._id });
+      const { failedToSendEmail } = res.data;
+      if (failedToSendEmail == null) {
+        const msg = `Email has been sent<br>・${user.email}`;
+        toastSuccess(msg);
+      }
+      else {
+        const msg = { message: `email: ${failedToSendEmail.email}<br>reason: ${failedToSendEmail.reason}` };
+        toastError(msg);
+      }
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
+
+  return (
+    <button className={`dropdown-item ${textColor}`} type="button" onClick={() => { onClickSendInvitationEmailButton() }}>
+      <i className="icon-fw icon-envelope"></i>
+      {user.isInvitationEmailSended && (<>{t('admin:user_management.user_table.resend_invitation_email')}</>)}
+      {!user.isInvitationEmailSended && (<>{t('admin:user_management.user_table.send_invitation_email')}</>)}
+    </button>
+  );
+};
+
+const SendInvitationEmailButtonWrapper = withUnstatedContainers(SendInvitationEmailButton, [AppContainer, AdminUsersContainer]);
+
+SendInvitationEmailButton.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  user: PropTypes.object.isRequired,
+};
+
+export default SendInvitationEmailButtonWrapper;

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

@@ -7,9 +7,10 @@ import {
 
 import StatusActivateButton from './StatusActivateButton';
 import StatusSuspendedButton from './StatusSuspendedButton';
-import RemoveUserButton from './UserRemoveButton';
+import UserRemoveButton from './UserRemoveButton';
 import RemoveAdminButton from './RemoveAdminButton';
 import GiveAdminButton from './GiveAdminButton';
+import SendInvitationEmailButton from './SendInvitationMailButton';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
@@ -57,7 +58,8 @@ class UserMenu extends React.Component {
         <li>
           {(user.status === 1 || user.status === 3) && <StatusActivateButton user={user} />}
           {user.status === 2 && <StatusSuspendedButton user={user} />}
-          {(user.status === 1 || user.status === 3 || user.status === 5) && <RemoveUserButton user={user} />}
+          {user.status === 5 && <SendInvitationEmailButton user={user} />}
+          {(user.status === 1 || user.status === 3 || user.status === 5) && <UserRemoveButton user={user} />}
         </li>
       </Fragment>
     );
@@ -85,7 +87,7 @@ class UserMenu extends React.Component {
       <UncontrolledDropdown id="userMenu" size="sm">
         <DropdownToggle caret color="secondary" outline>
           <i className="icon-settings" />
-          {(!user.isInvitationEmailSended && user.status === 5) && <i className="fa fa-circle text-danger grw-usermenu-notification-icon" />}
+          {(user.status === 5 && !user.isInvitationEmailSended) && <i className="fa fa-circle text-danger grw-usermenu-notification-icon" />}
         </DropdownToggle>
         <DropdownMenu positionFixed>
           {this.renderEditMenu()}

+ 50 - 0
src/server/routes/apiv3/users.js

@@ -816,5 +816,55 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /users/send-invitation-email:
+   *      put:
+   *        tags: [Users]
+   *        operationId: sendInvitationEmail
+   *        summary: /users/send-invitation-email
+   *        description: send invitation email
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  id:
+   *                    type: string
+   *                    description: user id for send invitation email
+   *        responses:
+   *          200:
+   *            description: success send invitation email
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    failedToSendEmail:
+   *                      type: object
+   *                      description: email and reasons for email sending failure
+   */
+  router.put('/send-invitation-email', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
+    const { id } = req.body;
+
+    try {
+      const user = await User.findById(id);
+      const newPassword = await User.resetPasswordByRandomString(id);
+      const userList = [{
+        email: user.email,
+        password: newPassword,
+        user: { id },
+      }];
+      const sendEmail = await sendEmailByUserList(userList);
+      // return null if absent
+      return res.apiv3({ failedToSendEmail: sendEmail.failedToSendEmailList[0] });
+    }
+    catch (err) {
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(err));
+    }
+  });
+
   return router;
 };