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

Merge pull request #6272 from weseek/imprv/10035-show-user-group-management

Imprv/10035 show user group management and slack integration
Yuki Takei 3 лет назад
Родитель
Сommit
3f926a5c91

+ 22 - 20
packages/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx

@@ -1,38 +1,40 @@
-import React, { useMemo, useState } from 'react';
-import PropTypes from 'prop-types';
+import React, { useEffect, useMemo, useState } from 'react';
+
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
+import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
 import loggerFactory from '~/utils/logger';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import { withLoadingSppiner } from '../../SuspenseUtils';
 
-import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
 
 import SlackConfiguration from './SlackConfiguration';
 
 const logger = loggerFactory('growi:NotificationSetting');
 
-let retrieveErrors = null;
+const retrieveErrors = null;
 function LegacySlackIntegration(props) {
   const { t } = useTranslation();
   const { adminSlackIntegrationLegacyContainer } = props;
 
   if (adminSlackIntegrationLegacyContainer.state.webhookUrl === adminSlackIntegrationLegacyContainer.dummyWebhookUrl) {
-    throw (async() => {
-      try {
-        await adminSlackIntegrationLegacyContainer.retrieveData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        retrieveErrors = errs;
-        adminSlackIntegrationLegacyContainer.setState({ webhookUrl: adminSlackIntegrationLegacyContainer.dummyWebhookUrlForError });
-      }
-    })();
+    // TODO: Omit AdminSlackIntegrationLegacyContainer by https://redmine.weseek.co.jp/issues/100947
+
+    // throw (async() => {
+    //   try {
+    //     await adminSlackIntegrationLegacyContainer.retrieveData();
+    //   }
+    //   catch (err) {
+    //     const errs = toArrayIfNot(err);
+    //     toastError(errs);
+    //     logger.error(errs);
+    //     retrieveErrors = errs;
+    //     adminSlackIntegrationLegacyContainer.setState({ webhookUrl: adminSlackIntegrationLegacyContainer.dummyWebhookUrlForError });
+    //   }
+    // })();
   }
 
   if (adminSlackIntegrationLegacyContainer.state.webhookUrl === adminSlackIntegrationLegacyContainer.dummyWebhookUrlForError) {
@@ -62,7 +64,7 @@ function LegacySlackIntegration(props) {
   );
 }
 
-const LegacySlackIntegrationWithUnstatedContainer = withUnstatedContainers(withLoadingSppiner(LegacySlackIntegration), [AdminSlackIntegrationLegacyContainer]);
+const LegacySlackIntegrationWithUnstatedContainer = withUnstatedContainers(LegacySlackIntegration, [AdminSlackIntegrationLegacyContainer]);
 
 LegacySlackIntegration.propTypes = {
   adminSlackIntegrationLegacyContainer: PropTypes.instanceOf(AdminSlackIntegrationLegacyContainer).isRequired,

+ 1 - 1
packages/app/src/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';

+ 7 - 11
packages/app/src/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -1,14 +1,13 @@
 import React, { useState, useEffect, useCallback } from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Delete, apiv3Put } from '~/client/util/apiv3-client';
+import { useAppTitle } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 
 import CustomBotWithProxyConnectionStatus from './CustomBotWithProxyConnectionStatus';
 import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
@@ -19,7 +18,7 @@ const logger = loggerFactory('growi:cli:SlackIntegration:CustomBotWithProxySetti
 
 const CustomBotWithProxySettings = (props) => {
   const {
-    appContainer, slackAppIntegrations, proxyServerUri,
+    slackAppIntegrations, proxyServerUri,
     onClickAddSlackWorkspaceBtn, onPrimaryUpdated,
     connectionStatuses, onUpdateTokens, onSubmitForm,
   } = props;
@@ -27,6 +26,7 @@ const CustomBotWithProxySettings = (props) => {
   const [integrationIdToDelete, setIntegrationIdToDelete] = useState(null);
   const [siteName, setSiteName] = useState('');
   const { t } = useTranslation();
+  const { data: appTitle } = useAppTitle();
 
   // componentDidUpdate
   useEffect(() => {
@@ -86,9 +86,8 @@ const CustomBotWithProxySettings = (props) => {
   };
 
   useEffect(() => {
-    const siteName = appContainer.config.crowi.title;
-    setSiteName(siteName);
-  }, [appContainer]);
+    setSiteName(appTitle);
+  }, [appTitle]);
 
   return (
     <>
@@ -183,14 +182,11 @@ const CustomBotWithProxySettings = (props) => {
   );
 };
 
-const CustomBotWithProxySettingsWrapper = withUnstatedContainers(CustomBotWithProxySettings, [AppContainer]);
-
 CustomBotWithProxySettings.defaultProps = {
   slackAppIntegrations: [],
 };
 
 CustomBotWithProxySettings.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   slackAppIntegrations: PropTypes.array,
   proxyServerUri: PropTypes.string,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
@@ -201,4 +197,4 @@ CustomBotWithProxySettings.propTypes = {
   onUpdateTokens: PropTypes.func,
 };
 
-export default CustomBotWithProxySettingsWrapper;
+export default CustomBotWithProxySettings;

+ 3 - 8
packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx

@@ -1,9 +1,8 @@
 import React, { useState, useEffect } from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Put } from '~/client/util/apiv3-client';
 
@@ -13,7 +12,7 @@ import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 const CustomBotWithoutProxySecretTokenSection = (props) => {
   const {
-    appContainer, slackSigningSecret, slackBotToken, slackSigningSecretEnv, slackBotTokenEnv, onUpdatedSecretToken,
+    slackSigningSecret, slackBotToken, slackSigningSecretEnv, slackBotTokenEnv, onUpdatedSecretToken,
   } = props;
   const { t } = useTranslation();
 
@@ -113,11 +112,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
   );
 };
 
-const CustomBotWithoutProxySecretTokenSectionWrapper = withUnstatedContainers(CustomBotWithoutProxySecretTokenSection, [AppContainer]);
-
 CustomBotWithoutProxySecretTokenSection.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
   onUpdatedSecretToken: PropTypes.func,
   slackSigningSecret: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
@@ -125,4 +120,4 @@ CustomBotWithoutProxySecretTokenSection.propTypes = {
   slackBotTokenEnv: PropTypes.string,
 };
 
-export default CustomBotWithoutProxySecretTokenSectionWrapper;
+export default CustomBotWithoutProxySecretTokenSection;

+ 11 - 9
packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -1,20 +1,24 @@
 import React, { useState, useEffect } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
-import AppContainer from '~/client/services/AppContainer';
+
+import { useAppTitle } from '~/stores/context';
+
 import { withUnstatedContainers } from '../../UnstatedUtils';
-import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
+
 import CustomBotWithoutProxyConnectionStatus from './CustomBotWithoutProxyConnectionStatus';
+import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 
 const CustomBotWithoutProxySettings = (props) => {
-  const { appContainer, connectionStatuses } = props;
+  const { connectionStatuses } = props;
   const { t } = useTranslation();
+  const { data: appTitle } = useAppTitle();
   const [siteName, setSiteName] = useState('');
 
   useEffect(() => {
-    const siteName = appContainer.config.crowi.title;
-    setSiteName(siteName);
-  }, [appContainer]);
+    setSiteName(appTitle);
+  }, [appTitle]);
 
   const workspaceName = connectionStatuses[props.slackBotToken]?.workspaceName;
 
@@ -58,10 +62,8 @@ const CustomBotWithoutProxySettings = (props) => {
   );
 };
 
