NotificationSetting.jsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import React, {
  2. useCallback, useEffect, useMemo, useState,
  3. } from 'react';
  4. import { SlackbotType } from '@growi/slack';
  5. import { useTranslation } from 'next-i18next';
  6. import PropTypes from 'prop-types';
  7. import {
  8. TabContent, TabPane,
  9. } from 'reactstrap';
  10. import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
  11. import { toastError } from '~/client/util/toastr';
  12. import { toArrayIfNot } from '~/utils/array-utils';
  13. import loggerFactory from '~/utils/logger';
  14. import { CustomNavTab } from '../../CustomNavigation/CustomNav';
  15. import { withUnstatedContainers } from '../../UnstatedUtils';
  16. import GlobalNotification from './GlobalNotification';
  17. import UserTriggerNotification from './UserTriggerNotification';
  18. const logger = loggerFactory('growi:NotificationSetting');
  19. let retrieveErrors = null;
  20. // eslint-disable-next-line react/prop-types
  21. const Badge = ({ isEnabled }) => {
  22. const { t } = useTranslation('admin');
  23. return isEnabled
  24. ? <span className="badge bg-success">{t('external_notification.enabled')}</span>
  25. : <span className="badge bg-primary">{t('external_notification.disabled')}</span>;
  26. };
  27. const SkeletonListItem = () => (
  28. <li className="list-group-item">
  29. <h4 className="mb-2">
  30. <span className="badge bg-primary">――</span>
  31. <span className="ml-2">...</span>
  32. </h4>
  33. </li>
  34. );
  35. // eslint-disable-next-line react/prop-types
  36. const SlackIntegrationListItem = ({ isEnabled, currentBotType }) => {
  37. const { t } = useTranslation('admin');
  38. const isCautionVisible = currentBotType === SlackbotType.OFFICIAL || currentBotType === SlackbotType.CUSTOM_WITH_PROXY;
  39. return (
  40. <li data-testid="slack-integration-list-item" className="list-group-item">
  41. <h4>
  42. <Badge isEnabled={isEnabled} />
  43. <a href="/admin/slack-integration" className="ml-2">{t('slack_integration.slack_integration')}</a>
  44. </h4>
  45. { isCautionVisible && (
  46. <ul className="mt-2 pl-4">
  47. {/* eslint-disable-next-line react/no-danger */}
  48. <li dangerouslySetInnerHTML={{ __html: t('external_notification.caution_enabled') }} />
  49. </ul>
  50. ) }
  51. </li>
  52. );
  53. };
  54. // eslint-disable-next-line react/prop-types
  55. const LegacySlackIntegrationListItem = ({ isEnabled }) => {
  56. const { t } = useTranslation('admin');
  57. return (
  58. <li className="list-group-item">
  59. <h4>
  60. <Badge isEnabled={isEnabled} />
  61. <a href="/admin/slack-integration-legacy" className="ml-2">{t('slack_integration_legacy.slack_integration_legacy')}</a>
  62. </h4>
  63. { isEnabled && (
  64. <ul className="mt-2 pl-4">
  65. <li>
  66. {/* eslint-disable-next-line react/no-danger */}
  67. <span className="text-danger" dangerouslySetInnerHTML={{ __html: t('slack_integration_legacy.alert_deplicated') }}></span>
  68. </li>
  69. </ul>
  70. ) }
  71. </li>
  72. );
  73. };
  74. function NotificationSetting(props) {
  75. const { adminNotificationContainer } = props;
  76. const { t } = useTranslation('admin');
  77. const [isMounted, setMounted] = useState(false);
  78. const [activeTab, setActiveTab] = useState('user_trigger_notification');
  79. const [activeComponents, setActiveComponents] = useState(new Set(['user_trigger_notification']));
  80. const switchActiveTab = (selectedTab) => {
  81. setActiveTab(selectedTab);
  82. setActiveComponents(activeComponents.add(selectedTab));
  83. };
  84. const fetchData = useCallback(async() => {
  85. try {
  86. await adminNotificationContainer.retrieveNotificationData();
  87. }
  88. catch (err) {
  89. const errs = toArrayIfNot(err);
  90. toastError(errs);
  91. logger.error(errs);
  92. retrieveErrors = errs;
  93. }
  94. finally {
  95. setMounted(true);
  96. }
  97. }, [adminNotificationContainer]);
  98. useEffect(() => {
  99. fetchData();
  100. }, [fetchData]);
  101. const navTabMapping = useMemo(() => {
  102. return {
  103. user_trigger_notification: {
  104. Icon: () => <i className="icon-settings" />,
  105. i18n: 'User trigger notification',
  106. },
  107. global_notification: {
  108. Icon: () => <i className="icon-settings" />,
  109. i18n: 'Global notification',
  110. },
  111. };
  112. }, []);
  113. const { isSlackbotConfigured, isSlackLegacyConfigured, currentBotType } = adminNotificationContainer.state;
  114. const isSlackEnabled = isSlackbotConfigured;
  115. const isSlackLegacyEnabled = !isSlackbotConfigured && isSlackLegacyConfigured;
  116. return (
  117. <div data-testid="admin-notification">
  118. <h2 className="admin-setting-header">{t('external_notification.header_status')}</h2>
  119. <ul className="list-group">
  120. { !isMounted && <SkeletonListItem />}
  121. { isMounted && (
  122. <>
  123. <SlackIntegrationListItem isEnabled={isSlackEnabled} currentBotType={currentBotType} />
  124. {/* Legacy Slack Integration become visible only when new Slack Integration is disabled */}
  125. { !isSlackEnabled && <LegacySlackIntegrationListItem isEnabled={isSlackLegacyEnabled} /> }
  126. </>
  127. ) }
  128. </ul>
  129. <h2 className="admin-setting-header mt-5">{t('notification_settings.notification_settings')}</h2>
  130. <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
  131. <TabContent activeTab={activeTab} className="p-5">
  132. <TabPane tabId="user_trigger_notification">
  133. {activeComponents.has('user_trigger_notification') && <UserTriggerNotification />}
  134. </TabPane>
  135. <TabPane tabId="global_notification">
  136. {activeComponents.has('global_notification') && <GlobalNotification />}
  137. </TabPane>
  138. </TabContent>
  139. </div>
  140. );
  141. }
  142. const NotificationSettingWithUnstatedContainer = withUnstatedContainers(NotificationSetting, [AdminNotificationContainer]);
  143. NotificationSetting.propTypes = {
  144. adminNotificationContainer: PropTypes.instanceOf(AdminNotificationContainer).isRequired,
  145. };
  146. export default NotificationSettingWithUnstatedContainer;