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

Merge branch 'imprv/98159-swrize-apiv3-get-personal-setting-new' into imprv/98160-swrize-apiv3Put-personal-setting

kaori 3 лет назад
Родитель
Сommit
99dd40245c

+ 12 - 11
packages/app/src/components/Me/ApiSettings.tsx

@@ -1,22 +1,23 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Put } from '~/client/util/apiv3-client';
-import { usePersonalSettingsInfo } from '~/stores/personal-settings';
+import { useSWRxPersonalSettings, usePersonalSettings } from '~/stores/personal-settings';
 
 
-const ApiSettings = (): JSX.Element => {
+const ApiSettings = React.memo((): JSX.Element => {
 
   const { t } = useTranslation();
-  const { data: personalSettingsInfoData, mutate: mutatePersonalSettingsInfo } = usePersonalSettingsInfo();
+  const { mutate: mutateDatabaseData } = useSWRxPersonalSettings();
+  const { data: personalSettingsData } = usePersonalSettings();
 
-  const submitHandler = async() => {
+  const submitHandler = useCallback(async() => {
 
     try {
-      const result = await apiv3Put('/personal-setting/api-token');
-      mutatePersonalSettingsInfo(result.data.userData);
+      await apiv3Put('/personal-setting/api-token');
+      mutateDatabaseData();
 
       toastSuccess(t('toaster.update_successed', { target: t('page_me_apitoken.api_token') }));
     }
@@ -24,7 +25,7 @@ const ApiSettings = (): JSX.Element => {
       toastError(err);
     }
 
-  };
+  }, [mutateDatabaseData, t]);
 
   return (
     <>
@@ -34,7 +35,7 @@ const ApiSettings = (): JSX.Element => {
       <div className="row mb-3">
         <label htmlFor="apiToken" className="col-md-3 text-md-right">{t('Current API Token')}</label>
         <div className="col-md-6">
-          {personalSettingsInfoData?.apiToken != null
+          {personalSettingsData?.apiToken != null
             ? (
               <input
                 data-testid="grw-api-settings-input"
@@ -42,7 +43,7 @@ const ApiSettings = (): JSX.Element => {
                 className="form-control"
                 type="text"
                 name="apiToken"
-                value={personalSettingsInfoData.apiToken}
+                value={personalSettingsData.apiToken}
                 readOnly
               />
             )
@@ -83,7 +84,7 @@ const ApiSettings = (): JSX.Element => {
 
   );
 
-};
+});
 
 
 export default ApiSettings;

+ 0 - 205
packages/app/src/components/Me/BasicInfoSettings.jsx

@@ -1,205 +0,0 @@
-import React, { useEffect } from 'react';
-
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-
-import AppContainer from '~/client/services/AppContainer';
-import PersonalContainer from '~/client/services/PersonalContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { localeMetadatas } from '~/client/util/i18n';
-import { usePersonalSettingsInfo } from '~/stores/personal-settings';
-
-import { withUnstatedContainers } from '../UnstatedUtils';
-
-
-class BasicInfoSettings extends React.Component {
-
-  constructor() {
-    super();
-    this.onClickSubmit = this.onClickSubmit.bind(this);
-  }
-
-  async onClickSubmit() {
-    const { t, update } = this.props;
-
-    try {
-      await update();
-      toastSuccess(t('toaster.update_successed', { target: t('Basic Info') }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  render() {
-    const {
-      t, appContainer, personalSettingsInfo, onChangePersonalSettings, error,
-    } = this.props;
-    const { registrationWhiteList } = appContainer.getConfig();
-
-    const changePersonalSettingsHandler = (data) => {
-      if (onChangePersonalSettings == null) {
-        return;
-      }
-      onChangePersonalSettings(data);
-    };
-
-
-    return (
-      <>
-
-        <div className="form-group row">
-          <label htmlFor="userForm[name]" className="text-left text-md-right col-md-3 col-form-label">{t('Name')}</label>
-          <div className="col-md-6">
-            <input
-              className="form-control"
-              type="text"
-              name="userForm[name]"
-              defaultValue={personalSettingsInfo.name}
-              onChange={(e) => { changePersonalSettingsHandler({ ...personalSettingsInfo, name: e.target.value }) }}
-            />
-          </div>
-        </div>
-
-        <div className="form-group row">
-          <label htmlFor="userForm[email]" className="text-left text-md-right col-md-3 col-form-label">{t('Email')}</label>
-          <div className="col-md-6">
-            <input
-              className="form-control"
-              type="text"
-              name="userForm[email]"
-              defaultValue={personalSettingsInfo.email}
-              onChange={(e) => { changePersonalSettingsHandler({ ...personalSettingsInfo, email: e.target.value }) }}
-            />
-            {registrationWhiteList.length !== 0 && (
-              <div className="form-text text-muted">
-                {t('page_register.form_help.email')}
-                <ul>
-                  {registrationWhiteList.map(data => <li key={data}><code>{data}</code></li>)}
-                </ul>
-              </div>
-            )}
-          </div>
-        </div>
-
-        <div className="form-group row">
-          <label className="text-left text-md-right col-md-3 col-form-label">{t('Disclose E-mail')}</label>
-          <div className="col-md-6">
-            <div className="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioEmailShow"
-                className="custom-control-input"
-                name="userForm[isEmailPublished]"
-                checked={personalSettingsInfo.isEmailPublished}
-                onChange={() => changePersonalSettingsHandler({ ...personalSettingsInfo, isEmailPublished: true })}
-              />
-              <label className="custom-control-label" htmlFor="radioEmailShow">{t('Show')}</label>
-            </div>
-            <div className="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioEmailHide"
-                className="custom-control-input"
-                name="userForm[isEmailPublished]"
-                checked={!personalSettingsInfo.isEmailPublished}
-                onChange={() => changePersonalSettingsHandler({ ...personalSettingsInfo, isEmailPublished: false })}
-              />
-              <label className="custom-control-label" htmlFor="radioEmailHide">{t('Hide')}</label>
-            </div>
-          </div>
-        </div>
-
-        <div className="form-group row">
-          <label className="text-left text-md-right col-md-3 col-form-label">{t('Language')}</label>
-          <div className="col-md-6">
-            {
-              localeMetadatas.map(meta => (
-                <div key={meta.id} className="custom-control custom-radio custom-control-inline">
-                  <input
-                    type="radio"
-                    id={`radioLang${meta.id}`}
-                    className="custom-control-input"
-                    name="userForm[lang]"
-                    checked={personalSettingsInfo.lang === meta.id}
-                    onChange={() => { changePersonalSettingsHandler({ ...personalSettingsInfo, lang: meta.id }) }}
-                  />
-                  <label className="custom-control-label" htmlFor={`radioLang${meta.id}`}>{meta.displayName}</label>
-                </div>
-              ))
-            }
-          </div>
-        </div>
-        <div className="form-group row">
-          <label htmlFor="userForm[slackMemberId]" className="text-left text-md-right col-md-3 col-form-label">{t('Slack Member ID')}</label>
-          <div className="col-md-6">
-            <input
-              className="form-control"
-              type="text"
-              key={personalSettingsInfo.slackMemberId}
-              name="userForm[slackMemberId]"
-              defaultValue={personalSettingsInfo.slackMemberId}
-              onChange={(e) => { changePersonalSettingsHandler({ ...personalSettingsInfo, slackMemberId: e.target.value }) }}
-            />
-          </div>
-        </div>
-
-        <div className="row my-3">
-          <div className="offset-4 col-5">
-            <button
-              data-testid="grw-besic-info-settings-update-button"
-              type="button"
-              className="btn btn-primary"
-              onClick={this.onClickSubmit}
-              disabled={error != null}
-            >
-              {t('Update')}
-            </button>
-          </div>
-        </div>
-
-      </>
-    );
-  }
-
-}
-
-BasicInfoSettings.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  personalContainer: PropTypes.instanceOf(PersonalContainer).isRequired,
-  personalSettingsInfo: PropTypes.object,
-  onChangePersonalSettings: PropTypes.func,
-  update: PropTypes.func,
-  error: PropTypes.object,
-};
-
-const BasicInfoSettingsWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const usePersonalSettingsInfoResult = usePersonalSettingsInfo();
-
-
-  useEffect(() => {
-    // Sync only when getting personal settings data from DB
-    usePersonalSettingsInfoResult.sync();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [usePersonalSettingsInfoResult.personalSettingsDataFromDB]);
-
-  return (
-    <BasicInfoSettings
-      t={t}
-      personalSettingsInfo={usePersonalSettingsInfoResult.data || {}}
-      onChangePersonalSettings={usePersonalSettingsInfoResult.mutate}
-      error={usePersonalSettingsInfoResult.error}
-      update={usePersonalSettingsInfoResult.update}
-      {...props}
-    />
-  );
-};
-
-/**
- * Wrapper component for using unstated
- */
-const BasicInfoSettingsWrapper = withUnstatedContainers(BasicInfoSettingsWrapperFC, [AppContainer, PersonalContainer]);
-
-export default BasicInfoSettingsWrapper;

+ 185 - 0
packages/app/src/components/Me/BasicInfoSettings.tsx

@@ -0,0 +1,185 @@
+import React, { useEffect } from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import AppContainer from '~/client/services/AppContainer';
+import PersonalContainer from '~/client/services/PersonalContainer';
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import { localeMetadatas } from '~/client/util/i18n';
+import { useSWRxPersonalSettings, usePersonalSettings } from '~/stores/personal-settings';
+
+import { withUnstatedContainers } from '../UnstatedUtils';
+
+type Props = {
+  appContainer: AppContainer,
+  personalContainer: PersonalContainer,
+}
+
+const BasicInfoSettings = (props: Props) => {
+  const { t } = useTranslation();
+  const {
+    // personalContainer will be removed by 98160
+    appContainer, /* personalContainer, */
+  } = props;
+
+  const {
+    mutate: mutateDatabaseData,
+  } = useSWRxPersonalSettings();
+  const {
+    data: personalSettingsInfo, mutate, sync, update, error, personalSettingsDataFromDB,
+  } = usePersonalSettings();
+
+  useEffect(() => {
+    sync();
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [personalSettingsDataFromDB]);
+
+  const submitHandler = async() => {
+
+    try {
+      // TODO: SWRize apiv3Put /personal-setting/ -> https://redmine.weseek.co.jp/issues/98160
+      // await personalContainer.updateBasicInfo();
+      update();
+      mutateDatabaseData();
+      toastSuccess(t('toaster.update_successed', { target: t('Basic Info') }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
+
+
+  const { registrationWhiteList } = appContainer.getConfig();
+
+  const changePersonalSettingsHandler = (updateData) => {
+    if (personalSettingsInfo == null) {
+      return;
+    }
+    mutate({ ...personalSettingsInfo, ...updateData });
+  };
+
+
+  return (
+    <>
+
+      <div className="form-group row">
+        <label htmlFor="userForm[name]" className="text-left text-md-right col-md-3 col-form-label">{t('Name')}</label>
+        <div className="col-md-6">
+          <input
+            className="form-control"
+            type="text"
+            name="userForm[name]"
+            defaultValue={personalSettingsInfo?.name || ''}
+            onChange={e => changePersonalSettingsHandler({ name: e.target.value })}
+          />
+        </div>
+      </div>
+
+      <div className="form-group row">
+        <label htmlFor="userForm[email]" className="text-left text-md-right col-md-3 col-form-label">{t('Email')}</label>
+        <div className="col-md-6">
+          <input
+            className="form-control"
+            type="text"
+            name="userForm[email]"
+            defaultValue={personalSettingsInfo?.email || ''}
+            onChange={e => changePersonalSettingsHandler({ email: e.target.value })}
+          />
+          {registrationWhiteList.length !== 0 && (
+            <div className="form-text text-muted">
+              {t('page_register.form_help.email')}
+              <ul>
+                {registrationWhiteList.map(data => <li key={data}><code>{data}</code></li>)}
+              </ul>
+            </div>
+          )}
+        </div>
+      </div>
+
+      <div className="form-group row">
+        <label className="text-left text-md-right col-md-3 col-form-label">{t('Disclose E-mail')}</label>
+        <div className="col-md-6">
+          <div className="custom-control custom-radio custom-control-inline">
+            <input
+              type="radio"
+              id="radioEmailShow"
+              className="custom-control-input"
+              name="userForm[isEmailPublished]"
+              checked={personalSettingsInfo?.isEmailPublished || true}
+              onChange={() => changePersonalSettingsHandler({ isEmailPublished: true })}
+            />
+            <label className="custom-control-label" htmlFor="radioEmailShow">{t('Show')}</label>
+          </div>
+          <div className="custom-control custom-radio custom-control-inline">
+            <input
+              type="radio"
+              id="radioEmailHide"
+              className="custom-control-input"
+              name="userForm[isEmailPublished]"
+              checked={!personalSettingsInfo?.isEmailPublished || false}
+              onChange={() => changePersonalSettingsHandler({ isEmailPublished: false })}
+            />
+            <label className="custom-control-label" htmlFor="radioEmailHide">{t('Hide')}</label>
+          </div>
+        </div>
+      </div>
+
+      <div className="form-group row">
+        <label className="text-left text-md-right col-md-3 col-form-label">{t('Language')}</label>
+        <div className="col-md-6">
+          {
+            localeMetadatas.map(meta => (
+              <div key={meta.id} className="custom-control custom-radio custom-control-inline">
+                <input
+                  type="radio"
+                  id={`radioLang${meta.id}`}
+                  className="custom-control-input"
+                  name="userForm[lang]"
+                  checked={personalSettingsInfo?.lang === meta.id}
+                  onChange={() => changePersonalSettingsHandler({ lang: meta.id })}
+                />
+                <label className="custom-control-label" htmlFor={`radioLang${meta.id}`}>{meta.displayName}</label>
+              </div>
+            ))
+          }
+        </div>
+      </div>
+      <div className="form-group row">
+        <label htmlFor="userForm[slackMemberId]" className="text-left text-md-right col-md-3 col-form-label">{t('Slack Member ID')}</label>
+        <div className="col-md-6">
+          <input
+            className="form-control"
+            type="text"
+            key="slackMemberId"
+            name="userForm[slackMemberId]"
+            defaultValue={personalSettingsInfo?.slackMemberId || ''}
+            onChange={e => changePersonalSettingsHandler({ slackMemberId: e.target.value })}
+          />
+        </div>
+      </div>
+
+      <div className="row my-3">
+        <div className="offset-4 col-5">
+          <button
+            data-testid="grw-besic-info-settings-update-button"
+            type="button"
+            className="btn btn-primary"
+            onClick={submitHandler}
+            disabled={error != null}
+          >
+            {t('Update')}
+          </button>
+        </div>
+      </div>
+
+    </>
+  );
+};
+
+
+/**
+ * Wrapper component for using unstated
+ */
+const BasicInfoSettingsWrapper = withUnstatedContainers(BasicInfoSettings, [AppContainer, PersonalContainer]);
+
+export default BasicInfoSettingsWrapper;

+ 10 - 4
packages/app/src/components/Me/PasswordSettings.jsx

@@ -1,11 +1,11 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import { usePersonalSettingsInfo } from '~/stores/personal-settings';
+import { usePersonalSettings } from '~/stores/personal-settings';
 
 
 class PasswordSettings extends React.Component {
@@ -160,8 +160,14 @@ PasswordSettings.propTypes = {
 
 const PasswordSettingsWrapperFC = (props) => {
   const { t } = useTranslation();
-  const { mutate: mutatePersonalSetting, sync: syncPersonalSettingsInfo } = usePersonalSettingsInfo();
-  return <PasswordSettings t={t} onSubmit={mutatePersonalSetting} syncPersonalSettingsInfo={syncPersonalSettingsInfo} {...props} />;
+  const { mutate: mutatePersonalSettings } = usePersonalSettings();
+
+  const submitHandler = useCallback(() => {
+    mutatePersonalSettings();
+  }, [mutatePersonalSettings]);
+
+
+  return <PasswordSettings t={t} onSubmit={submitHandler} {...props} />;
 };
 
 export default PasswordSettingsWrapperFC;

+ 6 - 6
packages/app/src/interfaces/user.ts

@@ -3,17 +3,17 @@ import { Ref } from './common';
 import { HasObjectId } from './has-object-id';
 
 export type IUser = {
-  name: string;
-  username: string;
-  email: string;
-  password: string;
+  name: string,
+  username: string,
+  email: string,
+  password: string,
   image?: string, // for backward conpatibility
   imageAttachment?: Ref<IAttachment>,
-  imageUrlCached: string;
+  imageUrlCached: string,
   isGravatarEnabled: boolean,
-  isEmailPublished: boolean,
   admin: boolean,
   apiToken?: string,
+  isEmailPublished: boolean,
   lang: string,
   slackMemberId?: string,
 }

+ 5 - 4
packages/app/src/stores/personal-settings.tsx

@@ -10,7 +10,7 @@ import { apiv3Get, apiv3Put } from '../client/util/apiv3-client';
 import { useStaticSWR } from './use-static-swr';
 
 
-const useSWRxPersonalSettingsInfo = (): SWRResponse<IUser, Error> => {
+export const useSWRxPersonalSettings = (): SWRResponse<IUser, Error> => {
   return useSWR(
     '/personal-setting',
     endpoint => apiv3Get(endpoint).then(response => response.data.currentUser),
@@ -23,14 +23,16 @@ export type IPersonalSettingsInfoOption = {
   update: () => void,
 }
 
-export const usePersonalSettingsInfo = (): SWRResponse<IUser, Error> & IPersonalSettingsInfoOption => {
-  const { data: personalSettingsDataFromDB } = useSWRxPersonalSettingsInfo();
+export const usePersonalSettings = (): SWRResponse<IUser, Error> & IPersonalSettingsInfoOption => {
+  const { data: personalSettingsDataFromDB } = useSWRxPersonalSettings();
 
   const swrResult = useStaticSWR<IUser, Error>('personalSettingsInfo', undefined);
 
   return {
     ...swrResult,
     personalSettingsDataFromDB,
+
+    // Sync with database
     sync: (): void => {
       const { mutate } = swrResult;
       mutate(personalSettingsDataFromDB);
@@ -58,7 +60,6 @@ export const usePersonalSettingsInfo = (): SWRResponse<IUser, Error> & IPersonal
   };
 };
 
-
 export const useSWRxPersonalExternalAccounts = (): SWRResponse<IExternalAccount[], Error> => {
   return useSWR(
     '/personal-setting/external-accounts',