Przeglądaj źródła

Merge pull request #3736 from weseek/imprv/5878-make-component-for-with-proxy-accordion

Imprv/5878 make a component for with proxy accordion
Kaori Tokashiki 4 lat temu
rodzic
commit
5f76a65202

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

@@ -306,7 +306,7 @@
       "generate_access_token": "Generate Access Token",
       "register_for_growi_official_bot_proxy_service": "Register for GROWI Official Bot Proxy Service",
       "enter_growi_register_on_slack": "Enter `/growi register` on slack",
-      "paste_growi_url": "Enter `http://localhost:3000` for <b>GROWI URL</b>",
+      "paste_growi_url": "Since a modal is displayed, enter the following URL in <b>GROWI URL</b>.",
       "enter_access_token_for_growi_and_proxy": "Enter <b>Access Token for GROWI</b> and <b>Access Token for Proxy</b>",
       "set_proxy_url_on_growi": "Set Proxy URL on GROWI",
       "enter_proxy_url_and_update": "1. Enter and update the Proxy URL that you copied in step ③ in the <b>Proxy URL</b>  of the <b>Custom bot with proxy integration</b> on this page.",

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

@@ -303,7 +303,7 @@
       "generate_access_token": "Access Tokenの発行",
       "register_for_growi_official_bot_proxy_service": "GROWI Official Bot Proxy サービスへの登録",
       "enter_growi_register_on_slack": "Slack上で `/growi register` と打つ",
-      "paste_growi_url": "<b>GROWI URL</b>には`http://localhost:3000`を貼り付ける",
+      "paste_growi_url": "モーダルが表示されるので、<b>GROWI URL</b> には下記のURLを入力します。",
       "enter_access_token_for_growi_and_proxy": "上記で発行した<b>Access Token for GROWI</b> と <b>Access Token for Proxy</b>を入れる",
       "set_proxy_url_on_growi": "ProxyのURLをGROWIに登録する",
       "enter_proxy_url_and_update": "1. 連携手順③でコピーしたProxy URLを、このページの<b>Custom bot with proxy 連携</b>の<b>Proxy URL</b>に入力、更新します。",

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

@@ -313,7 +313,7 @@
       "generate_access_token": "生成Access Token",
       "register_for_growi_official_bot_proxy_service": "注册 GROWI Official Bot Proxy Service",
       "enter_growi_register_on_slack": "在Slack中,输入`/growi register`。",
-      "paste_growi_url": "将`http://localhost:3000`粘贴到 <b>GROWI URL</b> 网址中",
+      "paste_growi_url": "由于显示了模式,请在 <b>GROWI URL</b> 中输入以下URL",
       "enter_access_token_for_growi_and_proxy": "插入上面发出的 <b>Access Token for GROWI</b> 和 <b>Access Token for Proxy</b>。",
       "set_proxy_url_on_growi": "向GROWI注册Proxy的URL",
       "enter_proxy_url_and_update": "1. 输入并更新你在步骤③中复制的ProxyURL到本页的<b>Custom bot with proxy 一体化</b>的<b>ProxyURL</b>。",

+ 46 - 11
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -1,21 +1,43 @@
-import React, { useState } from 'react';
+import React, {
+  useState, useEffect, useCallback,
+} from 'react';
 import { useTranslation } from 'react-i18next';
 import PropTypes from 'prop-types';
+import loggerFactory from '@alias/logger';
 import AppContainer from '../../../services/AppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import CustomBotWithProxyIntegrationCard from './CustomBotWithProxyIntegrationCard';
-import CustomBotWithProxySettingsAccordion from './CustomBotWithProxySettingsAccordion';
+import WithProxyAccordions from './WithProxyAccordions';
 import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