-const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWithoutProxySettings, [AppContainer]);
 
 CustomBotWithoutProxySettings.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   slackSigningSecret: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
@@ -75,4 +77,4 @@ CustomBotWithoutProxySettings.propTypes = {
   eventActionsPermission: PropTypes.object,
 };
 
-export default CustomBotWithoutProxySettingsWrapper;
+export default CustomBotWithoutProxySettings;

+ 3 - 9
packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -1,12 +1,10 @@
 import React, { useState } from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
-import AppContainer from '~/client/services/AppContainer';
 import { apiv3Post } from '~/client/util/apiv3-client';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import Accordion from '../Common/Accordion';
 
 import CustomBotWithoutProxySecretTokenSection from './CustomBotWithoutProxySecretTokenSection';
@@ -25,7 +23,7 @@ export const botInstallationStep = {
 
 const CustomBotWithoutProxySettingsAccordion = (props) => {
   const {
-    appContainer, activeStep, onTestConnectionInvoked,
+    activeStep, onTestConnectionInvoked,
     slackSigningSecret, slackBotToken, slackSigningSecretEnv, slackBotTokenEnv, commandPermission, eventActionsPermission,
   } = props;
   const successMessage = 'Successfully sent to Slack workspace.';
@@ -190,12 +188,8 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
 };
 
 
-const CustomBotWithoutProxySettingsAccordionWrapper = withUnstatedContainers(CustomBotWithoutProxySettingsAccordion, [AppContainer]);
-
-
 CustomBotWithoutProxySettingsAccordion.propTypes = {
   activeStep: PropTypes.oneOf(Object.values(botInstallationStep)).isRequired,
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   onUpdatedSecretToken: PropTypes.func,
   onTestConnectionInvoked: PropTypes.func,
@@ -208,4 +202,4 @@ CustomBotWithoutProxySettingsAccordion.propTypes = {
   eventActionsPermission: PropTypes.object,
 };
 
-export default CustomBotWithoutProxySettingsAccordionWrapper;
+export default CustomBotWithoutProxySettingsAccordion;

+ 8 - 11
packages/app/src/components/Admin/SlackIntegration/OfficialBotSettings.jsx

@@ -1,17 +1,15 @@
 import React, { useState, useEffect, useCallback } from 'react';
 
 import { SlackbotType } from '@growi/slack';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Delete, apiv3Put } from '~/client/util/apiv3-client';
+import { useAppTitle } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
 
 import CustomBotWithProxyConnectionStatus from './CustomBotWithProxyConnectionStatus';
 import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
@@ -22,13 +20,14 @@ const logger = loggerFactory('growi:cli:SlackIntegration:OfficialBotSettings');
 
 const OfficialBotSettings = (props) => {
   const {
-    appContainer, slackAppIntegrations,
+    slackAppIntegrations,
     onClickAddSlackWorkspaceBtn, onPrimaryUpdated,
     connectionStatuses, onUpdateTokens, onSubmitForm,
   } = props;
   const [siteName, setSiteName] = useState('');
   const [integrationIdToDelete, setIntegrationIdToDelete] = useState(null);
   const { t } = useTranslation();
+  const { data: appTitle } = useAppTitle();
 
   const addSlackAppIntegrationHandler = async() => {
     if (onClickAddSlackWorkspaceBtn != null) {
@@ -69,10 +68,10 @@ const OfficialBotSettings = (props) => {
     }
   };
 
+
   useEffect(() => {
-    const siteName = appContainer.config.crowi.title;
-    setSiteName(siteName);
-  }, [appContainer]);
+    setSiteName(appTitle);
+  }, [appTitle]);
 
   return (
     <>
@@ -151,14 +150,12 @@ const OfficialBotSettings = (props) => {
   );
 };
 
-const OfficialBotSettingsWrapper = withUnstatedContainers(OfficialBotSettings, [AppContainer]);
 
 OfficialBotSettings.defaultProps = {
   slackAppIntegrations: [],
 };
 
 OfficialBotSettings.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   slackAppIntegrations: PropTypes.array,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
@@ -169,4 +166,4 @@ OfficialBotSettings.propTypes = {
   onSubmitForm: PropTypes.func,
 };
 
-export default OfficialBotSettingsWrapper;
+export default OfficialBotSettings;

+ 2 - 13
packages/app/src/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -1,18 +1,14 @@
 import React, { useState, useEffect, useCallback } from 'react';
 
 import { SlackbotType } from '@growi/slack';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
 
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import {
   apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
 } from '~/client/util/apiv3-client';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
 import BotTypeCard from './BotTypeCard';
 import ConfirmBotChangeModal from './ConfirmBotChangeModal';
 import CustomBotWithProxySettings from './CustomBotWithProxySettings';
@@ -23,9 +19,8 @@ import OfficialBotSettings from './OfficialBotSettings';
 
 const botTypes = Object.values(SlackbotType);
 
-const SlackIntegration = (props) => {
+const SlackIntegration = () => {
 
-  const { appContainer } = props;
   const { t } = useTranslation();
   const [currentBotType, setCurrentBotType] = useState(null);
   const [selectedBotType, setSelectedBotType] = useState(null);
@@ -256,10 +251,4 @@ const SlackIntegration = (props) => {
   );
 };
 
-const SlackIntegrationWrapper = withUnstatedContainers(SlackIntegration, [AppContainer]);
-
-SlackIntegration.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-};
-
-export default SlackIntegrationWrapper;
+export default SlackIntegration;

+ 8 - 9
packages/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -2,14 +2,14 @@
 import React, { useState, useCallback } from 'react';
 
 import { SlackbotType } from '@growi/slack';
+import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
-import { useTranslation } from 'next-i18next';
 import { Tooltip } from 'reactstrap';
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
+import { useSiteUrl } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
@@ -145,7 +145,7 @@ const CustomCopyToClipBoard = (props) => {
 
 const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers((props) => {
   const { t } = useTranslation();
-  const { appContainer, slackAppIntegrationId } = props;
+  const { slackAppIntegrationId } = props;
 
   const regenerateTokensHandler = async() => {
     try {
@@ -231,7 +231,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers
     </div>
 
   );
-}, [AppContainer]);
+}, []);
 
 const TestProcess = ({
   slackAppIntegrationId, onSubmitForm, onSubmitFormFailed, isLatestConnectionSuccess,
@@ -313,6 +313,7 @@ const TestProcess = ({
 
 const WithProxyAccordions = (props) => {
   const { t } = useTranslation();
+  const { data: siteUrl } = useSiteUrl();
   const [isLatestConnectionSuccess, setIsLatestConnectionSuccess] = useState(false);
 
   const submitForm = () => {
@@ -334,7 +335,7 @@ const WithProxyAccordions = (props) => {
     '②': {
       title: 'register_for_growi_official_bot_proxy_service',
       content: <GeneratingTokensAndRegisteringProxyServiceProcess
-        growiUrl={props.appContainer.config.crowi.url}
+        growiUrl={siteUrl}
         slackAppIntegrationId={props.slackAppIntegrationId}
         tokenPtoG={props.tokenPtoG}
         tokenGtoP={props.tokenGtoP}
@@ -373,7 +374,7 @@ const WithProxyAccordions = (props) => {
     '③': {
       title: 'register_for_growi_custom_bot_proxy',
       content: <GeneratingTokensAndRegisteringProxyServiceProcess
-        growiUrl={props.appContainer.config.crowi.url}
+        growiUrl={siteUrl}
         slackAppIntegrationId={props.slackAppIntegrationId}
         tokenPtoG={props.tokenPtoG}
         tokenGtoP={props.tokenGtoP}
@@ -434,9 +435,7 @@ const WithProxyAccordions = (props) => {
 /**
  * Wrapper component for using unstated
  */
-const WithProxyAccordionsWrapper = withUnstatedContainers(WithProxyAccordions, [AppContainer]);
 WithProxyAccordions.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   botType: PropTypes.oneOf(Object.values(SlackbotType)).isRequired,
   slackAppIntegrationId: PropTypes.string.isRequired,
   tokenPtoG: PropTypes.string,
@@ -446,4 +445,4 @@ WithProxyAccordions.propTypes = {
   permissionsForSlackEventActions: PropTypes.object.isRequired,
 };
 
-export default WithProxyAccordionsWrapper;
+export default WithProxyAccordions;

+ 2 - 2
packages/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx

@@ -135,7 +135,7 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         onChange={handleActionChange}
       >
         <option value="" disabled>{t('admin:user_group_management.delete_modal.dropdown_desc')}</option>
-        {...options}
+        {options}
       </select>
     );
   }, [availableOptions, actionName, handleActionChange, t]);
@@ -164,7 +164,7 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         onChange={handleGroupChange}
       >
         <option value="" disabled>{defaultOptionText}</option>
-        {...options}
+        {options}
       </select>
     );
   }, [deleteUserGroup, userGroups, t, actionName, transferToUserGroupId, handleGroupChange]);

+ 13 - 15
packages/app/src/components/SuspenseUtils.jsx

@@ -5,19 +5,17 @@ import React, { Suspense } from 'react';
  * If you throw a Promise in the component, it will display a sppiner
  * @param {object} Component A React.Component or functional component
  */
-export function withLoadingSppiner(Component) {
-  return (props => function getWithLoadingSpinner() {
-    return (
+export const withLoadingSppiner = Component => function getWithLoadingSpinner(props) {
+  return (
     // wrap with <Suspense></Suspense>
-      <Suspense
-        fallback={(
-          <div className="my-5 text-center">
-            <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
-          </div>
-        )}
-      >
-        <Component {...props} />
-      </Suspense>
-    );
-  });
-}
+    <Suspense
+      fallback={(
+        <div className="my-5 text-center">
+          <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
+        </div>
+      )}
+    >
+      <Component {...props} />
+    </Suspense>
+  );
+};

+ 11 - 1
packages/app/src/pages/admin/[[...path]].page.tsx

@@ -11,9 +11,11 @@ import AdminHome from '~/components/Admin/AdminHome/AdminHome';
 import AppSettingsPageContents from '~/components/Admin/App/AppSettingsPageContents';
 import ExportArchiveDataPage from '~/components/Admin/ExportArchiveDataPage';
 import DataImportPageContents from '~/components/Admin/ImportData/ImportDataPageContents';
+import LegacySlackIntegration from '~/components/Admin/LegacySlackIntegration/LegacySlackIntegration';
 import MarkDownSettingContents from '~/components/Admin/MarkdownSetting/MarkDownSettingContents';
 import NotificationSetting from '~/components/Admin/Notification/NotificationSetting';
 import SecurityManagementContents from '~/components/Admin/Security/SecurityManagementContents';
+import SlackIntegration from '~/components/Admin/SlackIntegration/SlackIntegration';
 import UserGroupPage from '~/components/Admin/UserGroup/UserGroupPage';
 import UserManagement from '~/components/Admin/UserManagement';
 import AdminLayout from '~/components/Layout/AdminLayout';
@@ -99,13 +101,21 @@ const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
       title: '',
       component: <>global-notification</>,
     },
+    'slack-integration': {
+      title: useCustomTitle(props, t('slack_integration')),
+      component: <SlackIntegration />,
+    },
+    'slack-integration-legacy': {
+      title: useCustomTitle(props, t('Legacy_Slack_Integration')),
+      component: <LegacySlackIntegration />,
+    },
     users: {
       title: useCustomTitle(props, t('User_Management')),
       component: <UserManagement />,
     },
     'user-groups': {
       title: useCustomTitle(props, t('UserGroup Management')),
-      component: <>user-groups</>,
+      component: <UserGroupPage />,
     },
     search: {
       title: useCustomTitle(props, t('Full Text Search Management')),