SlackConfiguration.jsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import React, { useCallback, useEffect } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import PropTypes from 'prop-types';
  4. import { useForm } from 'react-hook-form';
  5. import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
  6. import { toastSuccess, toastError } from '~/client/util/toastr';
  7. import loggerFactory from '~/utils/logger';
  8. import { withUnstatedContainers } from '../../UnstatedUtils';
  9. import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
  10. const logger = loggerFactory('growi:slackAppConfiguration');
  11. const SlackConfiguration = (props) => {
  12. const { t, adminSlackIntegrationLegacyContainer } = props;
  13. const { webhookUrl, slackToken, retrieveError } = adminSlackIntegrationLegacyContainer.state;
  14. const { register, handleSubmit, reset } = useForm();
  15. // Sync form with container state
  16. useEffect(() => {
  17. reset({
  18. webhookUrl,
  19. slackToken,
  20. });
  21. }, [reset, webhookUrl, slackToken]);
  22. const onClickSubmit = useCallback(async(data) => {
  23. try {
  24. await adminSlackIntegrationLegacyContainer.changeWebhookUrl(data.webhookUrl ?? '');
  25. await adminSlackIntegrationLegacyContainer.changeSlackToken(data.slackToken ?? '');
  26. await adminSlackIntegrationLegacyContainer.updateSlackAppConfiguration();
  27. toastSuccess(t('notification_settings.updated_slackApp'));
  28. }
  29. catch (err) {
  30. toastError(err);
  31. logger.error(err);
  32. }
  33. }, [adminSlackIntegrationLegacyContainer, t]);
  34. return (
  35. <form onSubmit={handleSubmit(onClickSubmit)}>
  36. <React.Fragment>
  37. <div className="row my-3">
  38. <div className="col-6 text-start">
  39. <div className="dropdown">
  40. <button
  41. className="btn btn-secondary dropdown-toggle"
  42. type="button"
  43. id="dropdownMenuButton"
  44. data-bs-toggle="dropdown"
  45. aria-haspopup="true"
  46. aria-expanded="true"
  47. >
  48. {`Slack ${adminSlackIntegrationLegacyContainer.state.selectSlackOption}`}
  49. </button>
  50. <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
  51. <button className="dropdown-item" type="button" onClick={() => adminSlackIntegrationLegacyContainer.switchSlackOption('Incoming Webhooks')}>
  52. Slack Incoming Webhooks
  53. </button>
  54. <button className="dropdown-item" type="button" onClick={() => adminSlackIntegrationLegacyContainer.switchSlackOption('App')}>Slack App</button>
  55. </div>
  56. </div>
  57. </div>
  58. </div>
  59. {adminSlackIntegrationLegacyContainer.state.selectSlackOption === 'Incoming Webhooks' ? (
  60. <React.Fragment>
  61. <h2 className="border-bottom mb-5">{t('notification_settings.slack_incoming_configuration')}</h2>
  62. <div className="row mb-3">
  63. <label className="form-label col-md-3 text-start text-md-end">Webhook URL</label>
  64. <div className="col-md-6">
  65. <input
  66. className="form-control"
  67. type="text"
  68. {...register('webhookUrl')}
  69. />
  70. </div>
  71. </div>
  72. <div className="row mb-3">
  73. <div className="offset-md-3 col-md-6 text-start">
  74. <div className="form-check form-check-success">
  75. <input
  76. type="checkbox"
  77. className="form-check-input"
  78. id="cbPrioritizeIWH"
  79. checked={adminSlackIntegrationLegacyContainer.state.isIncomingWebhookPrioritized || false}
  80. onChange={() => { adminSlackIntegrationLegacyContainer.switchIsIncomingWebhookPrioritized() }}
  81. />
  82. <label className="form-label form-check-label" htmlFor="cbPrioritizeIWH">
  83. {t('notification_settings.prioritize_webhook')}
  84. </label>
  85. </div>
  86. <p className="form-text text-muted">
  87. {t('notification_settings.prioritize_webhook_desc')}
  88. </p>
  89. </div>
  90. </div>
  91. </React.Fragment>
  92. )
  93. : (
  94. <React.Fragment>
  95. <h2 className="border-bottom mb-3">{t('notification_settings.slack_app_configuration')}</h2>
  96. <div className="card custom-card bg-danger-subtle">
  97. <span className="text-danger"><span className="material-symbols-outlined">error</span>NOT RECOMMENDED</span>
  98. <br />
  99. {/* eslint-disable-next-line react/no-danger */}
  100. <span dangerouslySetInnerHTML={{ __html: t('notification_settings.slack_app_configuration_desc') }} />
  101. <br />
  102. <a
  103. href="#slack-incoming-webhooks"
  104. data-bs-toggle="tab"
  105. onClick={() => adminSlackIntegrationLegacyContainer.switchSlackOption('Incoming Webhooks')}
  106. >
  107. {t('notification_settings.use_instead')}
  108. </a>
  109. </div>
  110. <div className="row mb-5 mt-4">
  111. <label className="form-label col-md-3 text-start text-md-end">OAuth access token</label>
  112. <div className="col-md-6">
  113. <input
  114. className="form-control"
  115. type="text"
  116. {...register('slackToken')}
  117. />
  118. </div>
  119. </div>
  120. </React.Fragment>
  121. )
  122. }
  123. <AdminUpdateButtonRow
  124. disabled={retrieveError != null}
  125. onClick={handleSubmit(onClickSubmit)}
  126. />
  127. <hr />
  128. <h3>
  129. <span className="material-symbols-outlined" aria-hidden="true">help</span>{' '}
  130. <a href="#collapseHelpForIwh" data-bs-toggle="collapse">{t('notification_settings.how_to.header')}</a>
  131. </h3>
  132. <ol id="collapseHelpForIwh" className="collapse card custom-card bg-body-tertiary">
  133. <li className="ms-3">
  134. {t('notification_settings.how_to.workspace')}
  135. <ol>
  136. {/* eslint-disable-next-line react/no-danger */}
  137. <li dangerouslySetInnerHTML={{ __html: t('notification_settings.how_to.workspace_desc1') }} />
  138. <li>{t('notification_settings.how_to.workspace_desc2')}</li>
  139. <li>{t('notification_settings.how_to.workspace_desc3')}</li>
  140. </ol>
  141. </li>
  142. <li className="ms-3">
  143. {t('notification_settings.how_to.at_growi')}
  144. <ol>
  145. {/* eslint-disable-next-line react/no-danger */}
  146. <li dangerouslySetInnerHTML={{ __html: t('notification_settings.how_to.at_growi_desc') }} />
  147. </ol>
  148. </li>
  149. </ol>
  150. </React.Fragment>
  151. </form>
  152. );
  153. };
  154. SlackConfiguration.propTypes = {
  155. t: PropTypes.func.isRequired, // i18next
  156. adminSlackIntegrationLegacyContainer: PropTypes.instanceOf(AdminSlackIntegrationLegacyContainer).isRequired,
  157. };
  158. const SlackConfigurationWrapperFc = (props) => {
  159. const { t } = useTranslation('admin');
  160. return <SlackConfiguration t={t} {...props} />;
  161. };
  162. const SlackConfigurationWrapper = withUnstatedContainers(SlackConfigurationWrapperFc, [AdminSlackIntegrationLegacyContainer]);
  163. export default SlackConfigurationWrapper;