-import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+
+const logger = loggerFactory('growi:SlackBotSettings');
 
 const CustomBotWithProxySettings = (props) => {
   // eslint-disable-next-line no-unused-vars
   const { appContainer } = props;
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
+  const [proxyUri, setProxyUri] = useState(null);
 
   const { t } = useTranslation();
 
+  const retrieveProxyUri = useCallback(async() => {
+    try {
+      const res = await appContainer.apiv3.get('/slack-integration-settings');
+      const { proxyUri } = res.data.settings;
+      setProxyUri(proxyUri);
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }, [appContainer.apiv3]);
+
+  useEffect(() => {
+    retrieveProxyUri();
+  }, [retrieveProxyUri]);
+
+
   // TODO: Multiple accordion logic
   const [accordionComponentsCount, setAccordionComponentsCount] = useState(0);
   const addAccordionHandler = () => {
@@ -43,6 +65,19 @@ const CustomBotWithProxySettings = (props) => {
     }
   };
 
+  const updateProxyUri = async() => {
+    try {
+      await appContainer.apiv3.put('/slack-integration-settings/proxy-uri', {
+        proxyUri,
+      });
+      toastSuccess(t('toaster.update_successed', { target: t('Proxy URL') }));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  };
+
   return (
     <>
       <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
@@ -67,18 +102,18 @@ const CustomBotWithProxySettings = (props) => {
 
       <div className="form-group row my-4">
         <label className="text-left text-md-right col-md-3 col-form-label mt-3">Proxy URL</label>
-        <div className="col-md-6 mr-3 mt-3">
+        <div className="col-md-6 mt-3">
           <input
             className="form-control"
             type="text"
+            name="settingForm[proxyUrl]"
+            defaultValue={proxyUri}
+            onChange={(e) => { setProxyUri(e.target.value) }}
           />
         </div>
-        <AdminUpdateButtonRow
-          disabled={false}
-          // TODO: Add Proxy URL submit logic
-          // eslint-disable-next-line no-console
-          onClick={() => console.log('Update')}
-        />
+        <div className="col-md-2 mt-3 text-center text-md-left">
+          <button type="button" className="btn btn-primary" onClick={updateProxyUri} disabled={false}>{ t('Update') }</button>
+        </div>
       </div>
 
       <h2 className="admin-setting-header">{t('admin:slack_integration.cooperation_procedure')}</h2>
@@ -97,7 +132,7 @@ const CustomBotWithProxySettings = (props) => {
                 {t('admin:slack_integration.delete')}
               </button>
             </div>
-            <CustomBotWithProxySettingsAccordion key={i} />
+            <WithProxyAccordions botType="customBotWithProxy" key={i} />
           </>
         ))}
 

+ 0 - 210
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettingsAccordion.jsx

@@ -1,210 +0,0 @@
-import React, { useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import Accordion from '../Common/Accordion';
-
-const CustomBotWithProxySettingsAccordion = () => {
-  const [testChannel, setTestChannel] = useState('');
-  /* eslint-disable no-unused-vars */
-  // TODO: Add connection Logs
-  const [connectionErrorCode, setConnectionErrorCode] = useState(null);
-  const [connectionErrorMessage, setConnectionErrorMessage] = useState(null);
-  const [connectionSuccessMessage, setConnectionSuccessMessage] = useState(null);
-
-  const { t } = useTranslation();
-
-  // TODO: Handle test button
-  const submitForm = (e) => {
-    e.preventDefault();
-    // eslint-disable-next-line no-console
-    console.log('Form Submitted');
-  };
-
-  const inputTestChannelHandler = (channel) => {
-    setTestChannel(channel);
-  };
-
-  // TODO: Show test logs
-  let value = '';
-  if (connectionErrorMessage != null) {
-    value = [connectionErrorCode, connectionErrorMessage];
-  }
-  if (connectionSuccessMessage != null) {
-    value = connectionSuccessMessage;
-  }
-
-  return (
-    <div className="card border-0 rounded-lg shadow overflow-hidden">
-      <Accordion
-        title={<><span className="mr-2">①</span>{t('admin:slack_integration.accordion.create_bot')}</>}
-      >
-        <div className="my-5 d-flex flex-column align-items-center">
-          <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
-            {t('admin:slack_integration.accordion.create_bot')}
-            <i className="fa fa-external-link ml-2" aria-hidden="true" />
-          </button>
-          {/* TODO: Insert DOCS link */}
-          <a href="#">
-            <p className="text-center mt-1">
-              <small>
-                {t('admin:slack_integration.accordion.how_to_create_a_bot')}
-                <i className="fa fa-external-link ml-2" aria-hidden="true" />
-              </small>
-            </p>
-          </a>
-        </div>
-      </Accordion>
-      <Accordion
-        title={<><span className="mr-2">②</span>{t('admin:slack_integration.accordion.install_bot_to_slack')}</>}
-      >
-        <div className="my-5 d-flex flex-column align-items-center">
-          {/* TODO: Insert install link */}
-          <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
-            {t('admin:slack_integration.accordion.install_now')}
-            <i className="fa fa-external-link ml-2" aria-hidden="true" />
-          </button>
-          {/* TODO: Insert DOCS link */}
-          <a href="#">
-            <p className="text-center mt-1">
-              <small>
-                {t('admin:slack_integration.accordion.how_to_install')}
-                <i className="fa fa-external-link ml-2" aria-hidden="true" />
-              </small>
-            </p>
-          </a>
-        </div>
-      </Accordion>
-      <Accordion
-        title={(
-          <>
-            <span className="mr-2">③</span>
-            {t('admin:slack_integration.accordion.generate_access_token')}
-            {' / '}
-            {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}
-          </>
-        )}
-      >
-        <div className="py-4 px-5">
-          <p className="font-weight-bold">1. {t('admin:slack_integration.accordion.generate_access_token')}</p>
-          <div className="form-group row">
-            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
-            <div className="col-md-6">
-              <input
-                className="form-control"
-                type="text"
-              />
-            </div>
-          </div>
-          <div className="form-group row">
-            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
-            <div className="col-md-6">
-              <input
-                className="form-control"
-                type="text"
-              />
-            </div>
-          </div>
-
-          <div className="row my-3">
-            <div className="mx-auto">
-              <button type="button" className="btn btn-outline-secondary mx-2">{ t('admin:slack_integration.access_token_settings.discard') }</button>
-              <button type="button" className="btn btn-primary mx-2">{ t('admin:slack_integration.access_token_settings.generate') }</button>
-            </div>
-          </div>
-          <p className="font-weight-bold">2. {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}</p>
-          <div className="d-flex flex-column align-items-center">
-            <ol className="p-0">
-              <li><p className="ml-2">{t('admin:slack_integration.accordion.enter_growi_register_on_slack')}</p></li>
-              <li>
-                <p
-                  className="ml-2"
-                  // TODO: Add dynamic link
-                  // TODO: Copy to clipboard on click
-                  // TODO: Add logo
-                  // eslint-disable-next-line react/no-danger
-                  dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.paste_growi_url') }}
-                />
-              </li>
-              <li>
-                <p
-                  className="ml-2"
-                  // eslint-disable-next-line react/no-danger
-                  dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_access_token_for_growi_and_proxy') }}
-                />
-              </li>
-            </ol>
-            {/* TODO: Insert photo */}
-            <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
-              <h1 className="text-muted">参考画像</h1>
-            </div>
-          </div>
-        </div>
-      </Accordion>
-      <Accordion
-        title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.set_proxy_url_on_growi')}</>}
-      >
-        <div className="p-4 d-flex flex-column align-items-center">
-          <div>
-            <span
-                // eslint-disable-next-line react/no-danger
-              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_proxy_url_and_update') }}
-            />
-            <p className="text-danger">{t('admin:slack_integration.accordion.dont_need_update')}</p>
-          </div>
-          {/* TODO: Insert photo */}
-          <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
-            <h1 className="text-muted">参考画像</h1>
-          </div>
-        </div>
-      </Accordion>
-      <Accordion
-        title={<><span className="mr-2">⑤</span>{t('admin:slack_integration.accordion.test_connection')}</>}
-      >
-        {/* TODO: Responsive */}
-        <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
-        <div className="d-flex justify-content-center">
-          <form className="form-row justify-content-center" onSubmit={e => submitForm(e)}>
-            <div className="input-group col-8">
-              <div className="input-group-prepend">
-                <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
-              </div>
-              <input
-                className="form-control"
-                type="text"
-                value={testChannel}
-                placeholder="Slack Channel"
-                // TODO: Handle test button
-                onChange={e => inputTestChannelHandler(e.target.value)}
-              />
-            </div>
-            <button
-              type="submit"
-              className="btn btn-info mx-3 font-weight-bold"
-              disabled={testChannel.trim() === ''}
-            >Test
-            </button>
-          </form>
-        </div>
-        {connectionErrorMessage != null
-          && <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>}
-        {connectionSuccessMessage != null
-          && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
-        <form>
-          <div className="row my-3 justify-content-center">
-            <div className="form-group slack-connection-log col-md-4">
-              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2 m-0">Logs</p></label>
-              <textarea
-                className="form-control card border-info slack-connection-log-body rounded-lg"
-                rows="5"
-                // TODO: Show test logs
-                value={value}
-                readOnly
-              />
-            </div>
-          </div>
-        </form>
-      </Accordion>
-    </div>
-  );
-};
-
-export default CustomBotWithProxySettingsAccordion;

+ 2 - 2
src/client/js/components/Admin/SlackIntegration/OfficialBotSettings.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
-import OfficialBotSettingsAccordion from './OfficialbotSettingsAccordion';
 import CustomBotWithProxyIntegrationCard from './CustomBotWithProxyIntegrationCard';
+import WithProxyAccordions from './WithProxyAccordions';
 
 const OfficialBotSettings = () => {
   const { t } = useTranslation();
@@ -30,7 +30,7 @@ const OfficialBotSettings = () => {
       <h2 className="admin-setting-header">{t('admin:slack_integration.official_bot_settings')}</h2>
 
       <div className="my-5 mx-3">
-        <OfficialBotSettingsAccordion />
+        <WithProxyAccordions botType="officialBot" />
       </div>
     </>
 

+ 0 - 182
src/client/js/components/Admin/SlackIntegration/OfficialbotSettingsAccordion.jsx

@@ -1,182 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import { CopyToClipboard } from 'react-copy-to-clipboard';
-import loggerFactory from '@alias/logger';
-import Accordion from '../Common/Accordion';
-import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import { toastSuccess, toastError } from '../../../util/apiNotification';
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import AppContainer from '../../../services/AppContainer';
-
-const logger = loggerFactory('growi:SlackBotSettings'); //
-
-const OfficialBotSettingsAccordion = (props) => {
-  // TODO: apply i18n by GW-5878
-  const { t } = useTranslation();
-  const { appContainer } = props;
-  const [proxyUri, setProxyUri] = useState(null);
-
-  const growiUrl = appContainer.config.crowi.url;
-
-  const updateProxyUri = async() => {
-    try {
-      await appContainer.apiv3.put('/slack-integration-settings/proxy-uri', {
-        proxyUri,
-      });
-      toastSuccess(t('toaster.update_successed', { target: t('Proxy URL') }));
-    }
-    catch (err) {
-      toastError(err);
-      logger.error(err);
-    }
-  };
-
-  return (
-    <div className="card border-0 rounded-lg shadow overflow-hidden">
-      <Accordion
-        title={<><span className="mr-2">①</span>{t('admin:slack_integration.accordion.install_bot_to_slack')}</>}
-      >
-        <div className="my-5 d-flex flex-column align-items-center">
-          {/* TODO: Insert install link */}
-          <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank', 'noreferrer')}>
-            {t('admin:slack_integration.accordion.install_now')}
-            <i className="fa fa-external-link ml-2" aria-hidden="true" />
-          </button>
-          {/* TODO: Insert DOCS link */}
-          <a href="#">
-            <p className="text-center mt-1">
-              <small>
-                {t('admin:slack_integration.accordion.how_to_install')}
-                <i className="fa fa-external-link ml-2" aria-hidden="true" />
-              </small>
-            </p>
-          </a>
-        </div>
-      </Accordion>
-      <Accordion
-        title={<><span className="mr-2">②</span>{t('admin:slack_integration.accordion.register_official_bot_proxy_service')}</>}
-      >
-        <div className="py-4 px-5">
-          <p className="font-weight-bold">1. Access Tokenの発行</p>
-          <div className="form-group row">
-            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
-            <div className="col-md-6">
-              <input
-                className="form-control"
-                type="text"
-              />
-            </div>
-          </div>
-          <div className="form-group row">
-            <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
-            <div className="col-md-6">
-              <input
-                className="form-control"
-                type="text"
-              />
-            </div>
-          </div>
-          <div className="row my-3">
-            <div className="mx-auto">
-              <button type="button" className="btn btn-outline-secondary mx-2">破棄</button>
-              <button type="button" className="btn btn-primary mx-2">{ t('Update') }</button>
-            </div>
-          </div>
-          <p className="font-weight-bold">2. GROWI Official Bot Proxy サービスへの登録</p>
-          <div className="d-flex flex-column align-items-center">
-            <ol className="p-0">
-              <li><p className="ml-2">Slack上で`/growi register`と打つ</p></li>
-              <li>
-                <div className="input-group align-items-center ml-2 mb-3">
-                  <b> GROWI URL</b>には
-                  <div className="input-group-prepend mx-1">
-                    <input className="form-control" type="text" value={growiUrl} readOnly />
-                    <CopyToClipboard text={growiUrl} onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
-                      <div className="btn input-group-text">
-                        <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
-                      </div>
-                    </CopyToClipboard>
-                  </div>
-                    を貼り付ける
-                </div>
-              </li>
-              <li><p className="ml-2">上記で発行した<b>Access Token for GROWI と Access Token for Proxy</b>を入れる</p></li>
-            </ol>
-            {/* TODO: Insert photo by GW5857 */}
-            <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
-              <h1 className="text-muted">参考画像</h1>
-            </div>
-          </div>
-        </div>
-      </Accordion>
-      <Accordion
-        title={<><span className="mr-2">③</span>{t('admin:slack_integration.accordion.register_proxy_url')}</>}
-      >
-        <div className="p-4">
-          <p className="text-center">Slack上に通知された<b>Proxy URL</b>を入力し、更新してください。</p>
-          <div className="form-group row my-4">
-            <label className="text-left text-md-right col-md-3 col-form-label">Proxy URL</label>
-            <div className="col-md-6">
-              <input
-                className="form-control"
-                type="text"
-                onChange={(e) => { setProxyUri(e.target.value) }}
-              />
-            </div>
-          </div>
-          <AdminUpdateButtonRow
-            disabled={false}
-            onClick={() => updateProxyUri()}
-          />
-        </div>
-      </Accordion>
-      <Accordion
-        title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.test_connection')}</>}
-      >
-        <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
-        <div className="d-flex justify-content-center">
-          <form className="form-row align-items-center w-25">
-            <div className="col-8 input-group-prepend">
-              <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
-              <input
-                className="form-control w-100"
-                type="text"
-                placeholder="Slack Channel"
-              />
-            </div>
-            <div className="col-4">
-              <button
-                type="submit"
-                className="btn btn-info mx-3 font-weight-bold"
-              >Test
-              </button>
-            </div>
-          </form>
-        </div>
-        <form>
-          <div className="row my-3 justify-content-center">
-            <div className="form-group slack-connection-log w-25">
-              <label className="mb-1"><p className="border-info slack-connection-log-title pl-2">Logs</p></label>
-              <textarea
-                className="form-control card border-info slack-connection-log-body rounded-lg"
-                readOnly
-              />
-            </div>
-          </div>
-        </form>
-      </Accordion>
-    </div>
-  );
-};
-
-/**
- * Wrapper component for using unstated
- */
-const OfficialBotSettingsAccordionWrapper = withUnstatedContainers(OfficialBotSettingsAccordion, [AppContainer]);
-
-OfficialBotSettingsAccordion.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-};
-
-export default OfficialBotSettingsAccordionWrapper;

+ 304 - 0
src/client/js/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -0,0 +1,304 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess } from '../../../util/apiNotification';
+import AppContainer from '../../../services/AppContainer';
+import Accordion from '../Common/Accordion';
+
+
+export const BotCreateProcess = () => {
+  const { t } = useTranslation();
+  return (
+    <div className="my-5 d-flex flex-column align-items-center">
+      <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
+        {t('admin:slack_integration.accordion.create_bot')}
+        <i className="fa fa-external-link ml-2" aria-hidden="true" />
+      </button>
+      {/* TODO: Insert DOCS link */}
+      <a href="#">
+        <p className="text-center mt-1">
+          <small>
+            {t('admin:slack_integration.accordion.how_to_create_a_bot')}
+            <i className="fa fa-external-link ml-2" aria-hidden="true" />
+          </small>
+        </p>
+      </a>
+    </div>
+  );
+};
+
+export const BotInstallProcess = () => {
+  const { t } = useTranslation();
+  return (
+    <div className="my-5 d-flex flex-column align-items-center">
+      {/* TODO: Insert install link */}
+      <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
+        {t('admin:slack_integration.accordion.install_now')}
+        <i className="fa fa-external-link ml-2" aria-hidden="true" />
+      </button>
+      {/* TODO: Insert DOCS link */}
+      <a href="#">
+        <p className="text-center mt-1">
+          <small>
+            {t('admin:slack_integration.accordion.how_to_install')}
+            <i className="fa fa-external-link ml-2" aria-hidden="true" />
+          </small>
+        </p>
+      </a>
+    </div>
+  );
+};
+
+export const RegisteringProxyUrlProcess = () => {
+  const { t } = useTranslation();
+  return (
+    <div className="p-4 d-flex flex-column align-items-center">
+      <div>
+        <span
+          // eslint-disable-next-line react/no-danger
+          dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_proxy_url_and_update') }}
+        />
+        <p className="text-danger">{t('admin:slack_integration.accordion.dont_need_update')}</p>
+      </div>
+      <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
+        <h1 className="text-muted">参考画像</h1>
+      </div>
+    </div>
+  );
+};
+
+export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers((props) => {
+  const { t } = useTranslation();
+  const growiUrl = props.appContainer.config.crowi.url;
+  return (
+    <div className="py-4 px-5">
+      <p className="font-weight-bold">1. {t('admin:slack_integration.accordion.generate_access_token')}</p>
+      <div className="form-group row">
+        <label className="text-left text-md-right col-md-3 col-form-label">Access Token for GROWI</label>
+        <div className="col-md-6">
+          <input
+            className="form-control"
+            type="text"
+          />
+        </div>
+      </div>
+      <div className="form-group row">
+        <label className="text-left text-md-right col-md-3 col-form-label">Access Token for Proxy</label>
+        <div className="col-md-6">
+          <input
+            className="form-control"
+            type="text"
+          />
+        </div>
+      </div>
+
+      <div className="row my-3">
+        <div className="mx-auto">
+          <button type="button" className="btn btn-outline-secondary mx-2">{ t('admin:slack_integration.access_token_settings.discard') }</button>
+          <button type="button" className="btn btn-primary mx-2">{ t('admin:slack_integration.access_token_settings.generate') }</button>
+        </div>
+      </div>
+      <p className="font-weight-bold">2. {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}</p>
+      <div className="d-flex flex-column align-items-center">
+        <ol className="p-0">
+          <li><p className="ml-2">{t('admin:slack_integration.accordion.enter_growi_register_on_slack')}</p></li>
+          <li>
+            <p
+              className="ml-2"
+                // TODO: Add dynamic link
+                // TODO: Copy to clipboard on click
+                // TODO: Add logo
+                // eslint-disable-next-line react/no-danger
+              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.paste_growi_url') }}
+            />
+            <div className="input-group align-items-center ml-2 mb-3">
+              <div className="input-group-prepend mx-1">
+                <input className="form-control" type="text" value={growiUrl} readOnly />
+                <CopyToClipboard text={growiUrl} onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
+                  <div className="btn input-group-text">
+                    <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
+                  </div>
+                </CopyToClipboard>
+              </div>
+            </div>
+
+          </li>
+          <li>
+            <p
+              className="ml-2"
+                // eslint-disable-next-line react/no-danger
+              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.accordion.enter_access_token_for_growi_and_proxy') }}
+            />
+          </li>
+        </ol>
+        {/* TODO: Insert photo */}
+        <div className="rounded border w-50 d-flex justify-content-center align-items-center" style={{ height: '15rem' }}>
+          <h1 className="text-muted">参考画像</h1>
+        </div>
+      </div>
+    </div>
+
+  );
+}, [AppContainer]);
+
+GenelatingTokensAndRegisteringProxyServiceProcess.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+};
+
+
+export const TestProcess = () => {
+  const { t } = useTranslation();
+  const [testChannel, setTestChannel] = useState('');
+  /* eslint-disable no-unused-vars */
+  // TODO: Add connection Logs
+  const [connectionErrorCode, setConnectionErrorCode] = useState(null);
+  const [connectionErrorMessage, setConnectionErrorMessage] = useState(null);
+  const [connectionSuccessMessage, setConnectionSuccessMessage] = useState(null);
+
+  // TODO: Show test logs
+  let value = '';
+  if (connectionErrorMessage != null) {
+    value = [connectionErrorCode, connectionErrorMessage];
+  }
+  if (connectionSuccessMessage != null) {
+    value = connectionSuccessMessage;
+  }
+
+
+  // TODO: Handle test button
+  const submitForm = (e) => {
+    e.preventDefault();
+    // eslint-disable-next-line no-console
+    console.log('Form Submitted');
+  };
+
+  const inputTestChannelHandler = (channel) => {
+    setTestChannel(channel);
+  };
+
+  return (
+    <>
+      <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
+      <div className="d-flex justify-content-center">
+        <form className="form-row justify-content-center" onSubmit={e => submitForm(e)}>
+          <div className="input-group col-8">
+            <div className="input-group-prepend">
+              <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+            </div>
+            <input
+              className="form-control"
+              type="text"
+              value={testChannel}
+              placeholder="Slack Channel"
+            // TODO: Handle test button
+              onChange={e => inputTestChannelHandler(e.target.value)}
+            />
+          </div>
+          <button
+            type="submit"
+            className="btn btn-info mx-3 font-weight-bold"
+            disabled={testChannel.trim() === ''}
+          >Test
+          </button>
+        </form>
+      </div>
+      {connectionErrorMessage != null
+      && <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>}
+      {connectionSuccessMessage != null
+      && <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>}
+      <form>
+        <div className="row my-3 justify-content-center">
+          <div className="form-group slack-connection-log col-md-4">
+            <label className="mb-1"><p className="border-info slack-connection-log-title pl-2 m-0">Logs</p></label>
+            <textarea
+              className="form-control card border-info slack-connection-log-body rounded-lg"
+              rows="5"
+            // TODO: Show test logs
+              value={value}
+              readOnly
+            />
+          </div>
+        </div>
+      </form>
+    </>
+  );
+};
+
+const CustomBotCooperationProcedure = {
+  '①': {
+    title: 'create_bot',
+    content: <BotCreateProcess />,
+  },
+  '②': {
+    title: 'install_bot_to_slack',
+    content: <BotInstallProcess />,
+  },
+  '③': {
+    title: 'register_for_growi_official_bot_proxy_service',
+    content: <GenelatingTokensAndRegisteringProxyServiceProcess />,
+  },
+  '④': {
+    title: 'set_proxy_url_on_growi',
+    content: <RegisteringProxyUrlProcess />,
+  },
+  '⑤': {
+    title: 'test_connection',
+    content: <TestProcess />,
+  },
+};
+
+const officialBotCooperationProcedure = {
+  '①': {
+    title: 'install_bot_to_slack',
+    content: <BotInstallProcess />,
+  },
+  '②': {
+    title: 'register_for_growi_official_bot_proxy_service',
+    content: <GenelatingTokensAndRegisteringProxyServiceProcess />,
+  },
+  '③': {
+    title: 'set_proxy_url_on_growi',
+    content: <RegisteringProxyUrlProcess />,
+  },
+  '④': {
+    title: 'test_connection',
+    content: <TestProcess />,
+  },
+};
+
+
+const WithProxyAccordions = (props) => {
+  const { t } = useTranslation();
+  const cooperationProcedureMapping = props.botType === 'officialBot' ? officialBotCooperationProcedure : CustomBotCooperationProcedure;
+
+
+  return (
+    <div className="card border-0 rounded-lg shadow overflow-hidden">
+      {Object.entries(cooperationProcedureMapping).map(([key, value]) => {
+        return (
+          <Accordion
+            title={<><span className="mr-2">{key}</span>{t(`admin:slack_integration.accordion.${value.title}`)}</>}
+            key={key}
+          >
+            {value.content}
+          </Accordion>
+        );
+      })}
+    </div>
+  );
+};
+
+
+/**
+ * Wrapper component for using unstated
+ */
+
+const OfficialBotSettingsAccordionsWrapper = withUnstatedContainers(WithProxyAccordions, [AppContainer]);
+WithProxyAccordions.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  botType: PropTypes.string.isRequired,
+};
+
+export default OfficialBotSettingsAccordionsWrapper;