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

Merge pull request #1595 from weseek/reactify/create-component-for-manage-external-account

Reactify/create component for manage external account
Yuki Takei 6 лет назад
Родитель
Сommit
820475b75c

+ 7 - 2
src/client/js/components/Me/BasicInfoSettings.jsx

@@ -21,8 +21,13 @@ class BasicInfoSettings extends React.Component {
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
-  componentDidMount() {
-    this.props.personalContainer.retrievePersonalData();
+  async componentDidMount() {
+    try {
+      await this.props.personalContainer.retrievePersonalData();
+    }
+    catch (err) {
+      toastError(err);
+    }
   }
 
   async onClickSubmit() {

+ 73 - 0
src/client/js/components/Me/ExternalAccountLinkedMe.jsx

@@ -0,0 +1,73 @@
+
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../UnstatedUtils';
+import { toastError } from '../../util/apiNotification';
+
+import AppContainer from '../../services/AppContainer';
+import PersonalContainer from '../../services/PersonalContainer';
+import ExternalAccountRow from './ExternalAccountRow';
+
+class ExternalAccountLinkedMe extends React.Component {
+
+  async componentDidMount() {
+    try {
+      await this.props.personalContainer.retrieveExternalAccounts();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+  render() {
+    const { t, personalContainer } = this.props;
+    const { externalAccounts } = personalContainer.state;
+
+    return (
+      <Fragment>
+        <h2 className="border-bottom">
+          <button type="button" className="btn btn-default btn-sm pull-right">
+            <i className="icon-plus" aria-hidden="true" />
+            Add
+          </button>
+          { t('External Accounts') }
+        </h2>
+
+        <div className="row">
+          <div className="col-md-12">
+            <table className="table table-bordered table-user-list">
+              <thead>
+                <tr>
+                  <th width="120px">Authentication Provider</th>
+                  <th>
+                    <code>accountId</code>
+                  </th>
+                  <th width="200px">{ t('Created') }</th>
+                  <th width="150px">{ t('Admin') }</th>
+                </tr>
+              </thead>
+              <tbody>
+                {externalAccounts !== 0 && externalAccounts.map(account => <ExternalAccountRow account={account} key={account._id} />)}
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </Fragment>
+    );
+  }
+
+}
+
+const ExternalAccountLinkedMeWrapper = (props) => {
+  return createSubscribedElement(ExternalAccountLinkedMe, props, [AppContainer, PersonalContainer]);
+};
+
+ExternalAccountLinkedMe.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  personalContainer: PropTypes.instanceOf(PersonalContainer).isRequired,
+};
+
+export default withTranslation()(ExternalAccountLinkedMeWrapper);

+ 41 - 0
src/client/js/components/Me/ExternalAccountRow.jsx

@@ -0,0 +1,41 @@
+
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+import dateFnsFormat from 'date-fns/format';
+
+class ExternalAccountRow extends React.PureComponent {
+
+  render() {
+    const { t, account } = this.props;
+
+    return (
+      <tr>
+        <td>{ account.providerType }</td>
+        <td>
+          <strong>{ account.accountId }</strong>
+        </td>
+        <td>{dateFnsFormat(new Date(account.createdAt), 'yyyy-MM-dd')}</td>
+        <td className="text-center">
+          <button
+            type="button"
+            className="btn btn-default btn-sm btn-danger"
+          >
+            <i className="ti-unlink"></i>
+            { t('Diassociate') }
+          </button>
+        </td>
+      </tr>
+    );
+  }
+
+}
+
+ExternalAccountRow.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  account: PropTypes.object.isRequired,
+};
+
+export default withTranslation()(ExternalAccountRow);

+ 2 - 1
src/client/js/components/Me/PersonalSettings.jsx

@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
 import UserSettings from './UserSettings';
+import ExternalAccountLinkedMe from './ExternalAccountLinkedMe';
 
 class PersonalSettings extends React.Component {
 
@@ -34,7 +35,7 @@ class PersonalSettings extends React.Component {
                 <UserSettings />
               </div>
               <div id="external-accounts" className="tab-pane" role="tabpanel">
-                {/* TODO GW-1029 create component */}
+                <ExternalAccountLinkedMe />
               </div>
               <div id="password-settings" className="tab-pane" role="tabpanel">
                 {/* TODO GW-1030 create component */}

+ 20 - 4
src/client/js/services/PersonalContainer.js

@@ -2,8 +2,6 @@ import { Container } from 'unstated';
 
 import loggerFactory from '@alias/logger';
 
-import { toastError } from '../util/apiNotification';
-
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:services:PersonalContainer');
 
@@ -25,6 +23,7 @@ export default class PersonalContainer extends Container {
       registrationWhiteList: this.appContainer.getConfig().registrationWhiteList,
       isEmailPublished: false,
       lang: 'en-US',
+      externalAccounts: [],
     };
 
   }
@@ -52,9 +51,26 @@ export default class PersonalContainer extends Container {
       });
     }
     catch (err) {
-      this.setState({ retrieveError: err.message });
+      this.setState({ retrieveError: err });
+      logger.error(err);
+      throw new Error('Failed to fetch personal data');
+    }
+  }
+
+  /**
+   * retrieve external accounts that linked me
+   */
+  async retrieveExternalAccounts() {
+    try {
+      const response = await this.appContainer.apiv3.get('/personal-setting/external-accounts');
+      const { externalAccounts } = response.data;
+
+      this.setState({ externalAccounts });
+    }
+    catch (err) {
+      this.setState({ retrieveError: err });
       logger.error(err);
-      toastError(new Error('Failed to fetch data'));
+      throw new Error('Failed to fetch external accounts');
     }
   }
 

+ 35 - 1
src/server/routes/apiv3/personal-setting.js

@@ -21,7 +21,7 @@ module.exports = (crowi) => {
   const csrf = require('../../middleware/csrf')(crowi);
   const { customizeService } = crowi;
 
-  const { User } = crowi.models;
+  const { User, ExternalAccount } = crowi.models;
 
 
   const { ApiV3FormValidator } = crowi.middlewares;
@@ -51,5 +51,39 @@ module.exports = (crowi) => {
     return res.apiv3({ currentUser });
   });
 
+  /**
+   * @swagger
+   *
+   *    /personal-setting/external-accounts:
+   *      get:
+   *        tags: [PersonalSetting]
+   *        operationId: getExternalAccounts
+   *        summary: /personal-setting/external-accounts
+   *        description: Get external accounts that linked current user
+   *        responses:
+   *          200:
+   *            description: external accounts
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    externalAccounts:
+   *                      type: object
+   *                      description: array of external accounts
+   */
+  router.get('/external-accounts', loginRequiredStrictly, async(req, res) => {
+    const userData = req.user;
+
+    try {
+      const externalAccounts = await ExternalAccount.find({ user: userData });
+      return res.apiv3({ externalAccounts });
+    }
+    catch (err) {
+      logger.error(err);
+      return res.apiv3Err('get-external-accounts-failed');
+    }
+
+  });
+
   return router;
 };