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

Merge remote-tracking branch 'origin/support/apply-nextjs-2' into support/ci-for-prod

Yuki Takei 3 лет назад
Родитель
Сommit
9ee80ebea4
33 измененных файлов с 260 добавлено и 565 удалено
  1. 6 6
      packages/app/src-obsolete/client/admin.jsx
  2. 1 1
      packages/app/src-obsolete/client/app.jsx
  3. 1 5
      packages/app/src/client/services/AdminAppContainer.js
  4. 1 4
      packages/app/src/client/services/AdminImportContainer.js
  5. 1 4
      packages/app/src/client/services/AdminMarkDownContainer.js
  6. 1 3
      packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  7. 0 51
      packages/app/src/components/Admin/App/AppSettingsPage.jsx
  8. 22 1
      packages/app/src/components/Admin/App/AppSettingsPageContents.tsx
  9. 0 56
      packages/app/src/components/Admin/CustomCssEditor.jsx
  10. 0 57
      packages/app/src/components/Admin/CustomHeaderEditor.jsx
  11. 0 56
      packages/app/src/components/Admin/CustomScriptEditor.jsx
  12. 0 2
      packages/app/src/components/Admin/Customize/Customize.jsx
  13. 4 3
      packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  14. 4 3
      packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx
  15. 4 3
      packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  16. 23 1
      packages/app/src/components/Admin/ImportData/ImportDataPageContents.jsx
  17. 0 52
      packages/app/src/components/Admin/ImportDataPage.jsx
  18. 18 23
      packages/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  19. 0 49
      packages/app/src/components/Admin/MarkdownSetting/MarkDownSetting.jsx
  20. 36 3
      packages/app/src/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx
  21. 3 1
      packages/app/src/components/CustomNavigation/CustomNav.jsx
  22. 8 6
      packages/app/src/components/CustomNavigation/CustomNav.module.scss
  23. 12 18
      packages/app/src/components/CustomNavigation/CustomNavAndContents.tsx
  24. 1 1
      packages/app/src/components/DescendantsPageListModal.tsx
  25. 9 3
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  26. 3 5
      packages/app/src/components/NotFoundPage.tsx
  27. 1 1
      packages/app/src/components/Page/DisplaySwitcher.tsx
  28. 0 136
      packages/app/src/components/PageTimeline.jsx
  29. 78 0
      packages/app/src/components/PageTimeline.tsx
  30. 11 10
      packages/app/src/components/Theme/ThemeFireRed.module.scss
  31. 8 0
      packages/app/src/components/Theme/ThemeFireRed.tsx
  32. 3 0
      packages/app/src/components/Theme/utils/ThemeProvider.tsx
  33. 1 1
      packages/app/src/pages/admin/[[...path]].page.tsx

+ 6 - 6
packages/app/src-obsolete/client/admin.jsx

@@ -30,16 +30,16 @@ import loggerFactory from '~/utils/logger';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 
 import AdminHome from '../components/Admin/AdminHome/AdminHome';
-import AppSettingsPage from '../components/Admin/App/AppSettingsPage';
+// import AppSettingsPage from '../components/Admin/App/AppSettingsPage';
 import { AuditLogManagement } from '../components/Admin/AuditLogManagement';
 import AdminNavigation from '../components/Admin/Common/AdminNavigation';
 import Customize from '../components/Admin/Customize/Customize';
 import ExportArchiveDataPage from '../components/Admin/ExportArchiveDataPage';
 import FullTextSearchManagement from '../components/Admin/FullTextSearchManagement';
-import ImportDataPage from '../components/Admin/ImportDataPage';
+// import ImportDataPage from '../components/Admin/ImportDataPage';
 import LegacySlackIntegration from '../components/Admin/LegacySlackIntegration/LegacySlackIntegration';
 import ManageExternalAccount from '../components/Admin/ManageExternalAccount';
-import MarkdownSetting from '../components/Admin/MarkdownSetting/MarkDownSetting';
+// import MarkdownSetting from '../components/Admin/MarkdownSetting/MarkDownSetting';
 import ManageGlobalNotification from '../components/Admin/Notification/ManageGlobalNotification';
 import NotificationSetting from '../components/Admin/Notification/NotificationSetting';
 import SecurityManagement from '../components/Admin/Security/SecurityManagement';
@@ -94,10 +94,10 @@ logger.info('unstated containers have been initialized');
  */
 Object.assign(componentMappings, {
   'admin-home': <AdminHome />,
-  'admin-app': <AppSettingsPage />,
-  'admin-markdown-setting': <MarkdownSetting />,
+  // 'admin-app': <AppSettingsPage />,
+  // 'admin-markdown-setting': <MarkdownSetting />,
   'admin-customize': <Customize />,
-  'admin-importer': <ImportDataPage />,
+  // 'admin-importer': <ImportDataPage />,
   'admin-export-page': <ExportArchiveDataPage />,
   'admin-notification-setting': <NotificationSetting />,
   'admin-slack-integration': <SlackIntegration />,

+ 1 - 1
packages/app/src-obsolete/client/app.jsx

@@ -35,7 +35,7 @@ import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLa
 import PageContentFooter from '../components/PageContentFooter';
 import BookmarkList from '../components/PageList/BookmarkList';
 import PageStatusAlert from '../components/PageStatusAlert';
-import PageTimeline from '../components/PageTimeline';
+import { PageTimeline } from '../components/PageTimeline';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';

+ 1 - 5
packages/app/src/client/services/AdminAppContainer.js

@@ -11,13 +11,9 @@ export default class AdminAppContainer extends Container {
   constructor() {
     super();
 
-    this.dummyTitle = 0;
-    this.dummyTitleForError = 1;
-
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      title: this.dummyTitle,
+      title: '',
       confidential: '',
       globalLang: '',
       isEmailPublishedForNewUser: true,

+ 1 - 4
packages/app/src/client/services/AdminImportContainer.js

@@ -18,13 +18,10 @@ export default class AdminImportContainer extends Container {
     super();
 
     this.appContainer = appContainer;
-    this.dummyEsaTeamName = 0;
-    this.dummyEsaTeamNameForError = 1;
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      esaTeamName: this.dummyEsaTeamName,
+      esaTeamName: '',
       esaAccessToken: '',
       qiitaTeamName: '',
       qiitaAccessToken: '',

+ 1 - 4
packages/app/src/client/services/AdminMarkDownContainer.js

@@ -12,13 +12,10 @@ export default class AdminMarkDownContainer extends Container {
     super();
 
     this.appContainer = appContainer;
-    this.dummyIsEnabledLinebreaks = 0;
-    this.dummyIsEnabledLinebreaksForError = 1;
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      isEnabledLinebreaks: this.dummyIsEnabledLinebreaks,
+      isEnabledLinebreaks: false,
       isEnabledLinebreaksInComments: false,
       adminPreferredIndentSize: 4,
       isIndentSizeForced: false,

+ 1 - 3
packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js

@@ -12,14 +12,12 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
     super();
 
     this.appContainer = appContainer;
-    this.dummyWebhookUrl = 0;
-    this.dummyWebhookUrlForError = 1;
 
     this.state = {
       isSlackbotConfigured: false,
       retrieveError: null,
       selectSlackOption: 'Incoming Webhooks',
-      webhookUrl: this.dummyWebhookUrl,
+      webhookUrl: '',
       isIncomingWebhookPrioritized: false,
       slackToken: '',
     };

+ 0 - 51
packages/app/src/components/Admin/App/AppSettingsPage.jsx

@@ -1,51 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import AdminAppContainer from '~/client/services/AdminAppContainer';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import loggerFactory from '~/utils/logger';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-import AppSettingsPageContents from './AppSettingsPageContents';
-
-const logger = loggerFactory('growi:appSettings');
-
-let retrieveErrors = null;
-function AppSettingsPage(props) {
-  if (props.adminAppContainer.state.title === props.adminAppContainer.dummyTitle) {
-    throw (async() => {
-      try {
-        await props.adminAppContainer.retrieveAppSettingsData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        props.adminAppContainer.setState({
-          title: props.adminAppContainer.dummyTitleForError,
-        });
-        retrieveErrors = errs;
-      }
-    })();
-  }
-
-  if (props.adminAppContainer.state.title === props.adminAppContainer.dummyTitleForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
-
-  return <AppSettingsPageContents />;
-}
-
-AppSettingsPage.propTypes = {
-  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
-};
-
-/**
- * Wrapper component for using unstated
- */
-const AppSettingsPageWithUnstatedContainer = withUnstatedContainers(AppSettingsPage, [AdminAppContainer]);
-
-export default AppSettingsPageWithUnstatedContainer;

+ 22 - 1
packages/app/src/components/Admin/App/AppSettingsPageContents.tsx

@@ -1,8 +1,12 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
+import loggerFactory from '~/utils/logger';
+
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -14,6 +18,8 @@ import PluginSetting from './PluginSetting';
 import SiteUrlSetting from './SiteUrlSetting';
 import V5PageMigration from './V5PageMigration';
 
+const logger = loggerFactory('growi:appSettings');
+
 type Props = {
   adminAppContainer: AdminAppContainer,
 }
@@ -23,6 +29,21 @@ const AppSettingsPageContents = (props: Props) => {
   const { adminAppContainer } = props;
   const { isV5Compatible } = adminAppContainer.state;
 
+  useEffect(() => {
+    const fetchAppSettingsData = async() => {
+      await adminAppContainer.retrieveAppSettingsData();
+    };
+
+    try {
+      fetchAppSettingsData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminAppContainer]);
+
   return (
     <div data-testid="admin-app-settings">
       {

+ 0 - 56
packages/app/src/components/Admin/CustomCssEditor.jsx

@@ -1,56 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-
-require('codemirror/addon/lint/css-lint');
-require('codemirror/addon/hint/css-hint');
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/closebrackets');
-require('codemirror/mode/css/css');
-require('~/client/util/codemirror/autorefresh.ext');
-
-require('jquery-ui/ui/widgets/resizable');
-
-export default class CustomCssEditor extends React.Component {
-
-  render() {
-
-    return (
-      <CodeMirror
-        value={this.props.value}
-        autoFocus
-        detach
-        options={{
-          mode: 'css',
-          lineNumbers: true,
-          tabSize: 2,
-          indentUnit: 2,
-          theme: 'eclipse',
-          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-          matchBrackets: true,
-          autoCloseBrackets: true,
-          extraKeys: { 'Ctrl-Space': 'autocomplete' },
-        }}
-        editorDidMount={(editor, next) => {
-          // resizable with jquery.ui
-          $(editor.getWrapperElement()).resizable({
-            resize() {
-              editor.setSize($(this).width(), $(this).height());
-            },
-          });
-        }}
-        onChange={(editor, data, value) => {
-          this.props.onChange(value);
-        }}
-      />
-    );
-  }
-
-}
-
-CustomCssEditor.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};

+ 0 - 57
packages/app/src/components/Admin/CustomHeaderEditor.jsx

@@ -1,57 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/closebrackets');
-require('codemirror/mode/htmlmixed/htmlmixed');
-require('codemirror/addon/hint/html-hint');
-require('codemirror/addon/edit/closetag');
-require('~/client/util/codemirror/autorefresh.ext');
-
-require('jquery-ui/ui/widgets/resizable');
-
-export default class CustomHeaderEditor extends React.Component {
-
-  render() {
-
-    return (
-      <CodeMirror
-        value={this.props.value}
-        autoFocus
-        detach
-        options={{
-          mode: 'htmlmixed',
-          autoCloseTags: true,
-          lineNumbers: true,
-          tabSize: 2,
-          indentUnit: 2,
-          theme: 'eclipse',
-          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-          matchBrackets: true,
-          autoCloseBrackets: true,
-          extraKeys: { 'Ctrl-Space': 'autocomplete' },
-        }}
-        editorDidMount={(editor, next) => {
-          // resizable with jquery.ui
-          $(editor.getWrapperElement()).resizable({
-            resize() {
-              editor.setSize($(this).width(), $(this).height());
-            },
-          });
-        }}
-        onChange={(editor, data, value) => {
-          this.props.onChange(value);
-        }}
-      />
-    );
-  }
-
-}
-
-CustomHeaderEditor.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};

+ 0 - 56
packages/app/src/components/Admin/CustomScriptEditor.jsx

@@ -1,56 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-
-require('codemirror/addon/lint/javascript-lint');
-require('codemirror/addon/hint/javascript-hint');
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/closebrackets');
-require('codemirror/mode/javascript/javascript');
-require('~/client/util/codemirror/autorefresh.ext');
-
-require('jquery-ui/ui/widgets/resizable');
-
-export default class CustomScriptEditor extends React.Component {
-
-  render() {
-
-    return (
-      <CodeMirror
-        value={this.props.value}
-        autoFocus
-        detach
-        options={{
-          mode: 'javascript',
-          lineNumbers: true,
-          tabSize: 2,
-          indentUnit: 2,
-          theme: 'eclipse',
-          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-          matchBrackets: true,
-          autoCloseBrackets: true,
-          extraKeys: { 'Ctrl-Space': 'autocomplete' },
-        }}
-        editorDidMount={(editor, next) => {
-          // resizable with jquery.ui
-          $(editor.getWrapperElement()).resizable({
-            resize() {
-              editor.setSize($(this).width(), $(this).height());
-            },
-          });
-        }}
-        onChange={(editor, data, value) => {
-          this.props.onChange(value);
-        }}
-      />
-    );
-  }
-
-}
-
-CustomScriptEditor.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};

+ 0 - 2
packages/app/src/components/Admin/Customize/Customize.jsx

@@ -64,7 +64,6 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeTitle />
       </div>
-      {/* TODO: show CustomizeHeaderSetting, CustomizeCssSetting and CustomizeScriptSetting by https://redmine.weseek.co.jp/issues/100534
       <div className="mb-5">
         <CustomizeHeaderSetting />
       </div>
@@ -74,7 +73,6 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeScriptSetting />
       </div>
-    */}
       <div className="mb-5">
         <CustomizeLogoSetting />
       </div>

+ 4 - 3
packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx

@@ -8,7 +8,6 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import CustomCssEditor from '../CustomCssEditor';
 
 type Props = {
   adminCustomizeContainer: AdminCustomizeContainer
@@ -43,9 +42,11 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
           </Card>
 
           <div className="form-group">
-            <CustomCssEditor
+            <textarea
+              className="form-control"
+              name="customizeCss"
               value={adminCustomizeContainer.state.currentCustomizeCss || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeCss(e.target.value) }}
             />
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />

+ 4 - 3
packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx

@@ -8,7 +8,6 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import CustomHeaderEditor from '../CustomHeaderEditor';
 
 type Props = {
   adminCustomizeContainer: AdminCustomizeContainer
@@ -54,9 +53,11 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
           </div>
 
           <div className="form-group">
-            <CustomHeaderEditor
+            <textarea
+              className="form-control"
+              name="customizeHeader"
               value={adminCustomizeContainer.state.currentCustomizeHeader || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeHeader(e.target.value) }}
             />
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>

+ 4 - 3
packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -8,7 +8,6 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import CustomScriptEditor from '../CustomScriptEditor';
 
 type Props = {
   adminCustomizeContainer: AdminCustomizeContainer
@@ -84,9 +83,11 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
           </div>
 
           <div className="form-group">
-            <CustomScriptEditor
+            <textarea
+              className="form-control"
+              name="customizeScript"
               value={adminCustomizeContainer.state.currentCustomizeScript || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeScript(inputValue) }}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeScript(e.target.value) }}
             />
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />

+ 23 - 1
packages/app/src/components/Admin/ImportData/ImportDataPageContents.jsx

@@ -1,14 +1,19 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 
 import AdminImportContainer from '~/client/services/AdminImportContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
+import loggerFactory from '~/utils/logger';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 import GrowiArchiveSection from './GrowiArchiveSection';
 
+const logger = loggerFactory('growi:importer');
+
 class ImportDataPageContents extends React.Component {
 
   render() {
@@ -242,6 +247,23 @@ ImportDataPageContents.propTypes = {
 const ImportDataPageContentsWrapperFc = (props) => {
   const { t } = useTranslation();
 
+  const { adminImportContainer } = props;
+
+  useEffect(() => {
+    const fetchImportSettingsData = async() => {
+      await adminImportContainer.retrieveImportSettingsData();
+    };
+
+    try {
+      fetchImportSettingsData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminImportContainer]);
+
   return <ImportDataPageContents t={t} {...props} />;
 };
 

+ 0 - 52
packages/app/src/components/Admin/ImportDataPage.jsx

@@ -1,52 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import AdminImportContainer from '~/client/services/AdminImportContainer';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import loggerFactory from '~/utils/logger';
-
-import { withUnstatedContainers } from '../UnstatedUtils';
-
-import ImportDataPageContents from './ImportData/ImportDataPageContents';
-
-const logger = loggerFactory('growi:importer');
-
-let retrieveErrors = null;
-function ImportDataPage(props) {
-  const { adminImportContainer } = props;
-
-  if (adminImportContainer.state.esaTeamName === adminImportContainer.dummyEsaTeamName) {
-    throw (async() => {
-      try {
-        await adminImportContainer.retrieveImportSettingsData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        retrieveErrors = errs;
-        adminImportContainer.setState({ esaTeamName: adminImportContainer.dummyEsaTeamNameForError });
-      }
-    })();
-  }
-
-  if (adminImportContainer.state.esaTeamName === adminImportContainer.dummyEsaTeamNameForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
-
-  return <ImportDataPageContents />;
-}
-
-ImportDataPage.propTypes = {
-  adminImportContainer: PropTypes.instanceOf(AdminImportContainer).isRequired,
-};
-
-
-/**
- * Wrapper component for using unstated
- */
-const ImportDataPageWithUnstatedContainer = withUnstatedContainers(ImportDataPage, [AdminImportContainer]);
-
-export default ImportDataPageWithUnstatedContainer;

+ 18 - 23
packages/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx

@@ -15,31 +15,26 @@ import SlackConfiguration from './SlackConfiguration';
 
 const logger = loggerFactory('growi:NotificationSetting');
 
-const retrieveErrors = null;
-function LegacySlackIntegration(props) {
+const LegacySlackIntegration = (props) => {
   const { t } = useTranslation();
   const { adminSlackIntegrationLegacyContainer } = props;
 
-  if (adminSlackIntegrationLegacyContainer.state.webhookUrl === adminSlackIntegrationLegacyContainer.dummyWebhookUrl) {
-    // 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) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
+
+  useEffect(() => {
+    const fetchLegacySlackIntegrationData = async() => {
+      await adminSlackIntegrationLegacyContainer.retrieveData();
+    };
+
+    try {
+      fetchLegacySlackIntegrationData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminSlackIntegrationLegacyContainer]);
+
 
   const isDisabled = adminSlackIntegrationLegacyContainer.state.isSlackbotConfigured;
 
@@ -62,7 +57,7 @@ function LegacySlackIntegration(props) {
       <SlackConfiguration />
     </div>
   );
-}
+};
 
 const LegacySlackIntegrationWithUnstatedContainer = withUnstatedContainers(LegacySlackIntegration, [AdminSlackIntegrationLegacyContainer]);
 

+ 0 - 49
packages/app/src/components/Admin/MarkdownSetting/MarkDownSetting.jsx

@@ -1,49 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import loggerFactory from '~/utils/logger';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-import MarkDownSettingContents from './MarkDownSettingContents';
-
-
-const logger = loggerFactory('growi:MarkDown');
-
-let retrieveErrors = null;
-function MarkdownSetting(props) {
-  const { adminMarkDownContainer } = props;
-
-  if (adminMarkDownContainer.state.isEnabledLinebreaks === adminMarkDownContainer.dummyIsEnabledLinebreaks) {
-    throw (async() => {
-      try {
-        await adminMarkDownContainer.retrieveMarkdownData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        retrieveErrors = errs;
-        adminMarkDownContainer.setState({ isEnabledLinebreaks: adminMarkDownContainer.dummyIsEnabledLinebreaksForError });
-      }
-    })();
-  }
-
-  if (adminMarkDownContainer.state.isEnabledLinebreaks === adminMarkDownContainer.dummyIsEnabledLinebreaksForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
-
-  return <MarkDownSettingContents />;
-}
-
-const MarkdownSettingWithUnstatedContainer = withUnstatedContainers(MarkdownSetting, [AdminMarkDownContainer]);
-
-MarkdownSetting.propTypes = {
-  adminMarkDownContainer: PropTypes.instanceOf(AdminMarkDownContainer).isRequired,
-};
-
-export default MarkdownSettingWithUnstatedContainer;

+ 36 - 3
packages/app/src/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx

@@ -1,15 +1,44 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 
+import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
+import loggerFactory from '~/utils/logger';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
 import IndentForm from './IndentForm';
 import LineBreakForm from './LineBreakForm';
 import PresentationForm from './PresentationForm';
 import XssForm from './XssForm';
 
-const MarkDownSettingContents = React.memo((): JSX.Element => {
+const logger = loggerFactory('growi:MarkDown');
+
+type Props ={
+  adminMarkDownContainer: AdminMarkDownContainer
+}
+
+const MarkDownSettingContents = React.memo((props: Props): JSX.Element => {
   const { t } = useTranslation();
+  const { adminMarkDownContainer } = props;
+
+  useEffect(() => {
+    const fetchMarkdownData = async() => {
+      await adminMarkDownContainer.retrieveMarkdownData();
+    };
+
+    try {
+      fetchMarkdownData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminMarkDownContainer]);
 
   return (
     <div data-testid="admin-markdown">
@@ -45,4 +74,8 @@ const MarkDownSettingContents = React.memo((): JSX.Element => {
 });
 MarkDownSettingContents.displayName = 'MarkDownSettingContents';
 
-export default MarkDownSettingContents;
+
+const MarkdownSettingWithUnstatedContainer = withUnstatedContainers(MarkDownSettingContents, [AdminMarkDownContainer]);
+
+
+export default MarkdownSettingWithUnstatedContainer;

+ 3 - 1
packages/app/src/components/CustomNavigation/CustomNav.jsx

@@ -7,6 +7,8 @@ import {
   Nav, NavItem, NavLink,
 } from 'reactstrap';
 
+import styles from './CustomNav.module.scss';
+
 
 function getBreakpointOneLevelLarger(breakpoint) {
   switch (breakpoint) {
@@ -149,7 +151,7 @@ export const CustomNavTab = (props) => {
   }
 
   return (
-    <div className="grw-custom-nav-tab">
+    <div className={`grw-custom-nav-tab ${styles['grw-custom-nav-tab']}`}>
       <div ref={navContainer} className="d-flex justify-content-between">
         <Nav className="nav-title">
           {Object.entries(navTabMapping).map(([key, value]) => {

+ 8 - 6
packages/app/src/styles/_navbar.scss → packages/app/src/components/CustomNavigation/CustomNav.module.scss

@@ -1,14 +1,16 @@
 .grw-custom-nav-tab,
 .grw-custom-nav-dropdown {
-  svg {
-    width: 17px;
-    height: 17px;
-    margin-right: 5px;
-    vertical-align: text-bottom;
+  :global {
+    svg {
+      width: 17px;
+      height: 17px;
+      margin-right: 5px;
+      vertical-align: text-bottom;
+    }
   }
 }
 
-.grw-custom-nav-tab {
+.grw-custom-nav-tab :global {
   .nav-title {
     flex-wrap: nowrap;
   }

+ 12 - 18
packages/app/src/components/CustomNavigation/CustomNavAndContents.jsx → packages/app/src/components/CustomNavigation/CustomNavAndContents.tsx

@@ -1,14 +1,21 @@
-import React, { useState } from 'react';
-
-import PropTypes from 'prop-types';
+import React, { ReactNode, useState } from 'react';
 
 import CustomNav, { CustomNavTab, CustomNavDropdown } from './CustomNav';
 import CustomTabContent from './CustomTabContent';
 
+type CustomNavAndContentsProps = {
+  navTabMapping: any,
+  defaultTabIndex?: number,
+  navigationMode?: 'both' | 'tab' | 'dropdown',
+  tabContentClasses?: string[],
+  breakpointToHideInactiveTabsDown?: 'xs' | 'sm' | 'md' | 'lg' | 'xl',
+  navRightElement?: ReactNode
+}
+
 
-const CustomNavAndContents = (props) => {
+const CustomNavAndContents = (props: CustomNavAndContentsProps): JSX.Element => {
   const {
-    navTabMapping, defaultTabIndex, navigationMode, tabContentClasses, breakpointToHideInactiveTabsDown, navRightElement,
+    navTabMapping, defaultTabIndex, navigationMode = 'tab', tabContentClasses = ['p-4'], breakpointToHideInactiveTabsDown, navRightElement,
   } = props;
   const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
 
@@ -39,17 +46,4 @@ const CustomNavAndContents = (props) => {
   );
 };
 
-CustomNavAndContents.propTypes = {
-  navTabMapping: PropTypes.object.isRequired,
-  defaultTabIndex: PropTypes.number,
-  navigationMode: PropTypes.oneOf(['both', 'tab', 'dropdown']),
-  tabContentClasses: PropTypes.arrayOf(PropTypes.string),
-  breakpointToHideInactiveTabsDown: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
-  navRightElement: PropTypes.node,
-};
-CustomNavAndContents.defaultProps = {
-  navigationMode: 'tab',
-  tabContentClasses: ['p-4'],
-};
-
 export default CustomNavAndContents;

+ 1 - 1
packages/app/src/components/DescendantsPageListModal.tsx

@@ -15,7 +15,7 @@ import { DescendantsPageList } from './DescendantsPageList';
 import ExpandOrContractButton from './ExpandOrContractButton';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-import PageTimeline from './PageTimeline';
+import { PageTimeline } from './PageTimeline';
 
 
 export const DescendantsPageListModal = (): JSX.Element => {

+ 9 - 3
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -10,11 +10,12 @@ import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
 import {
-  IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity,
+  IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity, IPageHasId,
 } from '~/interfaces/page';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
   useCurrentPageId,
+  useCurrentPathname,
   useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData,
 } from '~/stores/context';
 import { usePageTagsForEditors } from '~/stores/editor';
@@ -162,6 +163,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: isDrawerMode } = useDrawerMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: pageId } = useCurrentPageId();
+  const { data: currentPathname } = useCurrentPathname();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
@@ -344,13 +346,17 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     templateMenuItemClickHandler, isPageTemplateModalShown,
   ]);
 
-  if (currentPage == null) {
+  if (currentPathname == null) {
     return <></>;
   }
 
+  const notFoundPage: Partial<IPageHasId> = {
+    path: currentPathname,
+  };
+
   return (
     <GrowiSubNavigation
-      page={currentPage}
+      page={currentPage ?? notFoundPage}
       showDrawerToggler={isDrawerMode}
       showTagLabel={isAbleToShowTagLabel}
       showPageAuthors={isAbleToShowPageAuthors}

+ 3 - 5
packages/app/src/components/NotFoundPage.tsx

@@ -6,13 +6,12 @@ import dynamic from 'next/dynamic';
 import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-// import PageTimeline from './PageTimeline';
+import { PageTimeline } from './PageTimeline';
+import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
 
 const NotFoundPage = (): JSX.Element => {
   const { t } = useTranslation();
 
-  const CustomNavAndContents = dynamic(() => import('./CustomNavigation/CustomNavAndContents'), { ssr: false });
-
   const navTabMapping = useMemo(() => {
     return {
       pagelist: {
@@ -23,8 +22,7 @@ const NotFoundPage = (): JSX.Element => {
       },
       timeLine: {
         Icon: TimeLineIcon,
-        // Content: PageTimeline,
-        Content: () => <></>,
+        Content: PageTimeline,
         i18n: t('Timeline View'),
         index: 1,
       },

+ 1 - 1
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -16,7 +16,6 @@ import { EditorMode, useEditorMode } from '~/stores/ui';
 import CountBadge from '../Common/CountBadge';
 import PageListIcon from '../Icons/PageListIcon';
 import { NotCreatablePage } from '../NotCreatablePage';
-import NotFoundPage from '../NotFoundPage';
 import { Page } from '../Page';
 // import PageEditor from '../PageEditor';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
@@ -37,6 +36,7 @@ const DisplaySwitcher = (): JSX.Element => {
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
+  const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
 
   // get element for smoothScroll
   // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);

+ 0 - 136
packages/app/src/components/PageTimeline.jsx

@@ -1,136 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-import { useTranslation } from 'next-i18next';
-
-import AppContainer from '~/client/services/AppContainer';
-import PageContainer from '~/client/services/PageContainer';
-import { apiv3Get } from '~/client/util/apiv3-client';
-import { RendererOptions } from '~/services/renderer/renderer';
-import { useTimelineOptions } from '~/stores/renderer';
-
-import RevisionLoader from './Page/RevisionLoader';
-import PaginationWrapper from './PaginationWrapper';
-import { withUnstatedContainers } from './UnstatedUtils';
-
-
-class PageTimeline extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      activePage: 1,
-      totalPageItems: 0,
-      limit: null,
-
-      // TODO: remove after when timeline is implemented with React and inject data with props
-      pages: this.props.pages,
-    };
-
-    this.handlePage = this.handlePage.bind(this);
-  }
-
-
-  async handlePage(selectedPage) {
-    const { appContainer, pageContainer } = this.props;
-    const { path } = pageContainer.state;
-    const page = selectedPage;
-
-    const res = await apiv3Get('/pages/list', { path, page });
-    const totalPageItems = res.data.totalCount;
-    const pages = res.data.pages;
-    const pagingLimit = res.data.limit;
-    this.setState({
-      activePage: selectedPage,
-      totalPageItems,
-      pages,
-      limit: pagingLimit,
-    });
-  }
-
-  UNSAFE_componentWillMount() {
-    const { rendererOptions } = this.props;
-    // initialize GrowiRenderer
-    this.rendererOptions = rendererOptions;
-  }
-
-  async componentDidMount() {
-    await this.handlePage(1);
-    this.setState({
-      activePage: 1,
-    });
-  }
-
-  render() {
-    const { t } = this.props;
-    const { pages } = this.state;
-
-    if (pages == null || pages.length === 0) {
-      return (
-        <div className="mt-2">
-          {/* eslint-disable-next-line react/no-danger */}
-          <p>{t('custom_navigation.no_page_list')}</p>
-        </div>
-      );
-    }
-
-    return (
-      <div>
-        { pages.map((page) => {
-          return (
-            <div className="timeline-body" key={`key-${page._id}`}>
-              <div className="card card-timeline">
-                <div className="card-header"><a href={page.path}>{page.path}</a></div>
-                <div className="card-body">
-                  <RevisionLoader
-                    lazy
-                    rendererOptions={this.rendererOptions}
-                    pageId={page._id}
-                    pagePath={page.path}
-                    revisionId={page.revision}
-                  />
-                </div>
-              </div>
-            </div>
-          );
-        }) }
-        <PaginationWrapper
-          activePage={this.state.activePage}
-          changePage={this.handlePage}
-          totalItemsCount={this.state.totalPageItems}
-          pagingLimit={this.state.limit}
-          align="center"
-        />
-      </div>
-    );
-
-  }
-
-}
-
-PageTimeline.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-  pages: PropTypes.arrayOf(PropTypes.object),
-};
-
-const PageTimelineWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const { data: rendererOptions } = useTimelineOptions();
-
-  if (rendererOptions == null) {
-    return <></>;
-  }
-
-  return <PageTimeline t={t} rendererOptions={rendererOptions} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const PageTimelineWrapper = withUnstatedContainers(PageTimelineWrapperFC, [AppContainer, PageContainer]);
-
-export default PageTimelineWrapper;

+ 78 - 0
packages/app/src/components/PageTimeline.tsx

@@ -0,0 +1,78 @@
+import React, { useEffect, useState, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { apiv3Get } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
+import { useCurrentPagePath } from '~/stores/context';
+import { useTimelineOptions } from '~/stores/renderer';
+
+import RevisionLoader from './Page/RevisionLoader';
+import PaginationWrapper from './PaginationWrapper';
+
+export const PageTimeline = (): JSX.Element => {
+  const [activePage, setActivePage] = useState(1);
+  const [totalPageItems, setTotalPageItems] = useState(0);
+  const [limit, setLimit] = useState(10);
+  const [pages, setPages] = useState<IPageHasId[] | null>(null);
+
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { t } = useTranslation();
+  const { data: rendererOptions } = useTimelineOptions();
+
+  const handlePage = useCallback(async(selectedPage: number) => {
+    if (currentPagePath == null) { return }
+    const res = await apiv3Get('/pages/list', { path: currentPagePath, selectedPage });
+    setTotalPageItems(res.data.totalCount);
+    setPages(res.data.pages);
+    setLimit(res.data.limit);
+    setActivePage(selectedPage);
+  }, [currentPagePath]);
+
+  useEffect(() => {
+    handlePage(1);
+  }, [handlePage]);
+
+  if (rendererOptions == null) {
+    return <></>;
+  }
+
+  if (pages == null || pages.length === 0) {
+    return (
+      <div className="mt-2">
+        {/* eslint-disable-next-line react/no-danger */}
+        <p>{t('custom_navigation.no_page_list')}</p>
+      </div>
+    );
+  }
+
+  return (
+    <div>
+      { pages.map((page) => {
+        return (
+          <div className="timeline-body" key={`key-${page._id}`}>
+            <div className="card card-timeline">
+              <div className="card-header"><a href={page.path}>{page.path}</a></div>
+              <div className="card-body">
+                <RevisionLoader
+                  lazy
+                  rendererOptions={rendererOptions}
+                  pageId={page._id}
+                  pagePath={page.path}
+                  revisionId={page.revision}
+                />
+              </div>
+            </div>
+          </div>
+        );
+      }) }
+      <PaginationWrapper
+        activePage={activePage}
+        changePage={handlePage}
+        totalItemsCount={totalPageItems}
+        pagingLimit={limit}
+        align="center"
+      />
+    </div>
+  );
+};

+ 11 - 10
packages/app/src/components/Theme/ThemeFireRed.module.scss

@@ -1,7 +1,8 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
-html[light] {
+.theme[data-color-scheme='light'] :global {
   // Theme colors
   $themecolor: #ea5532;
   $themelight: #ffffff;
@@ -70,8 +71,8 @@ html[light] {
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   // Navs {
   .nav-tabs {
@@ -89,12 +90,12 @@ html[light] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(#ffffff, $primary, $primary, lighten($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, lighten($primary, 20%));
     }
   }
 }
 
-html[dark] {
+.theme[data-color-scheme='dark'] :global {
   // Theme colors
   $themecolor: #ea5532;
   $themedark: #333333;
@@ -171,8 +172,8 @@ html[dark] {
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import 'apply-colors';
-  @import 'apply-colors-dark';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {
@@ -198,7 +199,7 @@ html[dark] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
     }
   }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeFireRed.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeFireRed.module.scss';
+
+const ThemeFireRed = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeFireRed;

+ 3 - 0
packages/app/src/components/Theme/utils/ThemeProvider.tsx

@@ -10,6 +10,7 @@ const ThemeAntarctic = dynamic(() => import('../ThemeAntarctic'));
 const ThemeBlackboard = dynamic(() => import('../ThemeBlackboard'));
 const ThemeChristmas = dynamic(() => import('../ThemeChristmas'));
 const ThemeDefault = dynamic(() => import('../ThemeDefault'));
+const ThemeFireRed = dynamic(() => import('../ThemeFireRed'));
 const ThemeJadeGreen = dynamic(() => import('../ThemeJadeGreen'));
 const ThemeIsland = dynamic(() => import('../ThemeIsland'));
 const ThemeSpring = dynamic(() => import('../ThemeSpring'));
@@ -31,6 +32,8 @@ export const ThemeProvider = ({ theme, children }: Props): JSX.Element => {
       return <ThemeBlackboard>{children}</ThemeBlackboard>;
     case GrowiThemes.CHRISTMAS:
       return <ThemeChristmas>{children}</ThemeChristmas>;
+    case GrowiThemes.FIRE_RED:
+      return <ThemeFireRed>{children}</ThemeFireRed>;
     case GrowiThemes.JADE_GREEN:
       return <ThemeJadeGreen>{children}</ThemeJadeGreen>;
     case GrowiThemes.ISLAND:

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

@@ -22,7 +22,7 @@ import {
 
 const AdminHome = dynamic(() => import('../../components/Admin/AdminHome/AdminHome'), { ssr: false });
 const AppSettingsPageContents = dynamic(() => import('../../components/Admin/App/AppSettingsPageContents'), { ssr: false });
-const SecurityManagementContents = dynamic(() => import('../../components/Admin/Notification/NotificationSetting'), { ssr: false });
+const SecurityManagementContents = dynamic(() => import('../../components/Admin/Security/SecurityManagementContents'), { ssr: false });
 const MarkDownSettingContents = dynamic(() => import('../../components/Admin/MarkdownSetting/MarkDownSettingContents'), { ssr: false });
 const CustomizeSettingContents = dynamic(() => import('../../components/Admin/Customize/Customize'), { ssr: false });
 const DataImportPageContents = dynamic(() => import('../../components/Admin/ImportData/ImportDataPageContents'), { ssr: false });